Skip to main content

omena_parser/
lib.rs

1//! Green-field parser substrate for omena-css.
2//!
3//! This crate is intentionally built next to `engine-style-parser`. It owns the
4//! future cstree parser track, but it does not replace the current product path
5//! until parser parity gates are met.
6
7use cstree::{
8    Syntax,
9    build::GreenNodeBuilder,
10    green::GreenNode,
11    interning::TokenInterner,
12    syntax::SyntaxNode,
13    text::{TextRange, TextSize},
14};
15use omena_interner::{
16    NameKind, intern_class_name, intern_css_ident, intern_custom_property_name, intern_file_path,
17    intern_keyframes_name, intern_mixin_name, intern_property_name, intern_selector_key,
18};
19pub use omena_syntax::StyleDialect;
20use omena_syntax::SyntaxKind;
21use serde::Serialize;
22use std::{
23    collections::{BTreeMap, BTreeSet},
24    sync::Arc,
25};
26
27mod public_product;
28pub use public_product::{
29    ParserCanonicalCandidateBundleV0, ParserCanonicalProducerSignalV0, ParserEvaluatorCandidatesV0,
30    ParserIndexSummaryV0, dialect_for_path, summarize_css_modules_intermediate,
31    summarize_parser_canonical_candidate, summarize_parser_canonical_producer_signal,
32    summarize_parser_evaluator_candidates,
33};
34
35const VALUES_L4_MATH_FUNCTION_NAMES: &[&str] = &[
36    "min", "max", "clamp", "round", "mod", "rem", "sin", "cos", "tan", "asin", "acos", "atan",
37    "atan2", "pow", "sqrt", "hypot", "log", "exp", "abs", "sign",
38];
39
40const CSS_COLOR_FUNCTION_NAMES: &[&str] = &[
41    "rgb",
42    "rgba",
43    "hsl",
44    "hsla",
45    "hwb",
46    "lab",
47    "lch",
48    "oklab",
49    "oklch",
50    "color",
51    "color-mix",
52    "device-cmyk",
53    "light-dark",
54    "contrast-color",
55];
56
57const CSS_GRADIENT_FUNCTION_NAMES: &[&str] = &[
58    "linear-gradient",
59    "radial-gradient",
60    "conic-gradient",
61    "repeating-linear-gradient",
62    "repeating-radial-gradient",
63    "repeating-conic-gradient",
64];
65
66const CSS_TRANSFORM_FUNCTION_NAMES: &[&str] = &[
67    "matrix",
68    "matrix3d",
69    "translate",
70    "translate3d",
71    "translateX",
72    "translateY",
73    "translateZ",
74    "scale",
75    "scale3d",
76    "scaleX",
77    "scaleY",
78    "scaleZ",
79    "rotate",
80    "rotate3d",
81    "rotateX",
82    "rotateY",
83    "rotateZ",
84    "skew",
85    "skewX",
86    "skewY",
87    "perspective",
88];
89
90const CSS_FILTER_FUNCTION_NAMES: &[&str] = &[
91    "blur",
92    "brightness",
93    "contrast",
94    "drop-shadow",
95    "grayscale",
96    "hue-rotate",
97    "invert",
98    "opacity",
99    "saturate",
100    "sepia",
101];
102
103const CSS_IMAGE_FUNCTION_NAMES: &[&str] = &["image", "image-set", "cross-fade", "element", "paint"];
104
105const CSS_SHAPE_FUNCTION_NAMES: &[&str] = &[
106    "path", "shape", "ray", "inset", "circle", "ellipse", "polygon",
107];
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
110#[serde(rename_all = "camelCase")]
111pub struct ParserByteSpanV0 {
112    pub start: usize,
113    pub end: usize,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
117#[serde(rename_all = "camelCase")]
118pub struct ParserPositionV0 {
119    pub line: usize,
120    pub character: usize,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
124#[serde(rename_all = "camelCase")]
125pub struct ParserRangeV0 {
126    pub start: ParserPositionV0,
127    pub end: ParserPositionV0,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum StyleLanguage {
132    Css,
133    Scss,
134    Less,
135}
136
137impl StyleLanguage {
138    pub fn from_module_path(path: &str) -> Option<Self> {
139        if path.ends_with(".module.css") || path.ends_with(".css") {
140            Some(Self::Css)
141        } else if path.ends_with(".module.scss") || path.ends_with(".scss") {
142            Some(Self::Scss)
143        } else if path.ends_with(".module.less") || path.ends_with(".less") {
144            Some(Self::Less)
145        } else {
146            None
147        }
148    }
149}
150
151#[derive(Debug, Clone)]
152pub struct ParseResult {
153    green: GreenNode,
154    interner: Option<Arc<TokenInterner>>,
155    errors: Vec<ParseError>,
156    token_count: usize,
157    dialect: StyleDialect,
158}
159
160impl PartialEq for ParseResult {
161    fn eq(&self, other: &Self) -> bool {
162        self.green == other.green
163            && self.errors == other.errors
164            && self.token_count == other.token_count
165            && self.dialect == other.dialect
166    }
167}
168
169impl Eq for ParseResult {}
170
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct LexResult {
173    tokens: Vec<LexedToken>,
174    errors: Vec<ParseError>,
175    dialect: StyleDialect,
176}
177
178impl LexResult {
179    pub fn tokens(&self) -> &[LexedToken] {
180        &self.tokens
181    }
182
183    pub fn errors(&self) -> &[ParseError] {
184        &self.errors
185    }
186
187    pub fn dialect(&self) -> StyleDialect {
188        self.dialect
189    }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct LexedToken {
194    pub kind: SyntaxKind,
195    pub range: TextRange,
196    pub text: String,
197}
198
199impl ParseResult {
200    pub fn green(&self) -> &GreenNode {
201        &self.green
202    }
203
204    pub fn syntax(&self) -> SyntaxNode<SyntaxKind> {
205        if let Some(interner) = &self.interner {
206            return SyntaxNode::new_root_with_resolver(self.green.clone(), Arc::clone(interner))
207                .syntax()
208                .clone();
209        }
210        SyntaxNode::new_root(self.green.clone())
211    }
212
213    pub fn source_text(&self) -> Option<String> {
214        let syntax = self.syntax();
215        syntax
216            .try_resolved()
217            .map(|resolved| resolved.text().to_string())
218    }
219
220    pub fn errors(&self) -> &[ParseError] {
221        &self.errors
222    }
223
224    pub fn token_count(&self) -> usize {
225        self.token_count
226    }
227
228    pub fn dialect(&self) -> StyleDialect {
229        self.dialect
230    }
231
232    pub fn cst(&self) -> ParsedCst {
233        ParsedCst::new(self.syntax())
234    }
235}
236
237#[derive(Debug, Clone, PartialEq, Eq)]
238pub struct ParseError {
239    pub code: ParseErrorCode,
240    pub range: TextRange,
241    pub message: &'static str,
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
245pub enum ParseErrorCode {
246    UnterminatedBlockComment,
247    UnterminatedString,
248    UnexpectedCharacter,
249    ExpectedSelectorName,
250    UnterminatedAttributeSelector,
251    ExpectedValue,
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
255pub enum ParseEntryPoint {
256    Stylesheet,
257    RuleList,
258    Rule,
259    DeclarationList,
260    Declaration,
261    Value,
262    ComponentValue,
263    ComponentValueList,
264    CommaSeparatedComponentValueList,
265    SimpleBlock,
266}
267
268#[derive(Debug, Clone, PartialEq, Eq)]
269pub struct ParserBoundarySummary {
270    pub product: &'static str,
271    pub tree_model: &'static str,
272    pub parser_track: &'static str,
273    pub dialect_count: usize,
274    pub shared_name_kind_count: usize,
275    pub ready_surfaces: Vec<&'static str>,
276    pub not_ready_surfaces: Vec<&'static str>,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
280pub struct ParserSemanticNameConsumptionSummaryV0 {
281    pub product: &'static str,
282    pub dialect: StyleDialect,
283    pub semantic_name_count: usize,
284    pub interned_name_count: usize,
285    pub invalid_name_count: usize,
286    pub class_name_count: usize,
287    pub css_ident_count: usize,
288    pub property_name_count: usize,
289    pub selector_key_count: usize,
290    pub custom_property_name_count: usize,
291    pub keyframes_name_count: usize,
292    pub mixin_name_count: usize,
293    pub file_path_count: usize,
294    pub ready_surfaces: Vec<&'static str>,
295}
296
297#[derive(Debug, Clone, PartialEq, Eq)]
298pub struct ParserCstEquivalenceSummaryV0 {
299    pub product: &'static str,
300    pub dialect: StyleDialect,
301    pub root_kind: SyntaxKind,
302    pub parser_node_count: usize,
303    pub parser_token_count: usize,
304    pub typed_wrapper_count: usize,
305    pub source_text_round_trip_ready: bool,
306    pub syntax_kind_round_trip_ready: bool,
307    pub zero_unknown_kind_ready: bool,
308    pub typed_cst_wrapper_ready: bool,
309    pub ready_surfaces: Vec<&'static str>,
310}
311
312#[derive(Debug, Clone, PartialEq, Eq)]
313pub struct ParserPrattValueCoverageSummaryV0 {
314    pub product: &'static str,
315    pub infix_operator_kinds: Vec<SyntaxKind>,
316    pub prefix_operator_kinds: Vec<SyntaxKind>,
317    pub value_expression_node_kinds: Vec<SyntaxKind>,
318    pub specialized_function_family_count: usize,
319    pub css_values_l4_math_function_count: usize,
320    pub css_color_function_count: usize,
321    pub ready_surfaces: Vec<&'static str>,
322    pub next_surfaces: Vec<&'static str>,
323}
324
325#[derive(Debug, Clone, PartialEq, Eq)]
326pub struct ParserRecursiveDescentCoverageSummaryV0 {
327    pub product: &'static str,
328    pub dialect_count: usize,
329    pub entry_point_count: usize,
330    pub selector_surface_count: usize,
331    pub at_rule_surface_count: usize,
332    pub dialect_extension_surface_count: usize,
333    pub recovery_surface_count: usize,
334    pub ready_surfaces: Vec<&'static str>,
335    pub next_surfaces: Vec<&'static str>,
336}
337
338#[derive(Debug, Clone, PartialEq, Eq)]
339struct ParserSemanticNameCandidateV0 {
340    kind: NameKind,
341    text: String,
342}
343
344#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct ParsedStyleFacts {
346    pub product: &'static str,
347    pub dialect: StyleDialect,
348    pub selector_count: usize,
349    pub selectors: Vec<ParsedSelectorFact>,
350    pub variable_count: usize,
351    pub variables: Vec<ParsedVariableFact>,
352    pub sass_symbol_count: usize,
353    pub sass_symbols: Vec<ParsedSassSymbolFact>,
354    pub sass_include_count: usize,
355    pub sass_includes: Vec<ParsedSassIncludeFact>,
356    pub sass_module_edge_count: usize,
357    pub sass_module_edges: Vec<ParsedSassModuleEdgeFact>,
358    pub animation_count: usize,
359    pub animations: Vec<ParsedAnimationFact>,
360    pub css_module_value_count: usize,
361    pub css_module_values: Vec<ParsedCssModuleValueFact>,
362    pub css_module_value_import_edge_count: usize,
363    pub css_module_value_import_edges: Vec<ParsedCssModuleValueImportEdgeFact>,
364    pub css_module_value_definition_edge_count: usize,
365    pub css_module_value_definition_edges: Vec<ParsedCssModuleValueDefinitionEdgeFact>,
366    pub css_module_composes_count: usize,
367    pub css_module_composes: Vec<ParsedCssModuleComposesFact>,
368    pub css_module_composes_edge_count: usize,
369    pub css_module_composes_edges: Vec<ParsedCssModuleComposesEdgeFact>,
370    pub icss_count: usize,
371    pub icss: Vec<ParsedIcssFact>,
372    pub icss_import_edge_count: usize,
373    pub icss_import_edges: Vec<ParsedIcssImportEdgeFact>,
374    pub icss_export_edge_count: usize,
375    pub icss_export_edges: Vec<ParsedIcssExportEdgeFact>,
376    pub at_rule_count: usize,
377    pub at_rules: Vec<ParsedAtRuleFact>,
378    pub error_count: usize,
379}
380
381#[derive(Debug, Clone, PartialEq, Eq)]
382pub struct ParsedSelectorFact {
383    pub kind: ParsedSelectorFactKind,
384    pub name: String,
385    pub range: TextRange,
386}
387
388#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
389pub enum ParsedSelectorFactKind {
390    Class,
391    Id,
392    Placeholder,
393}
394
395#[derive(Debug, Clone, PartialEq, Eq)]
396pub struct ParsedVariableFact {
397    pub kind: ParsedVariableFactKind,
398    pub name: String,
399    pub range: TextRange,
400}
401
402#[derive(Debug, Clone, Copy, PartialEq, Eq)]
403pub enum ParsedVariableFactKind {
404    ScssDeclaration,
405    ScssReference,
406    LessDeclaration,
407    LessReference,
408    CustomPropertyDeclaration,
409    CustomPropertyReference,
410}
411
412#[derive(Debug, Clone, PartialEq, Eq)]
413pub struct ParsedSassSymbolFact {
414    pub kind: ParsedSassSymbolFactKind,
415    pub symbol_kind: &'static str,
416    pub name: String,
417    pub role: &'static str,
418    pub namespace: Option<String>,
419    pub range: TextRange,
420}
421
422#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
423pub enum ParsedSassSymbolFactKind {
424    VariableDeclaration,
425    VariableReference,
426    MixinDeclaration,
427    MixinInclude,
428    FunctionDeclaration,
429    FunctionCall,
430}
431
432#[derive(Debug, Clone, PartialEq, Eq)]
433pub struct ParsedSassIncludeFact {
434    pub name: String,
435    pub namespace: Option<String>,
436    pub params: String,
437    pub range: TextRange,
438}
439
440#[derive(Debug, Clone, PartialEq, Eq)]
441pub struct ParsedSassModuleEdgeFact {
442    pub kind: ParsedSassModuleEdgeFactKind,
443    pub source: String,
444    pub namespace_kind: Option<&'static str>,
445    pub namespace: Option<String>,
446    pub visibility_filter_kind: Option<&'static str>,
447    pub visibility_filter_names: Vec<String>,
448    pub range: TextRange,
449}
450
451#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
452pub enum ParsedSassModuleEdgeFactKind {
453    Use,
454    Forward,
455    Import,
456}
457
458#[derive(Debug, Clone, PartialEq, Eq)]
459pub struct ParsedAnimationFact {
460    pub kind: ParsedAnimationFactKind,
461    pub name: String,
462    pub range: TextRange,
463}
464
465#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
466pub enum ParsedAnimationFactKind {
467    KeyframesDeclaration,
468    AnimationNameReference,
469}
470
471#[derive(Debug, Clone, PartialEq, Eq)]
472pub struct ParsedCssModuleValueFact {
473    pub kind: ParsedCssModuleValueFactKind,
474    pub name: String,
475    pub range: TextRange,
476}
477
478#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
479pub enum ParsedCssModuleValueFactKind {
480    Definition,
481    Reference,
482    ImportSource,
483}
484
485#[derive(Debug, Clone, PartialEq, Eq)]
486pub struct ParsedCssModuleValueImportEdgeFact {
487    pub remote_name: String,
488    pub local_name: String,
489    pub import_source: String,
490    pub local_range: TextRange,
491    pub remote_range: TextRange,
492    pub range: TextRange,
493}
494
495#[derive(Debug, Clone, PartialEq, Eq)]
496pub struct ParsedCssModuleValueDefinitionEdgeFact {
497    pub definition_name: String,
498    pub reference_names: Vec<String>,
499    pub range: TextRange,
500}
501
502#[derive(Debug, Clone, PartialEq, Eq)]
503pub struct ParsedCssModuleComposesFact {
504    pub kind: ParsedCssModuleComposesFactKind,
505    pub name: String,
506    pub range: TextRange,
507}
508
509#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
510pub enum ParsedCssModuleComposesFactKind {
511    Target,
512    ImportSource,
513}
514
515#[derive(Debug, Clone, PartialEq, Eq)]
516pub struct ParsedCssModuleComposesEdgeFact {
517    pub kind: ParsedCssModuleComposesEdgeKind,
518    pub owner_selector_names: Vec<String>,
519    pub target_names: Vec<String>,
520    pub import_source: Option<String>,
521    pub range: TextRange,
522}
523
524#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
525pub enum ParsedCssModuleComposesEdgeKind {
526    Local,
527    Global,
528    External,
529}
530
531#[derive(Debug, Clone, PartialEq, Eq)]
532pub struct ParsedIcssFact {
533    pub kind: ParsedIcssFactKind,
534    pub name: String,
535    pub range: TextRange,
536}
537
538#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
539pub enum ParsedIcssFactKind {
540    ExportName,
541    ImportLocalName,
542    ImportRemoteName,
543    ImportSource,
544}
545
546#[derive(Debug, Clone, PartialEq, Eq)]
547pub struct ParsedIcssImportEdgeFact {
548    pub local_name: String,
549    pub remote_name: String,
550    pub import_source: String,
551    pub range: TextRange,
552}
553
554#[derive(Debug, Clone, PartialEq, Eq)]
555pub struct ParsedIcssExportEdgeFact {
556    pub export_name: String,
557    pub reference_names: Vec<String>,
558    pub range: TextRange,
559}
560
561#[derive(Debug, Clone, PartialEq, Eq)]
562pub struct ParsedAtRuleFact {
563    pub name: String,
564    pub node_kind: Option<SyntaxKind>,
565    pub range: TextRange,
566}
567
568#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
569#[serde(rename_all = "camelCase")]
570pub struct OmenaParserStyleFactsSummaryV0 {
571    pub schema_version: &'static str,
572    pub product: &'static str,
573    pub dialect: &'static str,
574    pub class_selector_names: Vec<String>,
575    pub id_selector_names: Vec<String>,
576    pub placeholder_selector_names: Vec<String>,
577    pub keyframe_names: Vec<String>,
578    pub animation_reference_names: Vec<String>,
579    pub css_module_value_definition_names: Vec<String>,
580    pub css_module_value_reference_names: Vec<String>,
581    pub css_module_value_import_sources: Vec<String>,
582    pub css_module_value_import_edges: Vec<OmenaParserCssModuleValueImportEdgeFactV0>,
583    pub css_module_value_definition_edges: Vec<OmenaParserCssModuleValueDefinitionEdgeFactV0>,
584    pub css_module_composes_target_names: Vec<String>,
585    pub css_module_composes_import_sources: Vec<String>,
586    pub css_module_composes_edges: Vec<OmenaParserCssModuleComposesEdgeFactV0>,
587    pub icss_export_names: Vec<String>,
588    pub icss_import_local_names: Vec<String>,
589    pub icss_import_remote_names: Vec<String>,
590    pub icss_import_sources: Vec<String>,
591    pub icss_import_edges: Vec<OmenaParserIcssImportEdgeFactV0>,
592    pub icss_export_edges: Vec<OmenaParserIcssExportEdgeFactV0>,
593    pub variable_names: Vec<String>,
594    pub sass_symbol_declaration_names: Vec<String>,
595    pub sass_symbol_reference_names: Vec<String>,
596    pub sass_symbol_facts: Vec<OmenaParserSassSymbolFactV0>,
597    pub sass_symbol_resolution: OmenaParserSassSymbolResolutionV0,
598    pub sass_module_use_sources: Vec<String>,
599    pub sass_module_forward_sources: Vec<String>,
600    pub sass_module_import_sources: Vec<String>,
601    pub sass_module_edges: Vec<OmenaParserSassModuleEdgeFactV0>,
602    pub custom_property_names: Vec<String>,
603    pub custom_property_decl_names: Vec<String>,
604    pub custom_property_ref_names: Vec<String>,
605    pub at_rule_names: Vec<String>,
606    pub parser_error_count: usize,
607}
608
609#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
610#[serde(rename_all = "camelCase")]
611pub struct OmenaParserCssModuleValueImportEdgeFactV0 {
612    pub remote_name: String,
613    pub local_name: String,
614    pub import_source: String,
615}
616
617#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
618#[serde(rename_all = "camelCase")]
619pub struct OmenaParserCssModuleValueDefinitionEdgeFactV0 {
620    pub definition_name: String,
621    pub reference_names: Vec<String>,
622}
623
624#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
625#[serde(rename_all = "camelCase")]
626pub struct OmenaParserCssModuleComposesEdgeFactV0 {
627    pub kind: &'static str,
628    pub owner_selector_names: Vec<String>,
629    pub target_names: Vec<String>,
630    pub import_source: Option<String>,
631}
632
633#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
634#[serde(rename_all = "camelCase")]
635pub struct OmenaParserIcssImportEdgeFactV0 {
636    pub local_name: String,
637    pub remote_name: String,
638    pub import_source: String,
639}
640
641#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
642#[serde(rename_all = "camelCase")]
643pub struct OmenaParserIcssExportEdgeFactV0 {
644    pub export_name: String,
645    pub reference_names: Vec<String>,
646}
647
648#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
649#[serde(rename_all = "camelCase")]
650pub struct OmenaParserSassSymbolFactV0 {
651    pub kind: &'static str,
652    pub symbol_kind: &'static str,
653    pub name: String,
654    pub role: &'static str,
655    pub namespace: Option<String>,
656}
657
658#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
659#[serde(rename_all = "camelCase")]
660pub struct OmenaParserSassModuleEdgeFactV0 {
661    pub kind: &'static str,
662    pub source: String,
663    pub namespace_kind: Option<&'static str>,
664    pub namespace: Option<String>,
665    pub visibility_filter_kind: Option<&'static str>,
666    pub visibility_filter_names: Vec<String>,
667}
668
669#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
670#[serde(rename_all = "camelCase")]
671pub struct OmenaParserSassSymbolResolutionV0 {
672    pub schema_version: &'static str,
673    pub product: &'static str,
674    pub resolution_scope: &'static str,
675    pub declaration_count: usize,
676    pub reference_count: usize,
677    pub resolved_reference_count: usize,
678    pub unresolved_reference_count: usize,
679    pub edges: Vec<OmenaParserSassSymbolResolutionEdgeV0>,
680    pub capabilities: OmenaParserSassSymbolResolutionCapabilitiesV0,
681}
682
683#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
684#[serde(rename_all = "camelCase")]
685pub struct OmenaParserSassSymbolResolutionEdgeV0 {
686    pub symbol_kind: &'static str,
687    pub name: String,
688    pub namespace: Option<String>,
689    pub reference_kind: &'static str,
690    pub reference_role: &'static str,
691    pub reference_source_order: usize,
692    pub declaration_kind: Option<&'static str>,
693    pub declaration_source_order: Option<usize>,
694    pub status: &'static str,
695}
696
697#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
698#[serde(rename_all = "camelCase")]
699pub struct OmenaParserSassSymbolResolutionCapabilitiesV0 {
700    pub same_file_lexical_resolution_ready: bool,
701    pub declaration_before_reference_ready: bool,
702    pub unresolved_reference_reporting_ready: bool,
703    pub cross_file_module_resolution_ready: bool,
704}
705
706#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
707#[serde(rename_all = "camelCase")]
708pub struct OmenaParserLexSummaryV0 {
709    pub schema_version: &'static str,
710    pub product: &'static str,
711    pub dialect: &'static str,
712    pub tokens: Vec<OmenaParserLexTokenV0>,
713    pub parser_error_count: usize,
714}
715
716#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
717#[serde(rename_all = "camelCase")]
718pub struct OmenaParserLexTokenV0 {
719    pub kind: String,
720    pub text: String,
721    pub start: usize,
722    pub end: usize,
723}
724
725#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
726#[serde(rename_all = "camelCase")]
727pub struct OmenaParserParityLiteSummaryV0 {
728    pub schema_version: &'static str,
729    pub language: &'static str,
730    pub selector_names: Vec<String>,
731    pub keyframes_names: Vec<String>,
732    pub value_decl_names: Vec<String>,
733    pub diagnostic_count: usize,
734    pub rule_count: usize,
735    pub declaration_count: usize,
736    pub grouped_selector_count: usize,
737    pub max_nesting_depth: usize,
738    pub at_rule_kind_counts: OmenaParserAtRuleKindCountsV0,
739    pub declaration_kind_counts: OmenaParserDeclarationKindCountsV0,
740}
741
742#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
743#[serde(rename_all = "camelCase")]
744pub struct OmenaParserAtRuleKindCountsV0 {
745    pub media: usize,
746    pub supports: usize,
747    pub layer: usize,
748    pub keyframes: usize,
749    pub value: usize,
750    pub at_root: usize,
751    pub generic: usize,
752}
753
754#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
755#[serde(rename_all = "camelCase")]
756pub struct OmenaParserDeclarationKindCountsV0 {
757    pub composes: usize,
758    pub animation: usize,
759    pub animation_name: usize,
760    pub generic: usize,
761}
762
763#[derive(Debug, Clone, PartialEq, Eq)]
764pub struct ParsedCst {
765    root: SyntaxNode<SyntaxKind>,
766}
767
768impl ParsedCst {
769    pub fn new(root: SyntaxNode<SyntaxKind>) -> Self {
770        Self { root }
771    }
772
773    pub fn root(&self) -> &SyntaxNode<SyntaxKind> {
774        &self.root
775    }
776
777    pub fn stylesheet(&self) -> Option<StylesheetCstNode> {
778        self.first_node(StylesheetCstNode::cast)
779    }
780
781    pub fn rules(&self) -> Vec<RuleCstNode> {
782        self.nodes(RuleCstNode::cast)
783    }
784
785    pub fn selectors(&self) -> Vec<SelectorCstNode> {
786        self.nodes(SelectorCstNode::cast)
787    }
788
789    pub fn declarations(&self) -> Vec<DeclarationCstNode> {
790        self.nodes(DeclarationCstNode::cast)
791    }
792
793    pub fn declaration_lists(&self) -> Vec<DeclarationListCstNode> {
794        self.nodes(DeclarationListCstNode::cast)
795    }
796
797    pub fn values(&self) -> Vec<ValueCstNode> {
798        self.nodes(ValueCstNode::cast)
799    }
800
801    pub fn component_values(&self) -> Vec<ComponentValueCstNode> {
802        self.nodes(ComponentValueCstNode::cast)
803    }
804
805    pub fn simple_blocks(&self) -> Vec<SimpleBlockCstNode> {
806        self.nodes(SimpleBlockCstNode::cast)
807    }
808
809    pub fn component_value_lists(&self) -> Vec<ComponentValueListCstNode> {
810        self.nodes(ComponentValueListCstNode::cast)
811    }
812
813    pub fn comma_separated_component_value_lists(
814        &self,
815    ) -> Vec<CommaSeparatedComponentValueListCstNode> {
816        self.nodes(CommaSeparatedComponentValueListCstNode::cast)
817    }
818
819    pub fn custom_property_values(&self) -> Vec<CustomPropertyValueCstNode> {
820        self.nodes(CustomPropertyValueCstNode::cast)
821    }
822
823    pub fn at_rules(&self) -> Vec<AtRuleCstNode> {
824        self.nodes(AtRuleCstNode::cast)
825    }
826
827    pub fn bogus_nodes(&self) -> Vec<BogusCstNode> {
828        self.nodes(BogusCstNode::cast)
829    }
830
831    pub fn has_bogus_nodes(&self) -> bool {
832        self.first_node(BogusCstNode::cast).is_some()
833    }
834
835    fn first_node<T>(&self, cast: impl Fn(SyntaxNode<SyntaxKind>) -> Option<T>) -> Option<T> {
836        let mut nodes = Vec::new();
837        collect_typed_nodes(&self.root, &cast, &mut nodes);
838        nodes.into_iter().next()
839    }
840
841    fn nodes<T>(&self, cast: impl Fn(SyntaxNode<SyntaxKind>) -> Option<T>) -> Vec<T> {
842        let mut nodes = Vec::new();
843        collect_typed_nodes(&self.root, &cast, &mut nodes);
844        nodes
845    }
846}
847
848pub trait TypedCstNode: Sized {
849    fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self>;
850    fn syntax(&self) -> &SyntaxNode<SyntaxKind>;
851
852    fn kind(&self) -> SyntaxKind {
853        self.syntax().kind()
854    }
855
856    fn text_range(&self) -> TextRange {
857        self.syntax().text_range()
858    }
859
860    fn into_syntax(self) -> SyntaxNode<SyntaxKind>;
861}
862
863macro_rules! typed_cst_node {
864    ($name:ident, $kind:expr) => {
865        #[derive(Debug, Clone, PartialEq, Eq)]
866        pub struct $name {
867            syntax: SyntaxNode<SyntaxKind>,
868        }
869
870        impl $name {
871            pub const KIND: SyntaxKind = $kind;
872        }
873
874        impl TypedCstNode for $name {
875            fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self> {
876                (syntax.kind() == Self::KIND).then_some(Self { syntax })
877            }
878
879            fn syntax(&self) -> &SyntaxNode<SyntaxKind> {
880                &self.syntax
881            }
882
883            fn into_syntax(self) -> SyntaxNode<SyntaxKind> {
884                self.syntax
885            }
886        }
887    };
888}
889
890typed_cst_node!(StylesheetCstNode, SyntaxKind::Stylesheet);
891typed_cst_node!(RuleCstNode, SyntaxKind::Rule);
892typed_cst_node!(SelectorCstNode, SyntaxKind::Selector);
893typed_cst_node!(DeclarationCstNode, SyntaxKind::Declaration);
894typed_cst_node!(DeclarationListCstNode, SyntaxKind::DeclarationList);
895typed_cst_node!(ValueCstNode, SyntaxKind::Value);
896typed_cst_node!(ComponentValueCstNode, SyntaxKind::ComponentValue);
897typed_cst_node!(SimpleBlockCstNode, SyntaxKind::SimpleBlock);
898typed_cst_node!(ComponentValueListCstNode, SyntaxKind::ComponentValueList);
899typed_cst_node!(
900    CommaSeparatedComponentValueListCstNode,
901    SyntaxKind::CommaSeparatedComponentValueList
902);
903typed_cst_node!(CustomPropertyValueCstNode, SyntaxKind::CustomPropertyValue);
904
905#[derive(Debug, Clone, PartialEq, Eq)]
906pub struct AtRuleCstNode {
907    syntax: SyntaxNode<SyntaxKind>,
908}
909
910#[derive(Debug, Clone, PartialEq, Eq)]
911pub struct BogusCstNode {
912    syntax: SyntaxNode<SyntaxKind>,
913}
914
915impl TypedCstNode for AtRuleCstNode {
916    fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self> {
917        is_at_rule_node_kind(syntax.kind()).then_some(Self { syntax })
918    }
919
920    fn syntax(&self) -> &SyntaxNode<SyntaxKind> {
921        &self.syntax
922    }
923
924    fn into_syntax(self) -> SyntaxNode<SyntaxKind> {
925        self.syntax
926    }
927}
928
929impl TypedCstNode for BogusCstNode {
930    fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self> {
931        syntax.kind().is_bogus().then_some(Self { syntax })
932    }
933
934    fn syntax(&self) -> &SyntaxNode<SyntaxKind> {
935        &self.syntax
936    }
937
938    fn into_syntax(self) -> SyntaxNode<SyntaxKind> {
939        self.syntax
940    }
941}
942
943pub fn is_at_rule_node_kind(kind: SyntaxKind) -> bool {
944    matches!(
945        kind,
946        SyntaxKind::AtRule
947            | SyntaxKind::MediaRule
948            | SyntaxKind::SupportsRule
949            | SyntaxKind::ContainerRule
950            | SyntaxKind::LayerRule
951            | SyntaxKind::ScopeRule
952            | SyntaxKind::KeyframesRule
953            | SyntaxKind::FontFaceRule
954            | SyntaxKind::PageRule
955            | SyntaxKind::NamespaceRule
956            | SyntaxKind::ImportRule
957            | SyntaxKind::CharsetRule
958            | SyntaxKind::PropertyRule
959            | SyntaxKind::StartingStyleRule
960            | SyntaxKind::PageMarginRule
961            | SyntaxKind::WhenRule
962            | SyntaxKind::ElseRule
963            | SyntaxKind::CounterStyleRule
964            | SyntaxKind::FontPaletteValuesRule
965            | SyntaxKind::ColorProfileRule
966            | SyntaxKind::PositionTryRule
967            | SyntaxKind::FontFeatureValuesRule
968            | SyntaxKind::FontFeatureValuesStylisticRule
969            | SyntaxKind::FontFeatureValuesStylesetRule
970            | SyntaxKind::FontFeatureValuesCharacterVariantRule
971            | SyntaxKind::FontFeatureValuesSwashRule
972            | SyntaxKind::FontFeatureValuesOrnamentsRule
973            | SyntaxKind::FontFeatureValuesAnnotationRule
974            | SyntaxKind::FontFeatureValuesHistoricalFormsRule
975            | SyntaxKind::ViewTransitionRule
976            | SyntaxKind::NestRule
977            | SyntaxKind::CustomMediaRule
978            | SyntaxKind::ScssUseRule
979            | SyntaxKind::ScssForwardRule
980            | SyntaxKind::ScssMixinDeclaration
981            | SyntaxKind::ScssIncludeRule
982            | SyntaxKind::ScssFunctionDeclaration
983            | SyntaxKind::ScssReturnRule
984            | SyntaxKind::ScssAtRootRule
985            | SyntaxKind::ScssErrorRule
986            | SyntaxKind::ScssWarnRule
987            | SyntaxKind::ScssDebugRule
988            | SyntaxKind::ScssContentRule
989    )
990}
991
992fn collect_typed_nodes<T>(
993    node: &SyntaxNode<SyntaxKind>,
994    cast: &impl Fn(SyntaxNode<SyntaxKind>) -> Option<T>,
995    nodes: &mut Vec<T>,
996) {
997    if let Some(typed) = cast(node.clone()) {
998        nodes.push(typed);
999    }
1000    for child in node.children() {
1001        collect_typed_nodes(child, cast, nodes);
1002    }
1003}
1004
1005#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1006pub struct TokenSet {
1007    kinds: &'static [SyntaxKind],
1008}
1009
1010impl TokenSet {
1011    pub const fn new(kinds: &'static [SyntaxKind]) -> Self {
1012        Self { kinds }
1013    }
1014
1015    pub fn contains(self, kind: SyntaxKind) -> bool {
1016        self.kinds.contains(&kind)
1017    }
1018
1019    pub fn len(self) -> usize {
1020        self.kinds.len()
1021    }
1022
1023    pub fn is_empty(self) -> bool {
1024        self.kinds.is_empty()
1025    }
1026}
1027
1028pub const RECOVERY_TOP: TokenSet = TokenSet::new(&[
1029    SyntaxKind::AtKeyword,
1030    SyntaxKind::Dot,
1031    SyntaxKind::Hash,
1032    SyntaxKind::RightBrace,
1033    SyntaxKind::Semicolon,
1034]);
1035
1036pub const RECOVERY_DECLARATION: TokenSet =
1037    TokenSet::new(&[SyntaxKind::Semicolon, SyntaxKind::RightBrace]);
1038
1039pub const RECOVERY_SELECTOR: TokenSet = TokenSet::new(&[
1040    SyntaxKind::Comma,
1041    SyntaxKind::LeftBrace,
1042    SyntaxKind::RightBrace,
1043]);
1044
1045pub trait DialectExtension {
1046    fn dialect(&self) -> StyleDialect;
1047
1048    fn classify_variable_token(&self, text: &str) -> Option<SyntaxKind> {
1049        match self.dialect() {
1050            StyleDialect::Css => None,
1051            StyleDialect::Scss | StyleDialect::Sass if text.starts_with('$') => {
1052                Some(SyntaxKind::ScssVariable)
1053            }
1054            StyleDialect::Less if text.starts_with('@') => Some(SyntaxKind::LessVariable),
1055            StyleDialect::Scss | StyleDialect::Sass | StyleDialect::Less => None,
1056        }
1057    }
1058}
1059
1060#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1061pub struct BuiltinDialectExtension {
1062    dialect: StyleDialect,
1063}
1064
1065impl BuiltinDialectExtension {
1066    pub const fn new(dialect: StyleDialect) -> Self {
1067        Self { dialect }
1068    }
1069}
1070
1071impl DialectExtension for BuiltinDialectExtension {
1072    fn dialect(&self) -> StyleDialect {
1073        self.dialect
1074    }
1075}
1076
1077#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1078struct Token<'text> {
1079    kind: SyntaxKind,
1080    text: &'text str,
1081    range: TextRange,
1082}
1083
1084#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1085struct AtRuleSpec {
1086    node_kind: SyntaxKind,
1087    block_kind: AtRuleBlockKind,
1088}
1089
1090#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1091enum AtRuleBlockKind {
1092    GroupRuleList,
1093    DeclarationList,
1094    Keyframes,
1095    Raw,
1096}
1097
1098pub fn parse(text: &str, dialect: StyleDialect) -> ParseResult {
1099    parse_entry_point(text, dialect, ParseEntryPoint::Stylesheet)
1100}
1101
1102pub fn parse_entry_point(
1103    text: &str,
1104    dialect: StyleDialect,
1105    entry_point: ParseEntryPoint,
1106) -> ParseResult {
1107    let extension = BuiltinDialectExtension::new(dialect);
1108    parse_entry_point_with_extension(text, &extension, entry_point)
1109}
1110
1111pub fn lex(text: &str, dialect: StyleDialect) -> LexResult {
1112    let extension = BuiltinDialectExtension::new(dialect);
1113    lex_with_extension(text, &extension)
1114}
1115
1116pub fn lex_with_extension(text: &str, extension: &impl DialectExtension) -> LexResult {
1117    let (tokens, errors) = tokenize(text, extension);
1118    LexResult {
1119        tokens: tokens
1120            .into_iter()
1121            .map(|token| LexedToken {
1122                kind: token.kind,
1123                range: token.range,
1124                text: public_token_text(token.text),
1125            })
1126            .collect(),
1127        errors,
1128        dialect: extension.dialect(),
1129    }
1130}
1131
1132pub fn parse_with_extension(text: &str, extension: &impl DialectExtension) -> ParseResult {
1133    parse_entry_point_with_extension(text, extension, ParseEntryPoint::Stylesheet)
1134}
1135
1136pub fn parse_entry_point_with_extension(
1137    text: &str,
1138    extension: &impl DialectExtension,
1139    entry_point: ParseEntryPoint,
1140) -> ParseResult {
1141    let (tokens, errors) = tokenize(text, extension);
1142    let token_count = tokens.len();
1143    let mut parser = Parser::new(tokens, errors, extension.dialect());
1144    let (green, interner) = parser.parse_entry_point(entry_point);
1145
1146    ParseResult {
1147        green,
1148        interner,
1149        errors: parser.into_errors(),
1150        token_count,
1151        dialect: extension.dialect(),
1152    }
1153}
1154
1155pub fn collect_style_facts(text: &str, dialect: StyleDialect) -> ParsedStyleFacts {
1156    let extension = BuiltinDialectExtension::new(dialect);
1157    collect_style_facts_with_extension(text, &extension)
1158}
1159
1160pub fn summarize_omena_parser_style_facts(
1161    style_source: &str,
1162    dialect: StyleDialect,
1163) -> OmenaParserStyleFactsSummaryV0 {
1164    let facts = collect_style_facts(style_source, dialect);
1165    let sass_symbol_resolution = summarize_omena_parser_sass_symbol_resolution(&facts.sass_symbols);
1166    let mut class_selector_names = Vec::new();
1167    let mut id_selector_names = Vec::new();
1168    let mut placeholder_selector_names = Vec::new();
1169    let mut keyframe_names = Vec::new();
1170    let mut animation_reference_names = Vec::new();
1171    let mut css_module_value_definition_names = BTreeSet::new();
1172    let mut css_module_value_reference_names = BTreeSet::new();
1173    let mut css_module_value_import_sources = BTreeSet::new();
1174    let mut css_module_composes_target_names = BTreeSet::new();
1175    let mut css_module_composes_import_sources = BTreeSet::new();
1176    let mut icss_export_names = BTreeSet::new();
1177    let mut icss_import_local_names = BTreeSet::new();
1178    let mut icss_import_remote_names = BTreeSet::new();
1179    let mut icss_import_sources = BTreeSet::new();
1180    let mut variable_names = BTreeSet::new();
1181    let mut sass_symbol_declaration_names = BTreeSet::new();
1182    let mut sass_symbol_reference_names = BTreeSet::new();
1183    let mut sass_module_use_sources = BTreeSet::new();
1184    let mut sass_module_forward_sources = BTreeSet::new();
1185    let mut sass_module_import_sources = BTreeSet::new();
1186    let mut custom_property_names = BTreeSet::new();
1187    let mut custom_property_decl_names = BTreeSet::new();
1188    let mut custom_property_ref_names = BTreeSet::new();
1189
1190    for selector in facts.selectors {
1191        match selector.kind {
1192            ParsedSelectorFactKind::Class => class_selector_names.push(selector.name),
1193            ParsedSelectorFactKind::Id => id_selector_names.push(selector.name),
1194            ParsedSelectorFactKind::Placeholder => placeholder_selector_names.push(selector.name),
1195        }
1196    }
1197
1198    for variable in facts.variables {
1199        match variable.kind {
1200            ParsedVariableFactKind::ScssDeclaration
1201            | ParsedVariableFactKind::ScssReference
1202            | ParsedVariableFactKind::LessDeclaration
1203            | ParsedVariableFactKind::LessReference => {
1204                variable_names.insert(variable.name);
1205            }
1206            ParsedVariableFactKind::CustomPropertyDeclaration
1207            | ParsedVariableFactKind::CustomPropertyReference => {
1208                custom_property_names.insert(variable.name.clone());
1209                match variable.kind {
1210                    ParsedVariableFactKind::CustomPropertyDeclaration => {
1211                        custom_property_decl_names.insert(variable.name);
1212                    }
1213                    ParsedVariableFactKind::CustomPropertyReference => {
1214                        custom_property_ref_names.insert(variable.name);
1215                    }
1216                    _ => {}
1217                }
1218            }
1219        }
1220    }
1221
1222    for symbol in &facts.sass_symbols {
1223        match symbol.role {
1224            "declaration" => {
1225                sass_symbol_declaration_names.insert(symbol.name.clone());
1226            }
1227            _ => {
1228                sass_symbol_reference_names.insert(symbol.name.clone());
1229            }
1230        }
1231    }
1232
1233    for edge in &facts.sass_module_edges {
1234        match edge.kind {
1235            ParsedSassModuleEdgeFactKind::Use => {
1236                sass_module_use_sources.insert(edge.source.clone());
1237            }
1238            ParsedSassModuleEdgeFactKind::Forward => {
1239                sass_module_forward_sources.insert(edge.source.clone());
1240            }
1241            ParsedSassModuleEdgeFactKind::Import => {
1242                sass_module_import_sources.insert(edge.source.clone());
1243            }
1244        }
1245    }
1246
1247    for animation in facts.animations {
1248        match animation.kind {
1249            ParsedAnimationFactKind::KeyframesDeclaration => keyframe_names.push(animation.name),
1250            ParsedAnimationFactKind::AnimationNameReference => {
1251                animation_reference_names.push(animation.name);
1252            }
1253        }
1254    }
1255
1256    for value in facts.css_module_values {
1257        match value.kind {
1258            ParsedCssModuleValueFactKind::Definition => {
1259                css_module_value_definition_names.insert(value.name);
1260            }
1261            ParsedCssModuleValueFactKind::Reference => {
1262                css_module_value_reference_names.insert(value.name);
1263            }
1264            ParsedCssModuleValueFactKind::ImportSource => {
1265                css_module_value_import_sources.insert(value.name);
1266            }
1267        }
1268    }
1269
1270    for composes in facts.css_module_composes {
1271        match composes.kind {
1272            ParsedCssModuleComposesFactKind::Target => {
1273                css_module_composes_target_names.insert(composes.name);
1274            }
1275            ParsedCssModuleComposesFactKind::ImportSource => {
1276                css_module_composes_import_sources.insert(composes.name);
1277            }
1278        }
1279    }
1280
1281    for icss in facts.icss {
1282        match icss.kind {
1283            ParsedIcssFactKind::ExportName => {
1284                icss_export_names.insert(icss.name);
1285            }
1286            ParsedIcssFactKind::ImportLocalName => {
1287                icss_import_local_names.insert(icss.name);
1288            }
1289            ParsedIcssFactKind::ImportRemoteName => {
1290                icss_import_remote_names.insert(icss.name);
1291            }
1292            ParsedIcssFactKind::ImportSource => {
1293                icss_import_sources.insert(icss.name);
1294            }
1295        }
1296    }
1297
1298    OmenaParserStyleFactsSummaryV0 {
1299        schema_version: "0",
1300        product: "omena-parser.style-facts",
1301        dialect: style_dialect_label(dialect),
1302        class_selector_names,
1303        id_selector_names,
1304        placeholder_selector_names,
1305        keyframe_names,
1306        animation_reference_names,
1307        css_module_value_definition_names: css_module_value_definition_names.into_iter().collect(),
1308        css_module_value_reference_names: css_module_value_reference_names.into_iter().collect(),
1309        css_module_value_import_sources: css_module_value_import_sources.into_iter().collect(),
1310        css_module_value_import_edges: facts
1311            .css_module_value_import_edges
1312            .into_iter()
1313            .map(|edge| OmenaParserCssModuleValueImportEdgeFactV0 {
1314                remote_name: edge.remote_name,
1315                local_name: edge.local_name,
1316                import_source: edge.import_source,
1317            })
1318            .collect(),
1319        css_module_value_definition_edges: facts
1320            .css_module_value_definition_edges
1321            .into_iter()
1322            .map(|edge| OmenaParserCssModuleValueDefinitionEdgeFactV0 {
1323                definition_name: edge.definition_name,
1324                reference_names: edge.reference_names,
1325            })
1326            .collect(),
1327        css_module_composes_target_names: css_module_composes_target_names.into_iter().collect(),
1328        css_module_composes_import_sources: css_module_composes_import_sources
1329            .into_iter()
1330            .collect(),
1331        css_module_composes_edges: facts
1332            .css_module_composes_edges
1333            .into_iter()
1334            .map(|edge| OmenaParserCssModuleComposesEdgeFactV0 {
1335                kind: css_module_composes_edge_kind_label(edge.kind),
1336                owner_selector_names: edge.owner_selector_names,
1337                target_names: edge.target_names,
1338                import_source: edge.import_source,
1339            })
1340            .collect(),
1341        icss_export_names: icss_export_names.into_iter().collect(),
1342        icss_import_local_names: icss_import_local_names.into_iter().collect(),
1343        icss_import_remote_names: icss_import_remote_names.into_iter().collect(),
1344        icss_import_sources: icss_import_sources.into_iter().collect(),
1345        icss_import_edges: facts
1346            .icss_import_edges
1347            .into_iter()
1348            .map(|edge| OmenaParserIcssImportEdgeFactV0 {
1349                local_name: edge.local_name,
1350                remote_name: edge.remote_name,
1351                import_source: edge.import_source,
1352            })
1353            .collect(),
1354        icss_export_edges: facts
1355            .icss_export_edges
1356            .into_iter()
1357            .map(|edge| OmenaParserIcssExportEdgeFactV0 {
1358                export_name: edge.export_name,
1359                reference_names: edge.reference_names,
1360            })
1361            .collect(),
1362        variable_names: variable_names.into_iter().collect(),
1363        sass_symbol_declaration_names: sass_symbol_declaration_names.into_iter().collect(),
1364        sass_symbol_reference_names: sass_symbol_reference_names.into_iter().collect(),
1365        sass_symbol_facts: facts
1366            .sass_symbols
1367            .into_iter()
1368            .map(|symbol| OmenaParserSassSymbolFactV0 {
1369                kind: sass_symbol_fact_kind_label(symbol.kind),
1370                symbol_kind: symbol.symbol_kind,
1371                name: symbol.name,
1372                role: symbol.role,
1373                namespace: symbol.namespace,
1374            })
1375            .collect(),
1376        sass_symbol_resolution,
1377        sass_module_use_sources: sass_module_use_sources.into_iter().collect(),
1378        sass_module_forward_sources: sass_module_forward_sources.into_iter().collect(),
1379        sass_module_import_sources: sass_module_import_sources.into_iter().collect(),
1380        sass_module_edges: facts
1381            .sass_module_edges
1382            .into_iter()
1383            .map(|edge| OmenaParserSassModuleEdgeFactV0 {
1384                kind: sass_module_edge_fact_kind_label(edge.kind),
1385                source: edge.source,
1386                namespace_kind: edge.namespace_kind,
1387                namespace: edge.namespace,
1388                visibility_filter_kind: edge.visibility_filter_kind,
1389                visibility_filter_names: edge.visibility_filter_names,
1390            })
1391            .collect(),
1392        custom_property_names: custom_property_names.into_iter().collect(),
1393        custom_property_decl_names: custom_property_decl_names.into_iter().collect(),
1394        custom_property_ref_names: custom_property_ref_names.into_iter().collect(),
1395        at_rule_names: facts
1396            .at_rules
1397            .into_iter()
1398            .map(|at_rule| at_rule.name)
1399            .collect(),
1400        parser_error_count: facts.error_count,
1401    }
1402}
1403
1404pub fn summarize_omena_parser_lex(source: &str, dialect: StyleDialect) -> OmenaParserLexSummaryV0 {
1405    let result = lex(source, dialect);
1406    OmenaParserLexSummaryV0 {
1407        schema_version: "0",
1408        product: "omena-parser.lex-result",
1409        dialect: style_dialect_label(result.dialect()),
1410        tokens: result
1411            .tokens()
1412            .iter()
1413            .map(|token| OmenaParserLexTokenV0 {
1414                kind: format!("{:?}", token.kind),
1415                text: token.text.clone(),
1416                start: token.range.start().into(),
1417                end: token.range.end().into(),
1418            })
1419            .collect(),
1420        parser_error_count: result.errors().len(),
1421    }
1422}
1423
1424pub fn summarize_omena_parser_parity_lite(
1425    source: &str,
1426    dialect: StyleDialect,
1427) -> OmenaParserParityLiteSummaryV0 {
1428    let facts = collect_style_facts(source, dialect);
1429    let result = parse(source, dialect);
1430    let (tokens, _) = tokenize(source, &BuiltinDialectExtension::new(dialect));
1431    let mut structural = ParserStructuralSummary::default();
1432    summarize_parser_structural_range(&tokens, 0, tokens.len(), 0, &mut structural);
1433    let mut selector_names = collect_parity_lite_selector_names_from_tokens(&tokens);
1434    selector_names.sort();
1435
1436    OmenaParserParityLiteSummaryV0 {
1437        schema_version: "0",
1438        language: style_dialect_label(dialect),
1439        selector_names,
1440        keyframes_names: sorted_unique(
1441            facts
1442                .animations
1443                .iter()
1444                .filter(|animation| animation.kind == ParsedAnimationFactKind::KeyframesDeclaration)
1445                .map(|animation| animation.name.clone()),
1446        ),
1447        value_decl_names: sorted_unique(
1448            facts
1449                .css_module_values
1450                .iter()
1451                .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
1452                .map(|value| value.name.clone()),
1453        ),
1454        diagnostic_count: result.errors().len(),
1455        rule_count: structural.rule_count,
1456        declaration_count: structural.declaration_count,
1457        grouped_selector_count: structural.grouped_selector_count,
1458        max_nesting_depth: structural.max_nesting_depth,
1459        at_rule_kind_counts: structural.at_rule_kind_counts,
1460        declaration_kind_counts: structural.declaration_kind_counts,
1461    }
1462}
1463
1464fn style_dialect_label(dialect: StyleDialect) -> &'static str {
1465    match dialect {
1466        StyleDialect::Css => "css",
1467        StyleDialect::Scss => "scss",
1468        StyleDialect::Sass => "sass",
1469        StyleDialect::Less => "less",
1470    }
1471}
1472
1473#[derive(Default)]
1474struct ParserStructuralSummary {
1475    rule_count: usize,
1476    declaration_count: usize,
1477    grouped_selector_count: usize,
1478    max_nesting_depth: usize,
1479    at_rule_kind_counts: OmenaParserAtRuleKindCountsV0,
1480    declaration_kind_counts: OmenaParserDeclarationKindCountsV0,
1481}
1482
1483fn summarize_parser_structural_range(
1484    tokens: &[Token<'_>],
1485    start: usize,
1486    end: usize,
1487    depth: usize,
1488    summary: &mut ParserStructuralSummary,
1489) {
1490    let mut index = start;
1491    while index < end {
1492        index = skip_trivia_tokens(tokens, index, end);
1493        if index >= end {
1494            break;
1495        }
1496
1497        if tokens[index].kind == SyntaxKind::AtKeyword {
1498            increment_omena_parser_at_rule_kind_count(
1499                &mut summary.at_rule_kind_counts,
1500                classify_omena_parser_at_rule_kind(tokens[index].text),
1501            );
1502            let next_depth = depth + 1;
1503            summary.max_nesting_depth = summary.max_nesting_depth.max(next_depth);
1504            if let Some((open, close)) = find_block_after_header(tokens, index, end) {
1505                summarize_parser_structural_range(tokens, open + 1, close, next_depth, summary);
1506                index = close + 1;
1507            } else {
1508                index = skip_statement(tokens, index, end);
1509            }
1510            continue;
1511        }
1512
1513        let statement_end = css_module_value_statement_end(tokens, index);
1514        if is_root_less_variable_statement(tokens, index, statement_end.min(end), depth) {
1515            increment_omena_parser_at_rule_kind_count(
1516                &mut summary.at_rule_kind_counts,
1517                keyof_omena_parser_at_rule_kind_counts::Kind::Generic,
1518            );
1519            if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
1520                break;
1521            }
1522            index = statement_end + 1;
1523            continue;
1524        }
1525
1526        if statement_end < end && tokens[statement_end].kind == SyntaxKind::LeftBrace {
1527            summary.rule_count += 1;
1528            let next_depth = depth + 1;
1529            summary.max_nesting_depth = summary.max_nesting_depth.max(next_depth);
1530            let group_count = count_omena_parser_selector_groups(tokens, index, statement_end);
1531            if group_count > 1 {
1532                summary.grouped_selector_count += group_count;
1533            }
1534            if let Some(close) = matching_right_brace(tokens, statement_end, end) {
1535                summarize_parser_structural_range(
1536                    tokens,
1537                    statement_end + 1,
1538                    close,
1539                    next_depth,
1540                    summary,
1541                );
1542                index = close + 1;
1543            } else {
1544                index = statement_end + 1;
1545            }
1546            continue;
1547        }
1548
1549        if let Some(colon_index) = declaration_colon_index(tokens, index, statement_end.min(end)) {
1550            summary.declaration_count += 1;
1551            let property = previous_non_trivia_token_index(tokens, colon_index, index)
1552                .map(|property| tokens[property].text)
1553                .unwrap_or_default();
1554            increment_omena_parser_declaration_kind_count(
1555                &mut summary.declaration_kind_counts,
1556                classify_omena_parser_declaration_kind(property),
1557            );
1558        }
1559
1560        if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
1561            break;
1562        }
1563        index = statement_end + 1;
1564    }
1565}
1566
1567fn is_root_less_variable_statement(
1568    tokens: &[Token<'_>],
1569    start: usize,
1570    end: usize,
1571    depth: usize,
1572) -> bool {
1573    if depth != 0 {
1574        return false;
1575    }
1576    let Some(first) = next_non_trivia_token_index_until(tokens, start, end) else {
1577        return false;
1578    };
1579    tokens[first].kind == SyntaxKind::LessVariable
1580        && declaration_colon_index(tokens, first, end).is_some()
1581}
1582
1583fn count_omena_parser_selector_groups(tokens: &[Token<'_>], start: usize, end: usize) -> usize {
1584    split_selector_groups(tokens, start, end)
1585        .into_iter()
1586        .filter(|(group_start, group_end)| {
1587            *group_start < *group_end
1588                && next_non_trivia_token_index_until(tokens, *group_start, *group_end).is_some()
1589        })
1590        .count()
1591}
1592
1593fn collect_parity_lite_selector_names_from_tokens(tokens: &[Token<'_>]) -> Vec<String> {
1594    let mut names = Vec::new();
1595    collect_parity_lite_selector_names_in_range(tokens, 0, tokens.len(), &[], None, &mut names);
1596    names
1597}
1598
1599fn collect_parity_lite_selector_names_in_range(
1600    tokens: &[Token<'_>],
1601    start: usize,
1602    end: usize,
1603    parent_branches: &[SelectorBranch],
1604    css_module_scope: Option<&'static str>,
1605    names: &mut Vec<String>,
1606) {
1607    let mut index = start;
1608    while index < end {
1609        index = skip_trivia_tokens(tokens, index, end);
1610        if index >= end {
1611            break;
1612        }
1613
1614        if tokens[index].kind == SyntaxKind::AtKeyword {
1615            let block = find_block_after_header(tokens, index, end);
1616            if let Some((open, close)) = block {
1617                if tokens[index].text == "@nest" {
1618                    if css_module_scope == Some("global") {
1619                        collect_parity_lite_selector_names_in_range(
1620                            tokens,
1621                            open + 1,
1622                            close,
1623                            &[],
1624                            css_module_scope,
1625                            names,
1626                        );
1627                    } else {
1628                        let branches =
1629                            resolve_selector_header(tokens, index + 1, open, parent_branches);
1630                        names.extend(branches.iter().map(|branch| branch.name.clone()));
1631                        collect_grouped_ampersand_compound_selector_duplicates(
1632                            tokens,
1633                            index + 1,
1634                            open,
1635                            parent_branches.len(),
1636                            names,
1637                        );
1638                        collect_parity_lite_selector_names_in_range(
1639                            tokens,
1640                            open + 1,
1641                            close,
1642                            &branches,
1643                            css_module_scope,
1644                            names,
1645                        );
1646                    }
1647                } else if style_wrapper_at_rule(tokens[index].text) {
1648                    collect_parity_lite_selector_names_in_range(
1649                        tokens,
1650                        open + 1,
1651                        close,
1652                        parent_branches,
1653                        css_module_scope,
1654                        names,
1655                    );
1656                }
1657                index = close + 1;
1658            } else {
1659                index = skip_statement(tokens, index, end);
1660            }
1661            continue;
1662        }
1663
1664        let Some((open, close)) = find_block_after_header(tokens, index, end) else {
1665            index = skip_statement(tokens, index, end);
1666            continue;
1667        };
1668
1669        let effective_scope = css_module_scope
1670            .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
1671        if effective_scope == Some("global") {
1672            collect_parity_lite_selector_names_in_range(
1673                tokens,
1674                open + 1,
1675                close,
1676                &[],
1677                effective_scope,
1678                names,
1679            );
1680        } else {
1681            let branches = resolve_selector_header(tokens, index, open, parent_branches);
1682            names.extend(branches.iter().map(|branch| branch.name.clone()));
1683            collect_grouped_ampersand_compound_selector_duplicates(
1684                tokens,
1685                index,
1686                open,
1687                parent_branches.len(),
1688                names,
1689            );
1690            collect_parity_lite_selector_names_in_range(
1691                tokens,
1692                open + 1,
1693                close,
1694                &branches,
1695                effective_scope,
1696                names,
1697            );
1698        }
1699        index = close + 1;
1700    }
1701}
1702
1703fn collect_grouped_ampersand_compound_selector_duplicates(
1704    tokens: &[Token<'_>],
1705    start: usize,
1706    end: usize,
1707    parent_branch_count: usize,
1708    names: &mut Vec<String>,
1709) {
1710    if parent_branch_count <= 1 || !header_contains_ampersand(tokens, start, end) {
1711        return;
1712    }
1713    for (name, _) in collect_class_selector_names_from_header(tokens, start, end) {
1714        names.extend(std::iter::repeat_n(name, parent_branch_count - 1));
1715    }
1716}
1717
1718fn header_contains_ampersand(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
1719    tokens[start..end]
1720        .iter()
1721        .any(|token| token.kind == SyntaxKind::Ampersand)
1722}
1723
1724fn classify_omena_parser_at_rule_kind(text: &str) -> keyof_omena_parser_at_rule_kind_counts::Kind {
1725    match text.trim_start_matches('@').to_ascii_lowercase().as_str() {
1726        "media" => keyof_omena_parser_at_rule_kind_counts::Kind::Media,
1727        "supports" => keyof_omena_parser_at_rule_kind_counts::Kind::Supports,
1728        "layer" => keyof_omena_parser_at_rule_kind_counts::Kind::Layer,
1729        "keyframes" | "-webkit-keyframes" => {
1730            keyof_omena_parser_at_rule_kind_counts::Kind::Keyframes
1731        }
1732        "value" => keyof_omena_parser_at_rule_kind_counts::Kind::Value,
1733        "at-root" => keyof_omena_parser_at_rule_kind_counts::Kind::AtRoot,
1734        _ => keyof_omena_parser_at_rule_kind_counts::Kind::Generic,
1735    }
1736}
1737
1738fn increment_omena_parser_at_rule_kind_count(
1739    counts: &mut OmenaParserAtRuleKindCountsV0,
1740    kind: keyof_omena_parser_at_rule_kind_counts::Kind,
1741) {
1742    match kind {
1743        keyof_omena_parser_at_rule_kind_counts::Kind::Media => counts.media += 1,
1744        keyof_omena_parser_at_rule_kind_counts::Kind::Supports => counts.supports += 1,
1745        keyof_omena_parser_at_rule_kind_counts::Kind::Layer => counts.layer += 1,
1746        keyof_omena_parser_at_rule_kind_counts::Kind::Keyframes => counts.keyframes += 1,
1747        keyof_omena_parser_at_rule_kind_counts::Kind::Value => counts.value += 1,
1748        keyof_omena_parser_at_rule_kind_counts::Kind::AtRoot => counts.at_root += 1,
1749        keyof_omena_parser_at_rule_kind_counts::Kind::Generic => counts.generic += 1,
1750    }
1751}
1752
1753fn classify_omena_parser_declaration_kind(
1754    property: &str,
1755) -> keyof_omena_parser_declaration_kind_counts::Kind {
1756    match property.trim().to_ascii_lowercase().as_str() {
1757        "composes" => keyof_omena_parser_declaration_kind_counts::Kind::Composes,
1758        "animation" => keyof_omena_parser_declaration_kind_counts::Kind::Animation,
1759        "animation-name" => keyof_omena_parser_declaration_kind_counts::Kind::AnimationName,
1760        _ => keyof_omena_parser_declaration_kind_counts::Kind::Generic,
1761    }
1762}
1763
1764fn increment_omena_parser_declaration_kind_count(
1765    counts: &mut OmenaParserDeclarationKindCountsV0,
1766    kind: keyof_omena_parser_declaration_kind_counts::Kind,
1767) {
1768    match kind {
1769        keyof_omena_parser_declaration_kind_counts::Kind::Composes => counts.composes += 1,
1770        keyof_omena_parser_declaration_kind_counts::Kind::Animation => counts.animation += 1,
1771        keyof_omena_parser_declaration_kind_counts::Kind::AnimationName => {
1772            counts.animation_name += 1
1773        }
1774        keyof_omena_parser_declaration_kind_counts::Kind::Generic => counts.generic += 1,
1775    }
1776}
1777
1778mod keyof_omena_parser_at_rule_kind_counts {
1779    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1780    pub enum Kind {
1781        Media,
1782        Supports,
1783        Layer,
1784        Keyframes,
1785        Value,
1786        AtRoot,
1787        Generic,
1788    }
1789}
1790
1791mod keyof_omena_parser_declaration_kind_counts {
1792    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1793    pub enum Kind {
1794        Composes,
1795        Animation,
1796        AnimationName,
1797        Generic,
1798    }
1799}
1800
1801fn sorted_unique(values: impl IntoIterator<Item = String>) -> Vec<String> {
1802    values
1803        .into_iter()
1804        .collect::<BTreeSet<_>>()
1805        .into_iter()
1806        .collect()
1807}
1808
1809fn css_module_composes_edge_kind_label(kind: ParsedCssModuleComposesEdgeKind) -> &'static str {
1810    match kind {
1811        ParsedCssModuleComposesEdgeKind::Local => "local",
1812        ParsedCssModuleComposesEdgeKind::Global => "global",
1813        ParsedCssModuleComposesEdgeKind::External => "external",
1814    }
1815}
1816
1817fn sass_symbol_fact_kind_label(kind: ParsedSassSymbolFactKind) -> &'static str {
1818    match kind {
1819        ParsedSassSymbolFactKind::VariableDeclaration => "sassVariableDeclaration",
1820        ParsedSassSymbolFactKind::VariableReference => "sassVariableReference",
1821        ParsedSassSymbolFactKind::MixinDeclaration => "sassMixinDeclaration",
1822        ParsedSassSymbolFactKind::MixinInclude => "sassMixinInclude",
1823        ParsedSassSymbolFactKind::FunctionDeclaration => "sassFunctionDeclaration",
1824        ParsedSassSymbolFactKind::FunctionCall => "sassFunctionCall",
1825    }
1826}
1827
1828fn sass_module_edge_fact_kind_label(kind: ParsedSassModuleEdgeFactKind) -> &'static str {
1829    match kind {
1830        ParsedSassModuleEdgeFactKind::Use => "sassUse",
1831        ParsedSassModuleEdgeFactKind::Forward => "sassForward",
1832        ParsedSassModuleEdgeFactKind::Import => "sassImport",
1833    }
1834}
1835
1836fn summarize_omena_parser_sass_symbol_resolution(
1837    symbols: &[ParsedSassSymbolFact],
1838) -> OmenaParserSassSymbolResolutionV0 {
1839    let mut declaration_by_symbol: BTreeMap<
1840        (&'static str, Option<String>, String),
1841        (usize, &'static str),
1842    > = BTreeMap::new();
1843    let mut declaration_count = 0usize;
1844    let mut reference_count = 0usize;
1845    let mut edges = Vec::new();
1846
1847    for (source_order, symbol) in symbols.iter().enumerate() {
1848        let kind = sass_symbol_fact_kind_label(symbol.kind);
1849        if sass_symbol_fact_kind_is_declaration(symbol.kind) {
1850            declaration_count += 1;
1851            declaration_by_symbol.insert(
1852                (
1853                    symbol.symbol_kind,
1854                    symbol.namespace.clone(),
1855                    symbol.name.clone(),
1856                ),
1857                (source_order, kind),
1858            );
1859            continue;
1860        }
1861        if !sass_symbol_fact_kind_is_reference(symbol.kind) {
1862            continue;
1863        }
1864
1865        reference_count += 1;
1866        let declaration = declaration_by_symbol.get(&(
1867            symbol.symbol_kind,
1868            symbol.namespace.clone(),
1869            symbol.name.clone(),
1870        ));
1871        edges.push(OmenaParserSassSymbolResolutionEdgeV0 {
1872            symbol_kind: symbol.symbol_kind,
1873            name: symbol.name.clone(),
1874            namespace: symbol.namespace.clone(),
1875            reference_kind: kind,
1876            reference_role: symbol.role,
1877            reference_source_order: source_order,
1878            declaration_kind: declaration.map(|(_, declaration_kind)| *declaration_kind),
1879            declaration_source_order: declaration.map(|(declaration_order, _)| *declaration_order),
1880            status: if declaration.is_some() {
1881                "resolved"
1882            } else {
1883                "unresolved"
1884            },
1885        });
1886    }
1887
1888    let resolved_reference_count = edges
1889        .iter()
1890        .filter(|edge| edge.status == "resolved")
1891        .count();
1892
1893    OmenaParserSassSymbolResolutionV0 {
1894        schema_version: "0",
1895        product: "omena-parser.sass-symbol-same-file-resolution",
1896        resolution_scope: "same-file",
1897        declaration_count,
1898        reference_count,
1899        resolved_reference_count,
1900        unresolved_reference_count: reference_count.saturating_sub(resolved_reference_count),
1901        edges,
1902        capabilities: OmenaParserSassSymbolResolutionCapabilitiesV0 {
1903            same_file_lexical_resolution_ready: true,
1904            declaration_before_reference_ready: true,
1905            unresolved_reference_reporting_ready: true,
1906            cross_file_module_resolution_ready: false,
1907        },
1908    }
1909}
1910
1911fn sass_symbol_fact_kind_is_declaration(kind: ParsedSassSymbolFactKind) -> bool {
1912    matches!(
1913        kind,
1914        ParsedSassSymbolFactKind::VariableDeclaration
1915            | ParsedSassSymbolFactKind::MixinDeclaration
1916            | ParsedSassSymbolFactKind::FunctionDeclaration
1917    )
1918}
1919
1920fn sass_symbol_fact_kind_is_reference(kind: ParsedSassSymbolFactKind) -> bool {
1921    matches!(
1922        kind,
1923        ParsedSassSymbolFactKind::VariableReference
1924            | ParsedSassSymbolFactKind::MixinInclude
1925            | ParsedSassSymbolFactKind::FunctionCall
1926    )
1927}
1928
1929pub fn summarize_parser_cst_equivalence(
1930    text: &str,
1931    dialect: StyleDialect,
1932) -> ParserCstEquivalenceSummaryV0 {
1933    let result = parse(text, dialect);
1934    let syntax = result.syntax();
1935    let cst = result.cst();
1936
1937    let mut node_count = 0;
1938    let mut token_count = 0;
1939    let mut syntax_kind_round_trip_ready = true;
1940    let mut zero_unknown_kind_ready = true;
1941
1942    for node in syntax.descendants() {
1943        node_count += 1;
1944        let kind = node.kind();
1945        syntax_kind_round_trip_ready &= SyntaxKind::from_raw(kind.into_raw()) == kind;
1946        zero_unknown_kind_ready &= SyntaxKind::ALL.contains(&kind);
1947    }
1948
1949    for token in syntax
1950        .descendants_with_tokens()
1951        .filter_map(|element| element.into_token())
1952    {
1953        token_count += 1;
1954        let kind = token.kind();
1955        syntax_kind_round_trip_ready &= SyntaxKind::from_raw(kind.into_raw()) == kind;
1956        zero_unknown_kind_ready &= SyntaxKind::ALL.contains(&kind);
1957    }
1958
1959    let typed_wrapper_count = usize::from(cst.stylesheet().is_some())
1960        + cst.rules().len()
1961        + cst.selectors().len()
1962        + cst.declarations().len()
1963        + cst.declaration_lists().len()
1964        + cst.values().len()
1965        + cst.component_values().len()
1966        + cst.simple_blocks().len()
1967        + cst.component_value_lists().len()
1968        + cst.comma_separated_component_value_lists().len()
1969        + cst.custom_property_values().len()
1970        + cst.at_rules().len()
1971        + cst.bogus_nodes().len();
1972
1973    ParserCstEquivalenceSummaryV0 {
1974        product: "omena-parser.cst-equivalence",
1975        dialect,
1976        root_kind: syntax.kind(),
1977        parser_node_count: node_count,
1978        parser_token_count: token_count,
1979        typed_wrapper_count,
1980        source_text_round_trip_ready: result.source_text().as_deref() == Some(text),
1981        syntax_kind_round_trip_ready,
1982        zero_unknown_kind_ready,
1983        typed_cst_wrapper_ready: cst.stylesheet().is_some() && typed_wrapper_count > 1,
1984        ready_surfaces: vec![
1985            "parserCstEquivalence",
1986            "parserUsesOmenaSyntaxKind",
1987            "parserCstSourceTextRoundTrip",
1988            "typedCstWrapperEquivalence",
1989        ],
1990    }
1991}
1992
1993pub fn collect_style_facts_with_extension(
1994    text: &str,
1995    extension: &impl DialectExtension,
1996) -> ParsedStyleFacts {
1997    let (tokens, lex_errors) = tokenize(text, extension);
1998    let mut parser = Parser::new(tokens.clone(), lex_errors, extension.dialect());
1999    let _green = parser.parse();
2000    let errors = parser.into_errors();
2001    let selectors = collect_selector_facts_from_tokens(&tokens);
2002    let variables = collect_variable_facts_from_tokens(&tokens);
2003    let sass_symbols = collect_sass_symbol_facts_from_tokens(&tokens);
2004    let sass_includes = collect_sass_include_facts_from_tokens(text, &tokens);
2005    let sass_module_edges = collect_sass_module_edge_facts_from_tokens(&tokens);
2006    let animations = collect_animation_facts_from_tokens(&tokens);
2007    let css_module_values = collect_css_module_value_facts_from_tokens(&tokens);
2008    let css_module_value_import_edges =
2009        collect_css_module_value_import_edge_facts_from_tokens(&tokens);
2010    let css_module_value_definition_edges =
2011        collect_css_module_value_definition_edge_facts_from_tokens(&tokens);
2012    let css_module_composes = collect_css_module_composes_facts_from_tokens(&tokens);
2013    let css_module_composes_edges = collect_css_module_composes_edge_facts_from_tokens(&tokens);
2014    let icss = collect_icss_facts_from_tokens(&tokens);
2015    let icss_import_edges = collect_icss_import_edge_facts_from_tokens(&tokens);
2016    let icss_export_edges = collect_icss_export_edge_facts_from_tokens(&tokens);
2017    let at_rules = collect_at_rule_facts_from_tokens(&tokens, extension.dialect());
2018
2019    ParsedStyleFacts {
2020        product: "omena-parser.style-facts",
2021        dialect: extension.dialect(),
2022        selector_count: selectors.len(),
2023        selectors,
2024        variable_count: variables.len(),
2025        variables,
2026        sass_symbol_count: sass_symbols.len(),
2027        sass_symbols,
2028        sass_include_count: sass_includes.len(),
2029        sass_includes,
2030        sass_module_edge_count: sass_module_edges.len(),
2031        sass_module_edges,
2032        animation_count: animations.len(),
2033        animations,
2034        css_module_value_count: css_module_values.len(),
2035        css_module_values,
2036        css_module_value_import_edge_count: css_module_value_import_edges.len(),
2037        css_module_value_import_edges,
2038        css_module_value_definition_edge_count: css_module_value_definition_edges.len(),
2039        css_module_value_definition_edges,
2040        css_module_composes_count: css_module_composes.len(),
2041        css_module_composes,
2042        css_module_composes_edge_count: css_module_composes_edges.len(),
2043        css_module_composes_edges,
2044        icss_count: icss.len(),
2045        icss,
2046        icss_import_edge_count: icss_import_edges.len(),
2047        icss_import_edges,
2048        icss_export_edge_count: icss_export_edges.len(),
2049        icss_export_edges,
2050        at_rule_count: at_rules.len(),
2051        at_rules,
2052        error_count: errors.len(),
2053    }
2054}
2055
2056pub fn summarize_parser_semantic_name_consumption(
2057    text: &str,
2058    dialect: StyleDialect,
2059    db: &dyn salsa::Database,
2060) -> ParserSemanticNameConsumptionSummaryV0 {
2061    let facts = collect_style_facts(text, dialect);
2062    let candidates = parser_semantic_name_candidates(&facts);
2063    let interned_name_count = candidates
2064        .iter()
2065        .filter(|candidate| intern_parser_semantic_name(db, candidate.kind, &candidate.text))
2066        .count();
2067    let invalid_name_count = candidates.len().saturating_sub(interned_name_count);
2068
2069    ParserSemanticNameConsumptionSummaryV0 {
2070        product: "omena-parser.semantic-name-consumption",
2071        dialect,
2072        semantic_name_count: candidates.len(),
2073        interned_name_count,
2074        invalid_name_count,
2075        class_name_count: count_parser_semantic_name_kind(&candidates, NameKind::ClassName),
2076        css_ident_count: count_parser_semantic_name_kind(&candidates, NameKind::CssIdent),
2077        property_name_count: count_parser_semantic_name_kind(&candidates, NameKind::PropertyName),
2078        selector_key_count: count_parser_semantic_name_kind(&candidates, NameKind::SelectorKey),
2079        custom_property_name_count: count_parser_semantic_name_kind(
2080            &candidates,
2081            NameKind::CustomPropertyName,
2082        ),
2083        keyframes_name_count: count_parser_semantic_name_kind(&candidates, NameKind::KeyframesName),
2084        mixin_name_count: count_parser_semantic_name_kind(&candidates, NameKind::MixinName),
2085        file_path_count: count_parser_semantic_name_kind(&candidates, NameKind::FilePath),
2086        ready_surfaces: vec![
2087            "parserSemanticNameConsumption",
2088            "typedInternerValidation",
2089            "styleFactNameKindProjection",
2090        ],
2091    }
2092}
2093
2094fn parser_semantic_name_candidates(facts: &ParsedStyleFacts) -> Vec<ParserSemanticNameCandidateV0> {
2095    let mut candidates = Vec::new();
2096
2097    for selector in &facts.selectors {
2098        let kind = match selector.kind {
2099            ParsedSelectorFactKind::Class => NameKind::ClassName,
2100            ParsedSelectorFactKind::Id | ParsedSelectorFactKind::Placeholder => {
2101                NameKind::SelectorKey
2102            }
2103        };
2104        push_parser_semantic_name_candidate(&mut candidates, kind, &selector.name);
2105    }
2106
2107    for variable in &facts.variables {
2108        let kind = match variable.kind {
2109            ParsedVariableFactKind::CustomPropertyDeclaration
2110            | ParsedVariableFactKind::CustomPropertyReference => NameKind::CustomPropertyName,
2111            ParsedVariableFactKind::ScssDeclaration
2112            | ParsedVariableFactKind::ScssReference
2113            | ParsedVariableFactKind::LessDeclaration
2114            | ParsedVariableFactKind::LessReference => NameKind::CssIdent,
2115        };
2116        push_parser_semantic_name_candidate(&mut candidates, kind, &variable.name);
2117    }
2118
2119    for symbol in &facts.sass_symbols {
2120        let kind = match symbol.kind {
2121            ParsedSassSymbolFactKind::MixinDeclaration | ParsedSassSymbolFactKind::MixinInclude => {
2122                NameKind::MixinName
2123            }
2124            ParsedSassSymbolFactKind::VariableDeclaration
2125            | ParsedSassSymbolFactKind::VariableReference
2126            | ParsedSassSymbolFactKind::FunctionDeclaration
2127            | ParsedSassSymbolFactKind::FunctionCall => NameKind::CssIdent,
2128        };
2129        push_parser_semantic_name_candidate(&mut candidates, kind, &symbol.name);
2130        if let Some(namespace) = &symbol.namespace {
2131            push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
2132        }
2133    }
2134
2135    for include in &facts.sass_includes {
2136        push_parser_semantic_name_candidate(&mut candidates, NameKind::MixinName, &include.name);
2137        if let Some(namespace) = &include.namespace {
2138            push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
2139        }
2140    }
2141
2142    for edge in &facts.sass_module_edges {
2143        push_parser_semantic_name_candidate(&mut candidates, NameKind::FilePath, &edge.source);
2144        if let Some(namespace) = &edge.namespace {
2145            push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
2146        }
2147    }
2148
2149    for animation in &facts.animations {
2150        push_parser_semantic_name_candidate(
2151            &mut candidates,
2152            NameKind::KeyframesName,
2153            &animation.name,
2154        );
2155    }
2156
2157    for value in &facts.css_module_values {
2158        let kind = match value.kind {
2159            ParsedCssModuleValueFactKind::Definition | ParsedCssModuleValueFactKind::Reference => {
2160                NameKind::CssIdent
2161            }
2162            ParsedCssModuleValueFactKind::ImportSource => NameKind::FilePath,
2163        };
2164        push_parser_semantic_name_candidate(&mut candidates, kind, &value.name);
2165    }
2166
2167    for edge in &facts.css_module_value_import_edges {
2168        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.local_name);
2169        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.remote_name);
2170        push_parser_semantic_name_candidate(
2171            &mut candidates,
2172            NameKind::FilePath,
2173            &edge.import_source,
2174        );
2175    }
2176
2177    for edge in &facts.css_module_value_definition_edges {
2178        push_parser_semantic_name_candidate(
2179            &mut candidates,
2180            NameKind::CssIdent,
2181            &edge.definition_name,
2182        );
2183        for reference_name in &edge.reference_names {
2184            push_parser_semantic_name_candidate(
2185                &mut candidates,
2186                NameKind::CssIdent,
2187                reference_name,
2188            );
2189        }
2190    }
2191
2192    for composes in &facts.css_module_composes {
2193        let kind = match composes.kind {
2194            ParsedCssModuleComposesFactKind::Target => NameKind::ClassName,
2195            ParsedCssModuleComposesFactKind::ImportSource => NameKind::FilePath,
2196        };
2197        push_parser_semantic_name_candidate(&mut candidates, kind, &composes.name);
2198    }
2199
2200    for edge in &facts.css_module_composes_edges {
2201        for owner_selector_name in &edge.owner_selector_names {
2202            push_parser_semantic_name_candidate(
2203                &mut candidates,
2204                NameKind::ClassName,
2205                owner_selector_name,
2206            );
2207        }
2208        for target_name in &edge.target_names {
2209            push_parser_semantic_name_candidate(&mut candidates, NameKind::ClassName, target_name);
2210        }
2211        if let Some(import_source) = &edge.import_source {
2212            push_parser_semantic_name_candidate(&mut candidates, NameKind::FilePath, import_source);
2213        }
2214    }
2215
2216    for icss in &facts.icss {
2217        let kind = match icss.kind {
2218            ParsedIcssFactKind::ImportSource => NameKind::FilePath,
2219            ParsedIcssFactKind::ExportName
2220            | ParsedIcssFactKind::ImportLocalName
2221            | ParsedIcssFactKind::ImportRemoteName => NameKind::CssIdent,
2222        };
2223        push_parser_semantic_name_candidate(&mut candidates, kind, &icss.name);
2224    }
2225
2226    for edge in &facts.icss_import_edges {
2227        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.local_name);
2228        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.remote_name);
2229        push_parser_semantic_name_candidate(
2230            &mut candidates,
2231            NameKind::FilePath,
2232            &edge.import_source,
2233        );
2234    }
2235
2236    for edge in &facts.icss_export_edges {
2237        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.export_name);
2238        for reference_name in &edge.reference_names {
2239            push_parser_semantic_name_candidate(
2240                &mut candidates,
2241                NameKind::CssIdent,
2242                reference_name,
2243            );
2244        }
2245    }
2246
2247    for at_rule in &facts.at_rules {
2248        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &at_rule.name);
2249    }
2250
2251    candidates
2252}
2253
2254fn push_parser_semantic_name_candidate(
2255    candidates: &mut Vec<ParserSemanticNameCandidateV0>,
2256    kind: NameKind,
2257    text: &str,
2258) {
2259    candidates.push(ParserSemanticNameCandidateV0 {
2260        kind,
2261        text: text.to_string(),
2262    });
2263}
2264
2265fn count_parser_semantic_name_kind(
2266    candidates: &[ParserSemanticNameCandidateV0],
2267    kind: NameKind,
2268) -> usize {
2269    candidates
2270        .iter()
2271        .filter(|candidate| candidate.kind == kind)
2272        .count()
2273}
2274
2275fn intern_parser_semantic_name(db: &dyn salsa::Database, kind: NameKind, text: &str) -> bool {
2276    match kind {
2277        NameKind::ClassName => intern_class_name(db, text).is_ok(),
2278        NameKind::CssIdent => intern_css_ident(db, text).is_ok(),
2279        NameKind::PropertyName => intern_property_name(db, text).is_ok(),
2280        NameKind::SelectorKey => intern_selector_key(db, text).is_ok(),
2281        NameKind::CustomPropertyName => intern_custom_property_name(db, text).is_ok(),
2282        NameKind::KeyframesName => intern_keyframes_name(db, text).is_ok(),
2283        NameKind::MixinName => intern_mixin_name(db, text).is_ok(),
2284        NameKind::FilePath => intern_file_path(db, text).is_ok(),
2285    }
2286}
2287
2288pub fn summarize_pratt_value_parser_coverage() -> ParserPrattValueCoverageSummaryV0 {
2289    ParserPrattValueCoverageSummaryV0 {
2290        product: "omena-parser.pratt-value-coverage",
2291        infix_operator_kinds: vec![
2292            SyntaxKind::Plus,
2293            SyntaxKind::Minus,
2294            SyntaxKind::Star,
2295            SyntaxKind::Slash,
2296            SyntaxKind::Percent,
2297        ],
2298        prefix_operator_kinds: vec![SyntaxKind::Plus, SyntaxKind::Minus],
2299        value_expression_node_kinds: vec![
2300            SyntaxKind::UnaryExpression,
2301            SyntaxKind::BinaryExpression,
2302            SyntaxKind::ParenthesizedExpression,
2303            SyntaxKind::FunctionCall,
2304            SyntaxKind::FunctionArguments,
2305            SyntaxKind::ValueList,
2306            SyntaxKind::ComponentValueList,
2307            SyntaxKind::SimpleBlock,
2308            SyntaxKind::BogusValue,
2309        ],
2310        specialized_function_family_count: 10,
2311        css_values_l4_math_function_count: VALUES_L4_MATH_FUNCTION_NAMES.len(),
2312        css_color_function_count: CSS_COLOR_FUNCTION_NAMES.len(),
2313        ready_surfaces: vec![
2314            "prattValueParserCore",
2315            "prefixUnaryExpressions",
2316            "additiveMultiplicativePrecedence",
2317            "parenthesizedValueExpressions",
2318            "functionArgumentValueLists",
2319            "specializedCssValueFunctionFamilies",
2320            "valuesL4MathFunctionArityChecks",
2321            "varEnvAttrFunctionHeadChecks",
2322            "dynamicInterpolationEscapeHatches",
2323            "valueBogusRecovery",
2324        ],
2325        next_surfaces: vec!["fullPropertyValueGrammarRegistry"],
2326    }
2327}
2328
2329pub fn summarize_recursive_descent_parser_coverage() -> ParserRecursiveDescentCoverageSummaryV0 {
2330    ParserRecursiveDescentCoverageSummaryV0 {
2331        product: "omena-parser.recursive-descent-coverage",
2332        dialect_count: 4,
2333        entry_point_count: 10,
2334        selector_surface_count: 12,
2335        at_rule_surface_count: 19,
2336        dialect_extension_surface_count: 17,
2337        recovery_surface_count: 8,
2338        ready_surfaces: vec![
2339            "recursiveDescentParserCore",
2340            "stylesheetRuleDeclarationEntryPoints",
2341            "selectorsLevelFourCstNodes",
2342            "registeredAtRulePreludeParsers",
2343            "cssNestingRuleItems",
2344            "scssDialectStatements",
2345            "sassIndentedBlocks",
2346            "lessDialectStatements",
2347            "bogusRecoverySkeleton",
2348            "styleFactExtractionSurface",
2349        ],
2350        next_surfaces: vec!["completeExternalSpecMirror"],
2351    }
2352}
2353
2354pub fn summarize_parser_boundary() -> ParserBoundarySummary {
2355    ParserBoundarySummary {
2356        product: "omena-parser.boundary",
2357        tree_model: "cstree-green-root",
2358        parser_track: "greenFieldNextToEngineStyleParser",
2359        dialect_count: 4,
2360        shared_name_kind_count: NameKind::ALL.len(),
2361        ready_surfaces: vec![
2362            "lexResult",
2363            "lexedTokenTextSurface",
2364            "parseResult",
2365            "panicFreeTokenizer",
2366            "cstreeGreenBuilder",
2367            "tokenSetRecoveryScaffold",
2368            "dialectExtensionScaffold",
2369            "recursiveDescentParserCore",
2370            "recursiveDescentCoverageSummary",
2371            "selectorCstSkeleton",
2372            "atRuleRegistrySkeleton",
2373            "prattValueExpressionSkeleton",
2374            "prattValueParserCore",
2375            "prattValueCoverageSummary",
2376            "attributeMatcherTokenization",
2377            "attributeMatcherCstNodes",
2378            "attributeNameValueModifierCstNodes",
2379            "specializedValueFunctionCstNodes",
2380            "caseInsensitiveFunctionRegistry",
2381            "caseInsensitiveAtRuleRegistry",
2382            "valueAtomCstNodes",
2383            "identifierValueCstNodes",
2384            "stringValueCstNodes",
2385            "unicodeRangeValueCstNodes",
2386            "functionArgumentValueLists",
2387            "cssModuleScopeFunctionCstNodes",
2388            "cssModuleGlobalSelectorFactFiltering",
2389            "cssModuleLocalIdSelectorFacts",
2390            "cssModuleValueStyleFacts",
2391            "cssModuleValueDeclarationReferenceFacts",
2392            "cssModuleComposesStyleFacts",
2393            "icssStyleFacts",
2394            "animationNameStyleFacts",
2395            "animationShorthandStyleFacts",
2396            "scssStructuredBlockAtRules",
2397            "scssControlPreludeValidation",
2398            "scssControlStyleFactExtraction",
2399            "scssIncludeContentBlockStyleFacts",
2400            "scssSassModuleEdgeStyleFacts",
2401            "scssSassSymbolStyleFacts",
2402            "scssUtilityAtRules",
2403            "scssVariableFlagCstNodes",
2404            "scssNestedPropertyCstNodes",
2405            "scssModulePreludeSourceValidation",
2406            "scssModulePreludeClauseValidation",
2407            "scssModuleConfigCstNodes",
2408            "scssModuleConfigBogusRecovery",
2409            "scssPlaceholderSelectorCstNodes",
2410            "lessMixinDeclarationCstNodes",
2411            "lessMixinCallCstNodes",
2412            "lessMixinGuardCstNodes",
2413            "lessExtendPseudoCstNodes",
2414            "lessDetachedRulesetCstNodes",
2415            "lessNamespaceAccessCstNodes",
2416            "lessPropertyVariableTokenization",
2417            "lessPropertyVariableCstNodes",
2418            "lessEscapedStringTokenization",
2419            "lessEscapedStringValueCstNodes",
2420            "importantAnnotationTokenization",
2421            "urlTokenization",
2422            "urlValueCstNodes",
2423            "quotedUrlFunctionValueCstNodes",
2424            "conditionalAtRulePreludeCstNodes",
2425            "supportsAtRulePreludeValidation",
2426            "conditionalLevel5AtRuleCstNodes",
2427            "mediaQueryCstNodes",
2428            "mediaQueryListValidation",
2429            "importPreludeCstNodes",
2430            "importSourcePreludeValidation",
2431            "importTailPreludeValidation",
2432            "customMediaPreludeValidation",
2433            "propertyAtRuleNameValidation",
2434            "namedAtRulePreludeValidation",
2435            "containerAtRulePreludeValidation",
2436            "charsetNamespaceAtRulePreludeValidation",
2437            "keyframesAtRuleNameValidation",
2438            "emptyBlockAtRulePreludeValidation",
2439            "layerScopePreludeCstNodes",
2440            "layerAtRulePreludeValidation",
2441            "scopeAtRulePreludeValidation",
2442            "pageAtRulePreludeValidation",
2443            "pageMarginAtRuleCstNodes",
2444            "modernDeclarationAtRuleCstNodes",
2445            "fontFeatureValuesAtRuleCstNodes",
2446            "fontFeatureValuesPreludeValidation",
2447            "keyframeSelectorListValidation",
2448            "viewTransitionAtRuleCstNodes",
2449            "genericAtRulePreludeCstNodes",
2450            "bogusAtRulePreludeCstNodes",
2451            "nestingAtRuleCstNodes",
2452            "customMediaAtRuleCstNodes",
2453            "cssColorFunctionCstNodes",
2454            "colorFunctionArgumentChecks",
2455            "gradientFunctionCstNodes",
2456            "transformFunctionCstNodes",
2457            "filterFunctionCstNodes",
2458            "imageFunctionCstNodes",
2459            "shapeFunctionCstNodes",
2460            "envAttrFunctionCstNodes",
2461            "mathFunctionCstNodes",
2462            "mathFunctionArityChecks",
2463            "mathFunctionEmptyArgumentChecks",
2464            "varEnvAttrFunctionHeadChecks",
2465            "scssInterpolationTokenization",
2466            "scssInterpolationCstNodes",
2467            "lessInterpolationTokenization",
2468            "lessInterpolationCstNodes",
2469            "interpolationBogusRecovery",
2470            "unicodeRangeTokenization",
2471            "badStringTokenRecovery",
2472            "badStringValueBogusNodes",
2473            "emptyDeclarationValueRecovery",
2474            "emptyVariableValueRecovery",
2475            "missingSemicolonDeclarationRecovery",
2476            "coreBogusPopulationSlice",
2477            "dialectBogusPopulationSlice",
2478            "cssModuleValueCstNodes",
2479            "cssModuleComposesCstNodes",
2480            "icssModuleBlockCstNodes",
2481            "icssImportSourceValidation",
2482            "cssModuleFromClauseSourceValidation",
2483            "cssModuleComposesMultipleFromValidation",
2484            "cssModuleGlobalComposesValidation",
2485            "cssModuleBogusRecovery",
2486            "valueListCstNodes",
2487            "valueListBogusRecovery",
2488            "genericRecoveryBogusNodes",
2489            "sassIndentedTokenization",
2490            "sassIndentedBlockCstNodes",
2491            "sassIndentedStyleFacts",
2492            "differentialCorpusSeed",
2493            "differentialCorpus",
2494            "lightningCssDifferentialCorpusSlice",
2495            "lightningCssSelectorIdAndAtRuleDifferentialSlice",
2496            "midTypingNoPanicPropertySlice",
2497            "deterministicPanicFreeCorpus",
2498            "losslessCstTextRoundTripSmoke",
2499            "parseResultSourceTextSurface",
2500            "parseSourceParseRoundTripSmoke",
2501            "typedNumericValueAtomCstNodes",
2502            "bracketedValueCstNodes",
2503            "importantAnnotationCstNodes",
2504            "splitImportantAnnotationCstNodes",
2505            "unexpectedValueTokenBogusNodes",
2506            "cdoCdcTokenization",
2507            "cssIdentifierEscapeTokenization",
2508            "nullAndBomInputPreprocessingSlice",
2509            "hashDelimiterTokenization",
2510            "cssDashIdentTokenization",
2511            "signedNumericTokenization",
2512            "exponentNumericTokenization",
2513            "badUrlWhitespaceRecovery",
2514            "parserEntryPointApiSlice",
2515            "ruleListEntryPointApiSlice",
2516            "componentValueEntryPointApiSlice",
2517            "componentValueListEntryPointApiSlice",
2518            "commaSeparatedComponentValueListEntryPointApiSlice",
2519            "simpleBlockEntryPointApiSlice",
2520            "typedCstWrapperSlice",
2521            "parserCstEquivalence",
2522            "typedBogusCstWrapperSlice",
2523            "componentValueCstNodes",
2524            "simpleBlockCstNodes",
2525            "fullBogusPopulation",
2526            "componentValueListCstNodes",
2527            "commaSeparatedComponentValueListCstNodes",
2528            "customPropertyAnyValueComponentList",
2529            "customPropertyValueCstNodes",
2530            "functionalPseudoSelectorListCstNodes",
2531            "strictNotPseudoSelectorListCstNodes",
2532            "nthSelectorOfSelectorListCstNodes",
2533            "nthSelectorFormulaCstNodes",
2534            "hasRelativeSelectorListCstNodes",
2535            "langDirSelectorArgumentCstNodes",
2536            "namespaceQualifiedSelectorCstNodes",
2537            "selectorFunctionArgumentFactExclusion",
2538            "missingBlockCloseBogusTrivia",
2539            "initialDialectStatementNodes",
2540            "recoveryBogusSkeleton",
2541            "styleFactExtractionSurface",
2542            "parserSemanticNameConsumption",
2543        ],
2544        not_ready_surfaces: vec![
2545            "completeExternalSpecMirror",
2546            "fullPropertyValueGrammarRegistry",
2547            "productCutover",
2548        ],
2549    }
2550}
2551
2552fn tokenize<'text>(
2553    text: &'text str,
2554    extension: &impl DialectExtension,
2555) -> (Vec<Token<'text>>, Vec<ParseError>) {
2556    let mut tokenizer = Tokenizer::new(text, extension);
2557    tokenizer.tokenize();
2558    (tokenizer.tokens, tokenizer.errors)
2559}
2560
2561struct Tokenizer<'text, 'extension, E> {
2562    text: &'text str,
2563    extension: &'extension E,
2564    offset: usize,
2565    scss_interpolation_depth: usize,
2566    less_interpolation_depth: usize,
2567    sass_indent_stack: Vec<usize>,
2568    tokens: Vec<Token<'text>>,
2569    errors: Vec<ParseError>,
2570}
2571
2572struct Parser<'text> {
2573    tokens: Vec<Token<'text>>,
2574    position: usize,
2575    dialect: StyleDialect,
2576    builder: GreenNodeBuilder<'static, 'static, SyntaxKind>,
2577    errors: Vec<ParseError>,
2578}
2579
2580impl<'text> Parser<'text> {
2581    fn new(tokens: Vec<Token<'text>>, errors: Vec<ParseError>, dialect: StyleDialect) -> Self {
2582        Self {
2583            tokens,
2584            position: 0,
2585            dialect,
2586            builder: GreenNodeBuilder::new(),
2587            errors,
2588        }
2589    }
2590
2591    fn parse(&mut self) -> (GreenNode, Option<Arc<TokenInterner>>) {
2592        self.parse_entry_point(ParseEntryPoint::Stylesheet)
2593    }
2594
2595    fn parse_entry_point(
2596        &mut self,
2597        entry_point: ParseEntryPoint,
2598    ) -> (GreenNode, Option<Arc<TokenInterner>>) {
2599        self.builder.start_node(SyntaxKind::Root);
2600        match entry_point {
2601            ParseEntryPoint::Stylesheet => {
2602                self.builder.start_node(SyntaxKind::Stylesheet);
2603                self.parse_stylesheet_items();
2604                self.builder.finish_node();
2605            }
2606            ParseEntryPoint::RuleList => {
2607                self.builder.start_node(SyntaxKind::RuleList);
2608                self.parse_rule_list_items();
2609                self.builder.finish_node();
2610            }
2611            ParseEntryPoint::Rule => self.parse_rule(),
2612            ParseEntryPoint::DeclarationList => {
2613                self.builder.start_node(SyntaxKind::DeclarationList);
2614                self.parse_declaration_list();
2615                self.builder.finish_node();
2616            }
2617            ParseEntryPoint::Declaration => self.parse_declaration(),
2618            ParseEntryPoint::Value => {
2619                self.builder.start_node(SyntaxKind::Value);
2620                self.parse_value_or_value_list_until(&[]);
2621                self.builder.finish_node();
2622            }
2623            ParseEntryPoint::ComponentValue => self.parse_component_value(&[]),
2624            ParseEntryPoint::ComponentValueList => self.parse_component_value_list_until(&[]),
2625            ParseEntryPoint::CommaSeparatedComponentValueList => {
2626                self.parse_comma_separated_component_value_list_until(&[])
2627            }
2628            ParseEntryPoint::SimpleBlock => self.parse_simple_block_entry_point(&[]),
2629        }
2630        self.parse_sass_indentation_bogus();
2631        self.parse_entry_point_trailing_bogus();
2632        self.builder.finish_node();
2633
2634        let builder = std::mem::take(&mut self.builder);
2635        let (green, cache) = builder.finish();
2636        let interner = cache.and_then(|cache| cache.into_interner()).map(Arc::new);
2637        (green, interner)
2638    }
2639
2640    fn parse_sass_indentation_bogus(&mut self) {
2641        if self.dialect != StyleDialect::Sass
2642            || !self
2643                .errors
2644                .iter()
2645                .any(|error| error.message == "inconsistent Sass indentation")
2646        {
2647            return;
2648        }
2649        self.builder.start_node(SyntaxKind::BogusSassIndentation);
2650        self.builder.finish_node();
2651    }
2652
2653    fn parse_entry_point_trailing_bogus(&mut self) {
2654        self.eat_trivia();
2655        if self.at_end() {
2656            return;
2657        }
2658        self.builder.start_node(SyntaxKind::BogusRecovery);
2659        while !self.at_end() {
2660            self.token_current();
2661        }
2662        self.builder.finish_node();
2663    }
2664
2665    fn into_errors(self) -> Vec<ParseError> {
2666        self.errors
2667    }
2668
2669    fn parse_stylesheet_items(&mut self) {
2670        while !self.at_end() {
2671            self.eat_trivia();
2672            if self.at_end() {
2673                break;
2674            }
2675            match self.current_kind() {
2676                Some(SyntaxKind::AtKeyword) if self.current_is_css_module_value_rule() => {
2677                    self.parse_css_module_value_rule()
2678                }
2679                Some(SyntaxKind::AtKeyword) if self.current_dialect_at_rule_spec().is_some() => {
2680                    self.parse_dialect_at_rule()
2681                }
2682                Some(SyntaxKind::AtKeyword) => self.parse_at_rule(),
2683                Some(SyntaxKind::ScssVariable)
2684                    if matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass) =>
2685                {
2686                    self.parse_variable_declaration(SyntaxKind::ScssVariableDeclaration)
2687                }
2688                Some(SyntaxKind::LessVariable) if self.dialect == StyleDialect::Less => {
2689                    self.parse_variable_declaration(SyntaxKind::LessVariableDeclaration)
2690                }
2691                Some(SyntaxKind::Cdo | SyntaxKind::Cdc) => self.token_current(),
2692                Some(SyntaxKind::RightBrace | SyntaxKind::SassDedent) => self.token_current(),
2693                Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) => {
2694                    self.token_current()
2695                }
2696                Some(_) => self.parse_rule(),
2697                None => break,
2698            }
2699        }
2700    }
2701
2702    fn parse_rule(&mut self) {
2703        let starts_less_mixin =
2704            self.dialect == StyleDialect::Less && self.current_starts_less_callable_signature();
2705        let has_rule_block = self.find_rule_block_open_before_recovery(&[
2706            SyntaxKind::Semicolon,
2707            SyntaxKind::SassOptionalSemicolon,
2708            SyntaxKind::RightBrace,
2709            SyntaxKind::SassDedent,
2710        ]);
2711        let kind = if let Some(kind) = self
2712            .current_icss_module_rule_kind()
2713            .filter(|_| has_rule_block)
2714        {
2715            kind
2716        } else if self.current_starts_less_mixin_declaration() {
2717            SyntaxKind::LessMixinDeclaration
2718        } else if starts_less_mixin {
2719            SyntaxKind::BogusLessMixin
2720        } else if has_rule_block {
2721            SyntaxKind::Rule
2722        } else {
2723            SyntaxKind::BogusRule
2724        };
2725
2726        self.builder.start_node(kind);
2727        if kind == SyntaxKind::CssModuleImportBlock && !self.current_icss_import_has_source() {
2728            self.error_at_current(ParseErrorCode::ExpectedValue, "expected ICSS import source");
2729        }
2730        if kind == SyntaxKind::LessMixinDeclaration {
2731            self.parse_less_mixin_header();
2732        } else if kind == SyntaxKind::BogusLessMixin {
2733            self.parse_until_recovery_with_optional_less_guard(&[
2734                SyntaxKind::Semicolon,
2735                SyntaxKind::RightBrace,
2736                SyntaxKind::SassDedent,
2737            ]);
2738            self.error_at_current(
2739                ParseErrorCode::UnexpectedCharacter,
2740                "expected Less mixin block",
2741            );
2742        } else {
2743            self.parse_selector_list();
2744        }
2745        if self.current_kind() == Some(SyntaxKind::LeftBrace) {
2746            self.token_current();
2747            self.builder
2748                .start_node(if self.previous_left_brace_has_match() {
2749                    SyntaxKind::DeclarationList
2750                } else {
2751                    SyntaxKind::BogusDeclarationList
2752                });
2753            self.parse_declaration_list();
2754            self.builder.finish_node();
2755            if self.current_kind() == Some(SyntaxKind::RightBrace) {
2756                self.token_current();
2757            } else {
2758                self.missing_token_bogus_trivia(
2759                    ParseErrorCode::UnexpectedCharacter,
2760                    "unterminated declaration block",
2761                );
2762            }
2763        } else if self.current_kind() == Some(SyntaxKind::SassIndent) {
2764            self.builder.start_node(SyntaxKind::SassIndentedBlock);
2765            self.token_current();
2766            self.builder.start_node(SyntaxKind::DeclarationList);
2767            self.parse_declaration_list();
2768            self.builder.finish_node();
2769            if self.current_kind() == Some(SyntaxKind::SassDedent) {
2770                self.token_current();
2771            } else {
2772                self.missing_token_bogus_trivia(
2773                    ParseErrorCode::UnexpectedCharacter,
2774                    "unterminated Sass indented declaration block",
2775                );
2776            }
2777            self.builder.finish_node();
2778        } else {
2779            self.consume_until_recovery(&[
2780                SyntaxKind::Semicolon,
2781                SyntaxKind::SassOptionalSemicolon,
2782                SyntaxKind::RightBrace,
2783                SyntaxKind::SassDedent,
2784            ]);
2785            if self.current_kind().is_some_and(is_statement_end) {
2786                self.token_current();
2787            }
2788        }
2789        self.builder.finish_node();
2790    }
2791
2792    fn current_icss_module_rule_kind(&self) -> Option<SyntaxKind> {
2793        if self.current_kind() != Some(SyntaxKind::Colon) {
2794            return None;
2795        }
2796        let (name_index, name_kind) = self.non_trivia_token_from(self.position + 1)?;
2797        if name_kind != SyntaxKind::Ident {
2798            return None;
2799        }
2800        match self.tokens.get(name_index)?.text {
2801            "export" => Some(SyntaxKind::CssModuleExportBlock),
2802            "import" => Some(SyntaxKind::CssModuleImportBlock),
2803            _ => None,
2804        }
2805    }
2806
2807    fn current_icss_import_has_source(&self) -> bool {
2808        let Some((name_index, SyntaxKind::Ident)) = self.non_trivia_token_from(self.position + 1)
2809        else {
2810            return false;
2811        };
2812        if self
2813            .tokens
2814            .get(name_index)
2815            .is_none_or(|token| token.text != "import")
2816        {
2817            return false;
2818        }
2819        let Some((open_index, SyntaxKind::LeftParen)) = self.non_trivia_token_from(name_index + 1)
2820        else {
2821            return false;
2822        };
2823        let Some((_, source_kind)) = self.non_trivia_token_from(open_index + 1) else {
2824            return false;
2825        };
2826        matches!(
2827            source_kind,
2828            SyntaxKind::String | SyntaxKind::Url | SyntaxKind::ScssInterpolationStart
2829        )
2830    }
2831
2832    fn parse_selector_list(&mut self) {
2833        self.parse_selector_list_until(&[]);
2834    }
2835
2836    fn parse_selector_list_until(&mut self, recovery: &[SyntaxKind]) {
2837        let kind = if self.current_kind() == Some(SyntaxKind::LeftBrace) {
2838            SyntaxKind::BogusSelectorList
2839        } else {
2840            SyntaxKind::SelectorList
2841        };
2842        self.builder.start_node(kind);
2843        while !self.at_end() {
2844            match self.current_kind() {
2845                Some(SyntaxKind::Comma) => self.token_current(),
2846                Some(kind) if is_selector_boundary_until(kind, recovery) => break,
2847                Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
2848                Some(_)
2849                    if recovery.contains(&SyntaxKind::RightParen)
2850                        && self.current_selector_item_is_bogus(recovery) =>
2851                {
2852                    self.parse_bogus_selector_until(recovery)
2853                }
2854                Some(_) => self.parse_selector_until(recovery),
2855                None => break,
2856            }
2857        }
2858        self.builder.finish_node();
2859    }
2860
2861    fn parse_strict_selector_list_until(&mut self, recovery: &[SyntaxKind]) {
2862        self.builder.start_node(
2863            if self.selector_list_contains_bogus_item_until(recovery)
2864                && self.current_kind() != Some(SyntaxKind::RightParen)
2865            {
2866                SyntaxKind::BogusSelectorList
2867            } else {
2868                SyntaxKind::SelectorList
2869            },
2870        );
2871        while !self.at_end() {
2872            match self.current_kind() {
2873                Some(SyntaxKind::Comma) => self.token_current(),
2874                Some(kind) if is_selector_boundary_until(kind, recovery) => break,
2875                Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
2876                Some(_)
2877                    if self.current_selector_item_is_bogus(recovery)
2878                        && self.current_kind() != Some(SyntaxKind::RightParen) =>
2879                {
2880                    self.parse_bogus_selector_until(recovery)
2881                }
2882                Some(_) => self.parse_selector_until(recovery),
2883                None => break,
2884            }
2885        }
2886        self.builder.finish_node();
2887    }
2888
2889    fn parse_relative_selector_list_until(&mut self, recovery: &[SyntaxKind]) {
2890        self.builder.start_node(
2891            if self.current_selector_item_is_bogus(recovery)
2892                && self.current_kind() != Some(SyntaxKind::RightParen)
2893            {
2894                SyntaxKind::BogusSelectorList
2895            } else {
2896                SyntaxKind::RelativeSelectorList
2897            },
2898        );
2899        while !self.at_end() {
2900            match self.current_kind() {
2901                Some(SyntaxKind::Comma) => self.token_current(),
2902                Some(kind) if is_selector_boundary_until(kind, recovery) => break,
2903                Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
2904                Some(_)
2905                    if self.current_selector_item_is_bogus(recovery)
2906                        && self.current_kind() != Some(SyntaxKind::RightParen) =>
2907                {
2908                    self.parse_bogus_selector_until(recovery)
2909                }
2910                Some(_) => self.parse_relative_selector_until(recovery),
2911                None => break,
2912            }
2913        }
2914        self.builder.finish_node();
2915    }
2916
2917    fn parse_relative_selector_until(&mut self, recovery: &[SyntaxKind]) {
2918        self.builder.start_node(SyntaxKind::RelativeSelector);
2919        self.builder.start_node(SyntaxKind::ComplexSelector);
2920        self.parse_complex_selector_until(recovery);
2921        self.builder.finish_node();
2922        self.builder.finish_node();
2923    }
2924
2925    fn parse_bogus_selector_until(&mut self, recovery: &[SyntaxKind]) {
2926        self.builder.start_node(SyntaxKind::BogusSelector);
2927        self.error_at_current(
2928            ParseErrorCode::UnexpectedCharacter,
2929            "invalid selector in selector list",
2930        );
2931        let mut paren_depth = 0usize;
2932        let mut bracket_depth = 0usize;
2933        while !self.at_end() {
2934            let Some(kind) = self.current_kind() else {
2935                break;
2936            };
2937            if paren_depth == 0
2938                && bracket_depth == 0
2939                && (kind == SyntaxKind::Comma || is_selector_boundary_until(kind, recovery))
2940            {
2941                break;
2942            }
2943            match kind {
2944                SyntaxKind::LeftParen => paren_depth += 1,
2945                SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
2946                SyntaxKind::LeftBracket => bracket_depth += 1,
2947                SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
2948                _ => {}
2949            }
2950            self.token_current();
2951        }
2952        self.builder.finish_node();
2953    }
2954
2955    fn parse_selector_until(&mut self, recovery: &[SyntaxKind]) {
2956        self.builder.start_node(SyntaxKind::Selector);
2957        self.builder.start_node(SyntaxKind::ComplexSelector);
2958        self.parse_complex_selector_until(recovery);
2959        self.builder.finish_node();
2960        self.builder.finish_node();
2961    }
2962
2963    fn parse_complex_selector_until(&mut self, recovery: &[SyntaxKind]) {
2964        let mut has_component = false;
2965        while !self.at_end() {
2966            match self.current_kind() {
2967                Some(kind) if is_selector_boundary_until(kind, recovery) => break,
2968                Some(SyntaxKind::Whitespace) => {
2969                    if has_component
2970                        && self.next_non_trivia_kind().is_some_and(|kind| {
2971                            !is_selector_boundary_until(kind, recovery) && !is_combinator(kind)
2972                        })
2973                    {
2974                        self.parse_whitespace_combinator();
2975                        has_component = false;
2976                    } else {
2977                        self.token_current();
2978                    }
2979                }
2980                Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
2981                Some(kind) if is_combinator(kind) => {
2982                    self.parse_combinator();
2983                    has_component = false;
2984                }
2985                Some(_) => {
2986                    self.parse_compound_selector_until(recovery);
2987                    has_component = true;
2988                }
2989                None => break,
2990            }
2991        }
2992    }
2993
2994    fn parse_compound_selector_until(&mut self, recovery: &[SyntaxKind]) {
2995        let starts_valid = self.current_kind().is_some_and(|kind| {
2996            selector_component_can_start(kind)
2997                || self.current_starts_namespace_qualified_selector(kind)
2998                || is_interpolation_start(kind)
2999        });
3000        self.builder.start_node(if starts_valid {
3001            SyntaxKind::CompoundSelector
3002        } else {
3003            SyntaxKind::BogusCompoundSelector
3004        });
3005        let start = self.position;
3006        while !self.at_end() {
3007            match self.current_kind() {
3008                Some(kind)
3009                    if is_selector_boundary_until(kind, recovery)
3010                        || kind == SyntaxKind::Whitespace
3011                        || kind == SyntaxKind::SassIndentedNewline
3012                        || is_combinator(kind) =>
3013                {
3014                    break;
3015                }
3016                Some(SyntaxKind::Dot) => self.parse_class_selector(),
3017                Some(SyntaxKind::Hash) => self.parse_id_selector(),
3018                Some(kind) if self.current_starts_namespace_qualified_selector(kind) => {
3019                    self.parse_namespace_qualified_selector()
3020                }
3021                Some(SyntaxKind::Ident) => self.parse_type_selector(),
3022                Some(SyntaxKind::Star) => self.parse_universal_selector(),
3023                Some(SyntaxKind::Ampersand) => self.parse_nesting_selector(),
3024                Some(SyntaxKind::ScssPlaceholder) => self.parse_scss_placeholder_selector(),
3025                Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3026                    kind,
3027                    &[
3028                        SyntaxKind::Comma,
3029                        SyntaxKind::LeftBrace,
3030                        SyntaxKind::SassIndent,
3031                        SyntaxKind::RightBrace,
3032                        SyntaxKind::SassDedent,
3033                        SyntaxKind::RightParen,
3034                        SyntaxKind::Semicolon,
3035                        SyntaxKind::SassOptionalSemicolon,
3036                    ],
3037                ),
3038                Some(SyntaxKind::LeftBracket) => self.parse_attribute_selector(),
3039                Some(SyntaxKind::Colon) if self.current_starts_less_extend_rule() => {
3040                    self.parse_less_extend_rule()
3041                }
3042                Some(SyntaxKind::Colon) => {
3043                    self.parse_pseudo_selector(SyntaxKind::PseudoClassSelector)
3044                }
3045                Some(SyntaxKind::DoubleColon) => {
3046                    self.parse_pseudo_selector(SyntaxKind::PseudoElementSelector)
3047                }
3048                Some(_) => self.token_current(),
3049                None => break,
3050            }
3051        }
3052        if self.position == start {
3053            self.token_current();
3054        }
3055        if !starts_valid {
3056            self.error_at_current(
3057                ParseErrorCode::UnexpectedCharacter,
3058                "expected selector component",
3059            );
3060        }
3061        self.builder.finish_node();
3062    }
3063
3064    fn parse_class_selector(&mut self) {
3065        self.builder.start_node(SyntaxKind::ClassSelector);
3066        self.token_current();
3067        if matches!(
3068            self.current_kind(),
3069            Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
3070        ) {
3071            self.token_current();
3072        } else {
3073            self.empty_bogus_node(
3074                SyntaxKind::BogusSelector,
3075                ParseErrorCode::ExpectedSelectorName,
3076                "expected class selector name",
3077            );
3078        }
3079        self.builder.finish_node();
3080    }
3081
3082    fn parse_id_selector(&mut self) {
3083        self.builder.start_node(SyntaxKind::IdSelector);
3084        self.token_current();
3085        self.builder.finish_node();
3086    }
3087
3088    fn parse_type_selector(&mut self) {
3089        self.builder.start_node(SyntaxKind::TypeSelector);
3090        self.token_current();
3091        self.builder.finish_node();
3092    }
3093
3094    fn parse_universal_selector(&mut self) {
3095        self.builder.start_node(SyntaxKind::UniversalSelector);
3096        self.token_current();
3097        self.builder.finish_node();
3098    }
3099
3100    fn parse_namespace_qualified_selector(&mut self) {
3101        let selector_kind =
3102            if self.namespace_qualified_selector_target_kind() == Some(SyntaxKind::Star) {
3103                SyntaxKind::UniversalSelector
3104            } else {
3105                SyntaxKind::TypeSelector
3106            };
3107        self.builder.start_node(selector_kind);
3108        self.builder.start_node(SyntaxKind::NamespacePrefix);
3109        if self.current_kind() != Some(SyntaxKind::Pipe) {
3110            self.token_current();
3111        }
3112        self.token_current();
3113        self.builder.finish_node();
3114        if matches!(
3115            self.current_kind(),
3116            Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star)
3117        ) {
3118            self.token_current();
3119        } else {
3120            self.empty_bogus_node(
3121                SyntaxKind::BogusSelector,
3122                ParseErrorCode::ExpectedSelectorName,
3123                "expected namespace-qualified selector name",
3124            );
3125        }
3126        self.builder.finish_node();
3127    }
3128
3129    fn parse_nesting_selector(&mut self) {
3130        self.builder.start_node(SyntaxKind::NestingSelectorNode);
3131        self.token_current();
3132        self.builder.finish_node();
3133    }
3134
3135    fn parse_scss_placeholder_selector(&mut self) {
3136        self.builder.start_node(SyntaxKind::ScssPlaceholderSelector);
3137        self.token_current();
3138        self.builder.finish_node();
3139    }
3140
3141    fn parse_attribute_selector(&mut self) {
3142        let kind = if self.find_before_recovery(
3143            SyntaxKind::RightBracket,
3144            &[
3145                SyntaxKind::Comma,
3146                SyntaxKind::LeftBrace,
3147                SyntaxKind::RightBrace,
3148                SyntaxKind::Semicolon,
3149            ],
3150        ) {
3151            SyntaxKind::AttributeSelector
3152        } else {
3153            SyntaxKind::BogusSelector
3154        };
3155        self.builder.start_node(kind);
3156        self.token_current();
3157        let mut saw_matcher = false;
3158        let mut saw_value = false;
3159        let mut closed = false;
3160        while !self.at_end() {
3161            match self.current_kind() {
3162                Some(SyntaxKind::RightBracket) => {
3163                    self.token_current();
3164                    closed = true;
3165                    break;
3166                }
3167                Some(kind) if is_attribute_matcher(kind) => {
3168                    self.parse_attribute_matcher();
3169                    saw_matcher = true;
3170                }
3171                Some(kind) if is_selector_boundary(kind) => break,
3172                Some(kind) if !saw_matcher && attribute_name_token_can_start(kind) => {
3173                    self.parse_attribute_name()
3174                }
3175                Some(kind)
3176                    if saw_matcher && !saw_value && attribute_value_token_can_start(kind) =>
3177                {
3178                    self.parse_attribute_value();
3179                    saw_value = true;
3180                }
3181                Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) if saw_value => {
3182                    self.parse_attribute_modifier()
3183                }
3184                Some(_) => self.token_current(),
3185                None => break,
3186            }
3187        }
3188        if !closed {
3189            self.error_at_current(
3190                ParseErrorCode::UnterminatedAttributeSelector,
3191                "unterminated attribute selector",
3192            );
3193        }
3194        self.builder.finish_node();
3195    }
3196
3197    fn parse_attribute_matcher(&mut self) {
3198        self.builder.start_node(SyntaxKind::AttributeMatcher);
3199        self.token_current();
3200        self.builder.finish_node();
3201    }
3202
3203    fn parse_attribute_name(&mut self) {
3204        self.builder.start_node(SyntaxKind::AttributeName);
3205        while !self.at_end() {
3206            match self.current_kind() {
3207                Some(SyntaxKind::RightBracket) => break,
3208                Some(kind) if is_attribute_matcher(kind) || is_selector_boundary(kind) => break,
3209                Some(kind) if attribute_name_token_can_continue(kind) => self.token_current(),
3210                Some(_) => break,
3211                None => break,
3212            }
3213        }
3214        self.builder.finish_node();
3215    }
3216
3217    fn parse_attribute_value(&mut self) {
3218        self.builder.start_node(SyntaxKind::AttributeValue);
3219        self.token_current();
3220        self.builder.finish_node();
3221    }
3222
3223    fn parse_attribute_modifier(&mut self) {
3224        self.builder.start_node(SyntaxKind::AttributeModifier);
3225        self.token_current();
3226        self.builder.finish_node();
3227    }
3228
3229    fn parse_pseudo_selector(&mut self, kind: SyntaxKind) {
3230        self.builder.start_node(kind);
3231        self.token_current();
3232        let pseudo_name = self.current_text().map(str::to_owned);
3233        let css_module_scope_kind = if kind == SyntaxKind::PseudoClassSelector {
3234            self.current_text().and_then(css_module_scope_function_kind)
3235        } else {
3236            None
3237        };
3238        if self.current_kind() == Some(SyntaxKind::Ident) {
3239            if let Some(kind) = css_module_scope_kind {
3240                self.builder.start_node(kind);
3241            }
3242            self.token_current();
3243        } else {
3244            self.empty_bogus_node(
3245                SyntaxKind::BogusSelector,
3246                ParseErrorCode::ExpectedSelectorName,
3247                "expected pseudo selector name",
3248            );
3249        }
3250        if self.current_kind() == Some(SyntaxKind::LeftParen) {
3251            self.token_current();
3252            self.builder.start_node(SyntaxKind::PseudoSelectorArgument);
3253            if kind == SyntaxKind::PseudoClassSelector
3254                && pseudo_name
3255                    .as_deref()
3256                    .is_some_and(is_selector_list_pseudo_class)
3257            {
3258                self.parse_selector_list_until(&[SyntaxKind::RightParen]);
3259            } else if kind == SyntaxKind::PseudoClassSelector
3260                && pseudo_name.as_deref() == Some("not")
3261            {
3262                self.parse_strict_selector_list_until(&[SyntaxKind::RightParen]);
3263            } else if kind == SyntaxKind::PseudoClassSelector
3264                && pseudo_name.as_deref() == Some("has")
3265            {
3266                self.parse_relative_selector_list_until(&[SyntaxKind::RightParen]);
3267            } else if kind == SyntaxKind::PseudoClassSelector
3268                && pseudo_name.as_deref().is_some_and(is_nth_pseudo_class)
3269            {
3270                self.parse_nth_selector_argument();
3271            } else if kind == SyntaxKind::PseudoClassSelector
3272                && pseudo_name.as_deref() == Some("lang")
3273            {
3274                self.parse_language_selector_argument();
3275            } else if kind == SyntaxKind::PseudoClassSelector
3276                && pseudo_name.as_deref() == Some("dir")
3277            {
3278                self.parse_directionality_selector_argument();
3279            } else {
3280                while !self.at_end() {
3281                    match self.current_kind() {
3282                        Some(SyntaxKind::RightParen) => break,
3283                        Some(kind) if is_selector_boundary(kind) => break,
3284                        Some(_) => self.token_current(),
3285                        None => break,
3286                    }
3287                }
3288            }
3289            self.builder.finish_node();
3290            if self.current_kind() == Some(SyntaxKind::RightParen) {
3291                self.token_current();
3292            }
3293        }
3294        if css_module_scope_kind.is_some() {
3295            self.builder.finish_node();
3296        }
3297        self.builder.finish_node();
3298    }
3299
3300    fn parse_nth_selector_argument(&mut self) {
3301        self.builder.start_node(SyntaxKind::NthSelectorArgument);
3302        self.builder.start_node(SyntaxKind::NthSelectorFormula);
3303        while !self.at_end() {
3304            match self.current_kind() {
3305                Some(SyntaxKind::RightParen) => break,
3306                Some(kind) if is_selector_boundary(kind) => break,
3307                Some(SyntaxKind::Ident) if self.current_text() == Some("of") => break,
3308                Some(_) => self.token_current(),
3309                None => break,
3310            }
3311        }
3312        self.builder.finish_node();
3313
3314        if self.current_kind() == Some(SyntaxKind::Ident) && self.current_text() == Some("of") {
3315            self.builder
3316                .start_node(SyntaxKind::NthSelectorOfSelectorList);
3317            self.token_current();
3318            self.parse_selector_list_until(&[SyntaxKind::RightParen]);
3319            self.builder.finish_node();
3320        }
3321
3322        self.builder.finish_node();
3323    }
3324
3325    fn parse_language_selector_argument(&mut self) {
3326        self.builder
3327            .start_node(SyntaxKind::LanguageSelectorArgument);
3328        while !self.at_end() {
3329            match self.current_kind() {
3330                Some(SyntaxKind::RightParen) => break,
3331                Some(SyntaxKind::Comma) => self.token_current(),
3332                Some(kind) if is_selector_boundary(kind) => break,
3333                Some(kind) if language_tag_token_can_start(kind) => self.parse_language_tag(),
3334                Some(_) => self.token_current(),
3335                None => break,
3336            }
3337        }
3338        self.builder.finish_node();
3339    }
3340
3341    fn parse_language_tag(&mut self) {
3342        self.builder.start_node(SyntaxKind::LanguageTag);
3343        self.token_current();
3344        self.builder.finish_node();
3345    }
3346
3347    fn parse_directionality_selector_argument(&mut self) {
3348        self.builder
3349            .start_node(SyntaxKind::DirectionalitySelectorArgument);
3350        if self
3351            .current_kind()
3352            .is_some_and(language_tag_token_can_start)
3353        {
3354            self.token_current();
3355        }
3356        while !self.at_end() {
3357            match self.current_kind() {
3358                Some(SyntaxKind::RightParen) => break,
3359                Some(kind) if is_selector_boundary(kind) => break,
3360                Some(_) => self.token_current(),
3361                None => break,
3362            }
3363        }
3364        self.builder.finish_node();
3365    }
3366
3367    fn parse_less_extend_rule(&mut self) {
3368        self.builder.start_node(SyntaxKind::LessExtendRule);
3369        if self.current_kind() == Some(SyntaxKind::Colon) {
3370            self.token_current();
3371        }
3372        if self.current_text() == Some("extend") {
3373            self.token_current();
3374        } else {
3375            self.empty_bogus_node(
3376                SyntaxKind::BogusSelector,
3377                ParseErrorCode::ExpectedSelectorName,
3378                "expected Less extend selector",
3379            );
3380        }
3381        if self.current_kind() == Some(SyntaxKind::LeftParen) {
3382            self.token_current();
3383            self.builder.start_node(SyntaxKind::PseudoSelectorArgument);
3384            while !self.at_end() {
3385                match self.current_kind() {
3386                    Some(SyntaxKind::RightParen) => break,
3387                    Some(kind) if is_selector_boundary(kind) => break,
3388                    Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3389                        kind,
3390                        &[
3391                            SyntaxKind::RightParen,
3392                            SyntaxKind::Comma,
3393                            SyntaxKind::LeftBrace,
3394                            SyntaxKind::SassIndent,
3395                            SyntaxKind::Semicolon,
3396                            SyntaxKind::SassOptionalSemicolon,
3397                        ],
3398                    ),
3399                    Some(_) => self.token_current(),
3400                    None => break,
3401                }
3402            }
3403            self.builder.finish_node();
3404            if self.current_kind() == Some(SyntaxKind::RightParen) {
3405                self.token_current();
3406            }
3407        }
3408        self.builder.finish_node();
3409    }
3410
3411    fn parse_combinator(&mut self) {
3412        let has_rhs = self
3413            .next_non_trivia_kind()
3414            .is_some_and(|kind| selector_component_can_start(kind) || is_interpolation_start(kind));
3415        self.builder.start_node(if has_rhs {
3416            SyntaxKind::Combinator
3417        } else {
3418            SyntaxKind::BogusCombinator
3419        });
3420        self.token_current();
3421        if !has_rhs {
3422            self.error_at_current(
3423                ParseErrorCode::UnexpectedCharacter,
3424                "expected selector after combinator",
3425            );
3426        }
3427        self.builder.finish_node();
3428    }
3429
3430    fn parse_whitespace_combinator(&mut self) {
3431        self.builder.start_node(SyntaxKind::Combinator);
3432        while self.current_kind() == Some(SyntaxKind::Whitespace) {
3433            self.token_current();
3434        }
3435        self.builder.finish_node();
3436    }
3437
3438    fn parse_declaration_list(&mut self) {
3439        while !self.at_end() {
3440            self.eat_trivia();
3441            match self.current_kind() {
3442                Some(SyntaxKind::RightBrace | SyntaxKind::SassDedent) | None => break,
3443                Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) => {
3444                    self.token_current()
3445                }
3446                Some(SyntaxKind::AtKeyword) if self.current_is_css_module_value_rule() => {
3447                    self.parse_css_module_value_rule()
3448                }
3449                Some(SyntaxKind::AtKeyword) if self.current_dialect_at_rule_spec().is_some() => {
3450                    self.parse_dialect_at_rule()
3451                }
3452                Some(SyntaxKind::AtKeyword) => self.parse_at_rule(),
3453                Some(_) if self.current_starts_less_namespace_access() => {
3454                    self.parse_less_namespace_access()
3455                }
3456                Some(_) if self.current_starts_less_mixin_call() => self.parse_less_mixin_call(),
3457                Some(_) if self.current_starts_scss_nested_property() => {
3458                    self.parse_scss_nested_property()
3459                }
3460                Some(_) if self.current_starts_nested_rule() => self.parse_rule(),
3461                Some(SyntaxKind::ScssVariable)
3462                    if matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass) =>
3463                {
3464                    self.parse_variable_declaration(SyntaxKind::ScssVariableDeclaration)
3465                }
3466                Some(SyntaxKind::LessVariable) if self.dialect == StyleDialect::Less => {
3467                    self.parse_variable_declaration(SyntaxKind::LessVariableDeclaration)
3468                }
3469                Some(SyntaxKind::LeftBrace) => {
3470                    self.builder.start_node(SyntaxKind::BogusDeclaration);
3471                    self.token_current();
3472                    self.builder.finish_node();
3473                }
3474                Some(_) => self.parse_declaration(),
3475            }
3476        }
3477    }
3478
3479    fn parse_scss_nested_property(&mut self) {
3480        self.builder.start_node(SyntaxKind::ScssNestedProperty);
3481        self.builder.start_node(SyntaxKind::PropertyName);
3482        while !self.at_end() {
3483            match self.current_kind() {
3484                Some(SyntaxKind::Colon) => break,
3485                Some(
3486                    SyntaxKind::Semicolon
3487                    | SyntaxKind::SassOptionalSemicolon
3488                    | SyntaxKind::RightBrace
3489                    | SyntaxKind::SassDedent,
3490                ) => break,
3491                Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3492                    kind,
3493                    &[
3494                        SyntaxKind::Colon,
3495                        SyntaxKind::Semicolon,
3496                        SyntaxKind::SassOptionalSemicolon,
3497                        SyntaxKind::RightBrace,
3498                        SyntaxKind::SassDedent,
3499                    ],
3500                ),
3501                Some(_) => self.token_current(),
3502                None => break,
3503            }
3504        }
3505        self.builder.finish_node();
3506
3507        if self.current_kind() == Some(SyntaxKind::Colon) {
3508            self.token_current();
3509        }
3510
3511        let block_recovery = [
3512            SyntaxKind::LeftBrace,
3513            SyntaxKind::SassIndent,
3514            SyntaxKind::Semicolon,
3515            SyntaxKind::SassOptionalSemicolon,
3516            SyntaxKind::RightBrace,
3517            SyntaxKind::SassDedent,
3518        ];
3519        if !matches!(
3520            self.current_kind(),
3521            Some(
3522                SyntaxKind::LeftBrace
3523                    | SyntaxKind::SassIndent
3524                    | SyntaxKind::Semicolon
3525                    | SyntaxKind::SassOptionalSemicolon
3526                    | SyntaxKind::RightBrace
3527                    | SyntaxKind::SassDedent
3528            )
3529        ) {
3530            self.builder.start_node(SyntaxKind::Value);
3531            self.parse_value_or_value_list_until(&block_recovery);
3532            self.builder.finish_node();
3533        }
3534
3535        match self.current_kind() {
3536            Some(SyntaxKind::LeftBrace) => self.parse_declaration_block(),
3537            Some(SyntaxKind::SassIndent) => self.parse_sass_indented_nested_property_block(),
3538            Some(_) => self.consume_until_recovery(&[
3539                SyntaxKind::Semicolon,
3540                SyntaxKind::SassOptionalSemicolon,
3541                SyntaxKind::RightBrace,
3542                SyntaxKind::SassDedent,
3543            ]),
3544            None => {}
3545        }
3546
3547        if self.current_kind().is_some_and(is_statement_end) {
3548            self.token_current();
3549        }
3550        self.builder.finish_node();
3551    }
3552
3553    fn parse_sass_indented_nested_property_block(&mut self) {
3554        self.builder.start_node(SyntaxKind::SassIndentedBlock);
3555        if self.current_kind() == Some(SyntaxKind::SassIndent) {
3556            self.token_current();
3557        }
3558        self.builder.start_node(SyntaxKind::DeclarationList);
3559        self.parse_declaration_list();
3560        self.builder.finish_node();
3561        if self.current_kind() == Some(SyntaxKind::SassDedent) {
3562            self.token_current();
3563        } else {
3564            self.error_at_current(
3565                ParseErrorCode::UnexpectedCharacter,
3566                "unterminated Sass indented nested property block",
3567            );
3568        }
3569        self.builder.finish_node();
3570    }
3571
3572    fn parse_variable_declaration(&mut self, kind: SyntaxKind) {
3573        let has_colon = self.find_before_recovery(
3574            SyntaxKind::Colon,
3575            &[
3576                SyntaxKind::Semicolon,
3577                SyntaxKind::SassOptionalSemicolon,
3578                SyntaxKind::RightBrace,
3579                SyntaxKind::SassDedent,
3580            ],
3581        );
3582        self.builder
3583            .start_node(variable_declaration_node_kind(kind, has_colon));
3584        self.token_current();
3585        if self.current_kind() == Some(SyntaxKind::Colon) {
3586            self.token_current();
3587            self.eat_value_trivia();
3588            let value_recovery = [
3589                SyntaxKind::Semicolon,
3590                SyntaxKind::SassOptionalSemicolon,
3591                SyntaxKind::RightBrace,
3592                SyntaxKind::SassDedent,
3593            ];
3594            if kind == SyntaxKind::LessVariableDeclaration
3595                && self.current_kind() == Some(SyntaxKind::LeftBrace)
3596            {
3597                self.parse_less_detached_ruleset();
3598            } else {
3599                let has_value = self
3600                    .non_trivia_token_from(self.position)
3601                    .is_some_and(|(_, kind)| !value_recovery.contains(&kind));
3602                self.builder.start_node(SyntaxKind::Value);
3603                if has_value {
3604                    self.parse_value_or_value_list_until(&value_recovery);
3605                } else {
3606                    self.empty_bogus_node(
3607                        SyntaxKind::BogusValue,
3608                        ParseErrorCode::ExpectedValue,
3609                        "expected variable value",
3610                    );
3611                }
3612                self.builder.finish_node();
3613            }
3614        } else {
3615            self.error_at_current(
3616                ParseErrorCode::UnexpectedCharacter,
3617                "expected variable declaration colon",
3618            );
3619            self.consume_until_recovery(&[
3620                SyntaxKind::Semicolon,
3621                SyntaxKind::SassOptionalSemicolon,
3622                SyntaxKind::RightBrace,
3623                SyntaxKind::SassDedent,
3624            ]);
3625        }
3626        if self.current_kind().is_some_and(is_statement_end) {
3627            self.token_current();
3628        }
3629        self.builder.finish_node();
3630    }
3631
3632    fn parse_less_detached_ruleset(&mut self) {
3633        let closed = self.current_left_brace_has_match();
3634        self.builder.start_node(if closed {
3635            SyntaxKind::LessDetachedRulesetNode
3636        } else {
3637            SyntaxKind::BogusLessDetachedRuleset
3638        });
3639        if self.current_kind() == Some(SyntaxKind::LeftBrace) {
3640            self.token_current();
3641            self.builder.start_node(SyntaxKind::DeclarationList);
3642            self.parse_declaration_list();
3643            self.builder.finish_node();
3644        }
3645        if self.current_kind() == Some(SyntaxKind::RightBrace) {
3646            self.token_current();
3647        } else {
3648            self.error_at_current(
3649                ParseErrorCode::UnexpectedCharacter,
3650                "unterminated Less detached ruleset",
3651            );
3652        }
3653        self.builder.finish_node();
3654    }
3655
3656    fn parse_declaration(&mut self) {
3657        let starts_composes = self.current_text() == Some("composes");
3658        let starts_custom_property = self.current_kind() == Some(SyntaxKind::CustomPropertyName);
3659        let has_colon = self.find_before_recovery(
3660            SyntaxKind::Colon,
3661            &[
3662                SyntaxKind::Semicolon,
3663                SyntaxKind::SassOptionalSemicolon,
3664                SyntaxKind::RightBrace,
3665                SyntaxKind::SassDedent,
3666                SyntaxKind::LeftBrace,
3667                SyntaxKind::SassIndent,
3668            ],
3669        );
3670        let kind = if starts_composes && has_colon {
3671            SyntaxKind::CssModuleComposesDeclaration
3672        } else if starts_composes {
3673            SyntaxKind::BogusComposesDeclaration
3674        } else if has_colon {
3675            SyntaxKind::Declaration
3676        } else {
3677            SyntaxKind::BogusDeclaration
3678        };
3679        self.builder.start_node(kind);
3680        if kind == SyntaxKind::CssModuleComposesDeclaration
3681            && self.current_css_module_scope_context() == Some("global")
3682        {
3683            self.error_at_current(
3684                ParseErrorCode::UnexpectedCharacter,
3685                "composes is not allowed inside :global scope",
3686            );
3687        }
3688        let property_kind = if matches!(
3689            self.current_kind(),
3690            Some(
3691                SyntaxKind::Colon
3692                    | SyntaxKind::Semicolon
3693                    | SyntaxKind::SassOptionalSemicolon
3694                    | SyntaxKind::LeftBrace
3695                    | SyntaxKind::SassIndent
3696                    | SyntaxKind::RightBrace
3697                    | SyntaxKind::SassDedent
3698            )
3699        ) {
3700            SyntaxKind::BogusPropertyName
3701        } else {
3702            SyntaxKind::PropertyName
3703        };
3704        self.builder.start_node(property_kind);
3705        while !self.at_end() {
3706            match self.current_kind() {
3707                Some(
3708                    SyntaxKind::Colon
3709                    | SyntaxKind::Semicolon
3710                    | SyntaxKind::SassOptionalSemicolon
3711                    | SyntaxKind::RightBrace
3712                    | SyntaxKind::SassDedent,
3713                ) => break,
3714                Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3715                    kind,
3716                    &[
3717                        SyntaxKind::Colon,
3718                        SyntaxKind::Semicolon,
3719                        SyntaxKind::SassOptionalSemicolon,
3720                        SyntaxKind::RightBrace,
3721                        SyntaxKind::SassDedent,
3722                    ],
3723                ),
3724                Some(_) => self.token_current(),
3725                None => break,
3726            }
3727        }
3728        self.builder.finish_node();
3729        if property_kind == SyntaxKind::BogusPropertyName {
3730            self.error_at_current(
3731                ParseErrorCode::UnexpectedCharacter,
3732                "expected declaration property name",
3733            );
3734        }
3735
3736        if self.current_kind() == Some(SyntaxKind::Colon) {
3737            self.token_current();
3738            let value_recovery = [
3739                SyntaxKind::Semicolon,
3740                SyntaxKind::SassOptionalSemicolon,
3741                SyntaxKind::RightBrace,
3742                SyntaxKind::SassDedent,
3743            ];
3744            let has_value = self
3745                .non_trivia_token_from(self.position)
3746                .is_some_and(|(_, kind)| !value_recovery.contains(&kind));
3747            self.builder.start_node(SyntaxKind::Value);
3748            if kind == SyntaxKind::CssModuleComposesDeclaration {
3749                self.parse_composes_value_until(&value_recovery);
3750            } else if starts_custom_property {
3751                self.builder.start_node(SyntaxKind::CustomPropertyValue);
3752                self.parse_component_value_list_until(&value_recovery);
3753                self.builder.finish_node();
3754            } else if !has_value {
3755                self.empty_bogus_node(
3756                    SyntaxKind::BogusValue,
3757                    ParseErrorCode::ExpectedValue,
3758                    "expected declaration value",
3759                );
3760            } else {
3761                self.parse_declaration_value_or_value_list_until(&value_recovery);
3762            }
3763            self.builder.finish_node();
3764        } else {
3765            self.consume_until_recovery(&[
3766                SyntaxKind::Semicolon,
3767                SyntaxKind::SassOptionalSemicolon,
3768                SyntaxKind::RightBrace,
3769                SyntaxKind::SassDedent,
3770            ]);
3771        }
3772
3773        if self.current_kind().is_some_and(is_statement_end) {
3774            self.token_current();
3775        }
3776        self.builder.finish_node();
3777    }
3778
3779    fn parse_composes_value_until(&mut self, recovery: &[SyntaxKind]) {
3780        let mut saw_target = false;
3781        if self.current_composes_value_has_multiple_from_clauses(recovery) {
3782            self.error_at_current(
3783                ParseErrorCode::UnexpectedCharacter,
3784                "multiple composes from clauses are not allowed",
3785            );
3786        }
3787        while !self.at_end() {
3788            self.eat_value_trivia();
3789            match self.current_kind() {
3790                Some(kind) if recovery.contains(&kind) => break,
3791                Some(SyntaxKind::Ident) if self.current_text() == Some("from") => {
3792                    if !saw_target {
3793                        self.empty_bogus_node(
3794                            SyntaxKind::BogusComposesTarget,
3795                            ParseErrorCode::UnexpectedCharacter,
3796                            "expected composes target before from clause",
3797                        );
3798                        saw_target = true;
3799                    }
3800                    self.parse_css_module_from_clause(recovery);
3801                }
3802                Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
3803                    self.builder.start_node(SyntaxKind::CssModuleComposesTarget);
3804                    self.token_current();
3805                    self.builder.finish_node();
3806                    saw_target = true;
3807                }
3808                Some(kind) if is_interpolation_start(kind) => {
3809                    self.parse_interpolation(kind, recovery)
3810                }
3811                Some(_) => self.token_current(),
3812                None => break,
3813            }
3814        }
3815        if !saw_target {
3816            self.empty_bogus_node(
3817                SyntaxKind::BogusComposesTarget,
3818                ParseErrorCode::UnexpectedCharacter,
3819                "expected composes target",
3820            );
3821        }
3822    }
3823
3824    fn current_composes_value_has_multiple_from_clauses(&self, recovery: &[SyntaxKind]) -> bool {
3825        let mut index = self.position;
3826        let mut paren_depth = 0usize;
3827        let mut bracket_depth = 0usize;
3828        let mut brace_depth = 0usize;
3829        let mut from_count = 0usize;
3830        while let Some(token) = self.tokens.get(index) {
3831            if paren_depth == 0
3832                && bracket_depth == 0
3833                && brace_depth == 0
3834                && recovery.contains(&token.kind)
3835            {
3836                break;
3837            }
3838            match token.kind {
3839                SyntaxKind::LeftParen => paren_depth += 1,
3840                SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
3841                SyntaxKind::LeftBracket => bracket_depth += 1,
3842                SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
3843                SyntaxKind::LeftBrace => brace_depth += 1,
3844                SyntaxKind::RightBrace => brace_depth = brace_depth.saturating_sub(1),
3845                SyntaxKind::Ident
3846                    if paren_depth == 0
3847                        && bracket_depth == 0
3848                        && brace_depth == 0
3849                        && token.text == "from" =>
3850                {
3851                    from_count += 1;
3852                    if from_count > 1 {
3853                        return true;
3854                    }
3855                }
3856                _ => {}
3857            }
3858            index += 1;
3859        }
3860        false
3861    }
3862
3863    fn parse_css_module_from_clause(&mut self, recovery: &[SyntaxKind]) {
3864        let source = self.non_trivia_token_from(self.position + 1);
3865        let has_source = source.is_some_and(|(_, kind)| !recovery.contains(&kind));
3866        let has_valid_source = source.is_some_and(|(index, kind)| {
3867            self.tokens
3868                .get(index)
3869                .is_some_and(|token| is_css_module_from_source_token(kind, token.text))
3870        });
3871        self.builder.start_node(if has_valid_source {
3872            SyntaxKind::CssModuleFromClause
3873        } else {
3874            SyntaxKind::BogusFromClause
3875        });
3876        self.token_current();
3877        while !self.at_end() {
3878            match self.current_kind() {
3879                Some(kind) if recovery.contains(&kind) => break,
3880                Some(_) => self.token_current(),
3881                None => break,
3882            }
3883        }
3884        if !has_source {
3885            self.error_at_current(
3886                ParseErrorCode::UnexpectedCharacter,
3887                "expected CSS Modules from-clause source",
3888            );
3889        } else if !has_valid_source {
3890            self.error_at_current(
3891                ParseErrorCode::ExpectedValue,
3892                "invalid CSS Modules from-clause source",
3893            );
3894        }
3895        self.builder.finish_node();
3896    }
3897
3898    fn current_css_module_scope_context(&self) -> Option<&'static str> {
3899        let mut open_blocks = Vec::new();
3900        for (index, token) in self.tokens.iter().take(self.position).enumerate() {
3901            match token.kind {
3902                SyntaxKind::LeftBrace | SyntaxKind::SassIndent => open_blocks.push(index),
3903                SyntaxKind::RightBrace | SyntaxKind::SassDedent => {
3904                    open_blocks.pop();
3905                }
3906                _ => {}
3907            }
3908        }
3909
3910        if let Some(scope) = open_blocks.iter().copied().find_map(|block_start| {
3911            let header_start = self.header_start_for_block(block_start);
3912            css_module_block_scope_marker_in_header(&self.tokens, header_start, block_start)
3913        }) {
3914            return Some(scope);
3915        }
3916
3917        let block_start = open_blocks.last().copied()?;
3918        let header_start = self.header_start_for_block(block_start);
3919        css_module_header_is_global_only(&self.tokens, header_start, block_start)
3920            .then_some("global")
3921    }
3922
3923    fn header_start_for_block(&self, block_start: usize) -> usize {
3924        let mut index = block_start;
3925        while index > 0 {
3926            let previous = index - 1;
3927            if matches!(
3928                self.tokens[previous].kind,
3929                SyntaxKind::LeftBrace
3930                    | SyntaxKind::RightBrace
3931                    | SyntaxKind::SassIndent
3932                    | SyntaxKind::SassDedent
3933                    | SyntaxKind::Semicolon
3934                    | SyntaxKind::SassOptionalSemicolon
3935            ) {
3936                break;
3937            }
3938            index = previous;
3939        }
3940        index
3941    }
3942
3943    fn parse_dialect_at_rule(&mut self) {
3944        let Some(spec) = self.current_dialect_at_rule_spec() else {
3945            self.parse_at_rule();
3946            return;
3947        };
3948
3949        self.builder
3950            .start_node(self.current_dialect_at_rule_node_kind(spec));
3951        if self.current_kind() == Some(SyntaxKind::AtKeyword) {
3952            self.token_current();
3953        }
3954        if matches!(
3955            spec.node_kind,
3956            SyntaxKind::ScssUseRule | SyntaxKind::ScssForwardRule
3957        ) {
3958            self.parse_scss_module_prelude(spec.node_kind);
3959        }
3960        if is_scss_control_rule_kind(spec.node_kind)
3961            && !self.current_scss_control_prelude_is_valid(spec.node_kind)
3962        {
3963            self.error_at_current(
3964                ParseErrorCode::ExpectedValue,
3965                "invalid SCSS control prelude",
3966            );
3967        }
3968        while !self.at_end() {
3969            match self.current_kind() {
3970                Some(kind) if is_statement_end(kind) => {
3971                    self.token_current();
3972                    break;
3973                }
3974                Some(SyntaxKind::LeftBrace) => {
3975                    match spec.block_kind {
3976                        AtRuleBlockKind::GroupRuleList => self.parse_group_at_rule_block(),
3977                        AtRuleBlockKind::DeclarationList => self.parse_declaration_block(),
3978                        AtRuleBlockKind::Keyframes => self.parse_keyframes_block(),
3979                        AtRuleBlockKind::Raw => self.consume_balanced_block(),
3980                    }
3981                    break;
3982                }
3983                Some(SyntaxKind::SassIndent) => {
3984                    self.parse_sass_indented_at_rule_block(spec.block_kind);
3985                    break;
3986                }
3987                Some(_) => self.token_current(),
3988                None => break,
3989            }
3990        }
3991        self.builder.finish_node();
3992    }
3993
3994    fn parse_scss_module_prelude(&mut self, node_kind: SyntaxKind) {
3995        self.validate_scss_module_prelude(node_kind);
3996        while !self.at_end() {
3997            match self.current_kind() {
3998                Some(kind)
3999                    if is_statement_end(kind)
4000                        || kind == SyntaxKind::LeftBrace
4001                        || kind == SyntaxKind::SassIndent =>
4002                {
4003                    break;
4004                }
4005                Some(SyntaxKind::Ident | SyntaxKind::KeywordWith)
4006                    if self.current_text() == Some("with")
4007                        && self
4008                            .non_trivia_token_from(self.position + 1)
4009                            .is_some_and(|(_, kind)| kind == SyntaxKind::LeftParen) =>
4010                {
4011                    self.parse_scss_module_config()
4012                }
4013                Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
4014                    kind,
4015                    &[
4016                        SyntaxKind::Semicolon,
4017                        SyntaxKind::SassOptionalSemicolon,
4018                        SyntaxKind::LeftBrace,
4019                        SyntaxKind::SassIndent,
4020                    ],
4021                ),
4022                Some(_) => self.token_current(),
4023                None => break,
4024            }
4025        }
4026    }
4027
4028    fn validate_scss_module_prelude(&mut self, node_kind: SyntaxKind) {
4029        let recovery = [
4030            SyntaxKind::Semicolon,
4031            SyntaxKind::SassOptionalSemicolon,
4032            SyntaxKind::LeftBrace,
4033            SyntaxKind::SassIndent,
4034        ];
4035        let Some((source_index, source_kind)) = self.non_trivia_token_from(self.position) else {
4036            self.error_at_current(ParseErrorCode::ExpectedValue, "expected SCSS module source");
4037            return;
4038        };
4039        if recovery.contains(&source_kind) || !is_scss_module_source_token(source_kind) {
4040            let range = self
4041                .tokens
4042                .get(source_index)
4043                .map(|token| token.range)
4044                .unwrap_or_else(|| self.current_range());
4045            self.errors.push(ParseError {
4046                code: ParseErrorCode::ExpectedValue,
4047                range,
4048                message: "expected SCSS module source",
4049            });
4050        }
4051
4052        let mut index = source_index;
4053        while let Some(token) = self.tokens.get(index).copied() {
4054            if recovery.contains(&token.kind) {
4055                break;
4056            }
4057            if token.kind == SyntaxKind::Ident {
4058                if token.text.eq_ignore_ascii_case("as") {
4059                    let next_kind = self.non_trivia_token_from(index + 1).map(|(_, kind)| kind);
4060                    if next_kind.is_none_or(|kind| {
4061                        recovery.contains(&kind) || !is_scss_module_namespace_token(kind)
4062                    }) {
4063                        self.errors.push(ParseError {
4064                            code: ParseErrorCode::ExpectedValue,
4065                            range: token.range,
4066                            message: "expected SCSS module namespace",
4067                        });
4068                    }
4069                } else if token.text.eq_ignore_ascii_case("with") {
4070                    let next_kind = self.non_trivia_token_from(index + 1).map(|(_, kind)| kind);
4071                    if next_kind != Some(SyntaxKind::LeftParen) {
4072                        self.errors.push(ParseError {
4073                            code: ParseErrorCode::ExpectedValue,
4074                            range: token.range,
4075                            message: "expected SCSS module configuration",
4076                        });
4077                    }
4078                } else if matches_ignore_ascii_case(token.text, &["show", "hide"]) {
4079                    if node_kind != SyntaxKind::ScssForwardRule {
4080                        self.errors.push(ParseError {
4081                            code: ParseErrorCode::UnexpectedCharacter,
4082                            range: token.range,
4083                            message: "unexpected SCSS module visibility clause",
4084                        });
4085                    }
4086                    let next_kind = self.non_trivia_token_from(index + 1).map(|(_, kind)| kind);
4087                    if next_kind.is_none_or(|kind| {
4088                        recovery.contains(&kind) || !is_scss_module_visibility_name_token(kind)
4089                    }) {
4090                        self.errors.push(ParseError {
4091                            code: ParseErrorCode::ExpectedValue,
4092                            range: token.range,
4093                            message: "expected SCSS module visibility name",
4094                        });
4095                    }
4096                }
4097            }
4098            index += 1;
4099        }
4100    }
4101
4102    fn current_scss_control_prelude_is_valid(&self, node_kind: SyntaxKind) -> bool {
4103        let recovery = [
4104            SyntaxKind::LeftBrace,
4105            SyntaxKind::SassIndent,
4106            SyntaxKind::Semicolon,
4107            SyntaxKind::SassOptionalSemicolon,
4108            SyntaxKind::RightBrace,
4109            SyntaxKind::SassDedent,
4110        ];
4111        match node_kind {
4112            SyntaxKind::ScssControlIf | SyntaxKind::ScssControlWhile => self
4113                .non_trivia_token_from(self.position)
4114                .is_some_and(|(_, kind)| !recovery.contains(&kind)),
4115            SyntaxKind::ScssControlFor => {
4116                self.non_trivia_token_from(self.position)
4117                    .is_some_and(|(_, kind)| kind == SyntaxKind::ScssVariable)
4118                    && self.find_text_before_recovery("from", &recovery)
4119                    && (self.find_text_before_recovery("to", &recovery)
4120                        || self.find_text_before_recovery("through", &recovery))
4121            }
4122            SyntaxKind::ScssControlEach => {
4123                self.non_trivia_token_from(self.position)
4124                    .is_some_and(|(_, kind)| kind == SyntaxKind::ScssVariable)
4125                    && self.find_text_before_recovery("in", &recovery)
4126            }
4127            SyntaxKind::ScssControlElse => true,
4128            _ => true,
4129        }
4130    }
4131
4132    fn parse_scss_module_config(&mut self) {
4133        let has_balanced_config = self.current_scss_module_config_has_balanced_parens();
4134        self.builder.start_node(if has_balanced_config {
4135            SyntaxKind::ScssModuleConfig
4136        } else {
4137            SyntaxKind::BogusScssModuleConfig
4138        });
4139        self.token_current();
4140        self.eat_trivia();
4141        if self.current_kind() == Some(SyntaxKind::LeftParen) {
4142            self.parse_balanced_parenthesized_prelude_until(
4143                None,
4144                &[
4145                    SyntaxKind::LeftBrace,
4146                    SyntaxKind::SassIndent,
4147                    SyntaxKind::Semicolon,
4148                    SyntaxKind::SassOptionalSemicolon,
4149                ],
4150            );
4151        }
4152        self.builder.finish_node();
4153    }
4154
4155    fn parse_css_module_value_rule(&mut self) {
4156        let has_name = self
4157            .non_trivia_token_from(self.position + 1)
4158            .and_then(|(index, kind)| {
4159                self.tokens
4160                    .get(index)
4161                    .map(|token| (kind, token.text != "from"))
4162            })
4163            .is_some_and(|(kind, allowed_name)| {
4164                allowed_name && matches!(kind, SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
4165            });
4166        let has_from = self.find_text_before_recovery(
4167            "from",
4168            &[
4169                SyntaxKind::Semicolon,
4170                SyntaxKind::SassOptionalSemicolon,
4171                SyntaxKind::LeftBrace,
4172                SyntaxKind::SassIndent,
4173            ],
4174        );
4175        let has_colon = self.find_before_recovery(
4176            SyntaxKind::Colon,
4177            &[
4178                SyntaxKind::Semicolon,
4179                SyntaxKind::SassOptionalSemicolon,
4180                SyntaxKind::LeftBrace,
4181                SyntaxKind::SassIndent,
4182            ],
4183        );
4184        let kind = if !has_name {
4185            SyntaxKind::BogusCssModuleBlock
4186        } else if has_from && !has_colon {
4187            SyntaxKind::CssModuleImportBlock
4188        } else {
4189            SyntaxKind::CssModuleExportBlock
4190        };
4191
4192        self.builder.start_node(kind);
4193        self.token_current();
4194        if !has_name {
4195            self.error_at_current(
4196                ParseErrorCode::UnexpectedCharacter,
4197                "expected CSS Modules @value name",
4198            );
4199        }
4200        if has_colon {
4201            self.parse_css_module_value_export();
4202        } else {
4203            self.parse_css_module_value_import_or_statement();
4204        }
4205        if self.current_kind().is_some_and(is_statement_end) {
4206            self.token_current();
4207        }
4208        self.builder.finish_node();
4209    }
4210
4211    fn parse_css_module_value_export(&mut self) {
4212        self.parse_css_module_token_definitions_until(&[
4213            SyntaxKind::Colon,
4214            SyntaxKind::Semicolon,
4215            SyntaxKind::SassOptionalSemicolon,
4216        ]);
4217        if self.current_kind() == Some(SyntaxKind::Colon) {
4218            self.token_current();
4219            self.builder.start_node(SyntaxKind::Value);
4220            self.parse_css_module_token_references_until(&[
4221                SyntaxKind::Semicolon,
4222                SyntaxKind::SassOptionalSemicolon,
4223            ]);
4224            self.builder.finish_node();
4225        }
4226    }
4227
4228    fn parse_css_module_value_import_or_statement(&mut self) {
4229        self.parse_css_module_token_definitions_until(&[
4230            SyntaxKind::Semicolon,
4231            SyntaxKind::SassOptionalSemicolon,
4232        ]);
4233    }
4234
4235    fn parse_css_module_token_definitions_until(&mut self, recovery: &[SyntaxKind]) {
4236        while !self.at_end() {
4237            match self.current_kind() {
4238                Some(kind) if recovery.contains(&kind) => break,
4239                Some(SyntaxKind::Ident) if self.current_text() == Some("from") => {
4240                    self.parse_css_module_from_clause(recovery);
4241                    break;
4242                }
4243                Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
4244                    self.builder.start_node(SyntaxKind::TokenDefinition);
4245                    self.token_current();
4246                    self.builder.finish_node();
4247                }
4248                Some(_) => self.token_current(),
4249                None => break,
4250            }
4251        }
4252    }
4253
4254    fn parse_css_module_token_references_until(&mut self, recovery: &[SyntaxKind]) {
4255        while !self.at_end() {
4256            self.eat_value_trivia();
4257            match self.current_kind() {
4258                Some(kind) if recovery.contains(&kind) => break,
4259                Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
4260                    self.builder.start_node(SyntaxKind::TokenReference);
4261                    self.token_current();
4262                    self.builder.finish_node();
4263                }
4264                Some(kind) if is_interpolation_start(kind) => {
4265                    self.parse_interpolation(kind, recovery)
4266                }
4267                Some(_) => self.token_current(),
4268                None => break,
4269            }
4270        }
4271    }
4272
4273    fn parse_less_mixin_header(&mut self) {
4274        self.builder.start_node(SyntaxKind::SelectorList);
4275        self.parse_until_recovery_with_optional_less_guard(&[SyntaxKind::LeftBrace]);
4276        self.builder.finish_node();
4277    }
4278
4279    fn parse_less_mixin_call(&mut self) {
4280        self.builder.start_node(SyntaxKind::LessMixinCall);
4281        self.parse_until_recovery_with_optional_less_guard(&[
4282            SyntaxKind::Semicolon,
4283            SyntaxKind::SassOptionalSemicolon,
4284            SyntaxKind::RightBrace,
4285            SyntaxKind::SassDedent,
4286        ]);
4287        if self.current_kind().is_some_and(is_statement_end) {
4288            self.token_current();
4289        }
4290        self.builder.finish_node();
4291    }
4292
4293    fn parse_less_namespace_access(&mut self) {
4294        self.builder.start_node(SyntaxKind::LessNamespaceAccess);
4295        while !self.at_end() {
4296            match self.current_kind() {
4297                Some(
4298                    SyntaxKind::Semicolon
4299                    | SyntaxKind::SassOptionalSemicolon
4300                    | SyntaxKind::RightBrace
4301                    | SyntaxKind::SassDedent
4302                    | SyntaxKind::LeftBrace
4303                    | SyntaxKind::SassIndent,
4304                ) => break,
4305                Some(_) if self.current_starts_less_mixin_call() => {
4306                    self.parse_less_mixin_call();
4307                    break;
4308                }
4309                Some(_) => self.token_current(),
4310                None => break,
4311            }
4312        }
4313        if self.current_kind().is_some_and(is_statement_end) {
4314            self.token_current();
4315        }
4316        self.builder.finish_node();
4317    }
4318
4319    fn parse_until_recovery_with_optional_less_guard(&mut self, recovery: &[SyntaxKind]) {
4320        let mut guard_open = false;
4321        while !self.at_end() {
4322            match self.current_kind() {
4323                Some(kind) if recovery.contains(&kind) => break,
4324                Some(SyntaxKind::Ident) if self.current_text() == Some("when") && !guard_open => {
4325                    self.builder.start_node(
4326                        if self.current_less_guard_has_condition_before(recovery) {
4327                            SyntaxKind::LessMixinGuard
4328                        } else {
4329                            SyntaxKind::BogusLessGuard
4330                        },
4331                    );
4332                    guard_open = true;
4333                    self.token_current();
4334                }
4335                Some(_) => self.token_current(),
4336                None => break,
4337            }
4338        }
4339        if guard_open {
4340            self.builder.finish_node();
4341        }
4342    }
4343
4344    fn parse_value_until(&mut self, recovery: &[SyntaxKind]) {
4345        while !self.at_end() {
4346            self.eat_value_trivia();
4347            if matches!(self.current_kind(), Some(kind) if recovery.contains(&kind)) {
4348                break;
4349            }
4350            if self.at_end() {
4351                break;
4352            }
4353            self.parse_value_expression(0, recovery);
4354        }
4355    }
4356
4357    fn parse_value_or_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4358        if self.current_value_has_top_level_comma_before(recovery) {
4359            self.parse_value_list_until(recovery);
4360        } else {
4361            self.parse_value_until(recovery);
4362        }
4363    }
4364
4365    fn parse_declaration_value_or_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4366        if self.current_value_has_top_level_comma_before(recovery) {
4367            self.parse_declaration_value_list_until(recovery);
4368        } else {
4369            self.parse_declaration_value_until(recovery);
4370        }
4371    }
4372
4373    fn parse_declaration_value_until(&mut self, recovery: &[SyntaxKind]) {
4374        let mut saw_value = false;
4375        while !self.at_end() {
4376            self.eat_value_trivia();
4377            if matches!(self.current_kind(), Some(kind) if recovery.contains(&kind)) {
4378                break;
4379            }
4380            if saw_value && self.current_starts_missing_semicolon_declaration(recovery) {
4381                self.error_at_current(
4382                    ParseErrorCode::UnexpectedCharacter,
4383                    "expected semicolon between declarations",
4384                );
4385                break;
4386            }
4387            if self.at_end() {
4388                break;
4389            }
4390            self.parse_value_expression(0, recovery);
4391            saw_value = true;
4392        }
4393    }
4394
4395    fn parse_declaration_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4396        self.builder
4397            .start_node(if self.current_value_list_is_bogus(recovery) {
4398                SyntaxKind::BogusValueList
4399            } else {
4400                SyntaxKind::ValueList
4401            });
4402        let item_recovery = value_list_item_recovery(recovery);
4403        let mut saw_item = false;
4404        while !self.at_end() {
4405            self.eat_value_trivia();
4406            match self.current_kind() {
4407                Some(kind) if recovery.contains(&kind) => break,
4408                Some(SyntaxKind::Comma) => self.token_current(),
4409                Some(_)
4410                    if saw_item && self.current_starts_missing_semicolon_declaration(recovery) =>
4411                {
4412                    self.error_at_current(
4413                        ParseErrorCode::UnexpectedCharacter,
4414                        "expected semicolon between declarations",
4415                    );
4416                    break;
4417                }
4418                Some(_) => {
4419                    self.parse_value_expression(0, &item_recovery);
4420                    saw_item = true;
4421                }
4422                None => break,
4423            }
4424        }
4425        self.builder.finish_node();
4426    }
4427
4428    fn parse_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4429        self.builder
4430            .start_node(if self.current_value_list_is_bogus(recovery) {
4431                SyntaxKind::BogusValueList
4432            } else {
4433                SyntaxKind::ValueList
4434            });
4435        let item_recovery = value_list_item_recovery(recovery);
4436        while !self.at_end() {
4437            self.eat_value_trivia();
4438            match self.current_kind() {
4439                Some(kind) if recovery.contains(&kind) => break,
4440                Some(SyntaxKind::Comma) => self.token_current(),
4441                Some(_) => self.parse_value_expression(0, &item_recovery),
4442                None => break,
4443            }
4444        }
4445        self.builder.finish_node();
4446    }
4447
4448    fn parse_component_value(&mut self, recovery: &[SyntaxKind]) {
4449        self.builder.start_node(SyntaxKind::ComponentValue);
4450        self.parse_component_value_inner(recovery);
4451        self.builder.finish_node();
4452    }
4453
4454    fn parse_component_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4455        self.builder.start_node(SyntaxKind::ComponentValueList);
4456        while !self.at_end() {
4457            self.eat_value_trivia();
4458            match self.current_kind() {
4459                Some(kind) if recovery.contains(&kind) => break,
4460                Some(_) => self.parse_component_value(recovery),
4461                None => break,
4462            }
4463        }
4464        self.builder.finish_node();
4465    }
4466
4467    fn parse_comma_separated_component_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4468        self.builder
4469            .start_node(SyntaxKind::CommaSeparatedComponentValueList);
4470        let item_recovery = comma_separated_component_value_list_item_recovery(recovery);
4471        while !self.at_end() {
4472            self.eat_value_trivia();
4473            match self.current_kind() {
4474                Some(kind) if recovery.contains(&kind) => break,
4475                Some(SyntaxKind::Comma) => self.token_current(),
4476                Some(_) => self.parse_component_value(&item_recovery),
4477                None => break,
4478            }
4479        }
4480        self.builder.finish_node();
4481    }
4482
4483    fn parse_component_value_inner(&mut self, recovery: &[SyntaxKind]) {
4484        self.eat_value_trivia();
4485        match self.current_kind() {
4486            Some(kind) if recovery.contains(&kind) => {
4487                self.empty_bogus_node(
4488                    SyntaxKind::BogusValue,
4489                    ParseErrorCode::ExpectedValue,
4490                    "expected component value",
4491                );
4492            }
4493            Some(SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen) => {
4494                self.parse_simple_block(recovery)
4495            }
4496            Some(SyntaxKind::Ident) if self.next_kind() == Some(SyntaxKind::LeftParen) => {
4497                self.parse_function_call(recovery)
4498            }
4499            Some(kind) if is_component_value_atom_start(kind) => self.parse_value_prefix(recovery),
4500            Some(_) => self.token_current(),
4501            None => {
4502                self.empty_bogus_node(
4503                    SyntaxKind::BogusValue,
4504                    ParseErrorCode::ExpectedValue,
4505                    "expected component value",
4506                );
4507            }
4508        }
4509    }
4510
4511    fn parse_simple_block_entry_point(&mut self, recovery: &[SyntaxKind]) {
4512        self.eat_value_trivia();
4513        match self.current_kind() {
4514            Some(SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen) => {
4515                self.parse_simple_block(recovery)
4516            }
4517            Some(_) | None => {
4518                self.empty_bogus_node(
4519                    SyntaxKind::BogusSimpleBlock,
4520                    ParseErrorCode::ExpectedValue,
4521                    "expected simple block",
4522                );
4523            }
4524        }
4525    }
4526
4527    fn parse_simple_block(&mut self, recovery: &[SyntaxKind]) {
4528        let Some(open_kind) = self.current_kind() else {
4529            self.empty_bogus_node(
4530                SyntaxKind::BogusSimpleBlock,
4531                ParseErrorCode::ExpectedValue,
4532                "expected simple block",
4533            );
4534            return;
4535        };
4536        let Some(close_kind) = matching_simple_block_close(open_kind) else {
4537            self.empty_bogus_node(
4538                SyntaxKind::BogusSimpleBlock,
4539                ParseErrorCode::ExpectedValue,
4540                "expected simple block",
4541            );
4542            return;
4543        };
4544
4545        let block_kind = if self.current_simple_block_has_matching_close(recovery) {
4546            SyntaxKind::SimpleBlock
4547        } else {
4548            SyntaxKind::BogusSimpleBlock
4549        };
4550        self.builder.start_node(block_kind);
4551        self.token_current();
4552
4553        let block_recovery = simple_block_recovery(close_kind, recovery);
4554        while !self.at_end() {
4555            self.eat_value_trivia();
4556            match self.current_kind() {
4557                Some(kind) if kind == close_kind => break,
4558                Some(kind) if recovery.contains(&kind) => break,
4559                Some(_) => self.parse_component_value(&block_recovery),
4560                None => break,
4561            }
4562        }
4563
4564        if self.current_kind() == Some(close_kind) {
4565            self.token_current();
4566        } else {
4567            self.error_at_current(
4568                ParseErrorCode::UnexpectedCharacter,
4569                "unterminated simple block",
4570            );
4571        }
4572        self.builder.finish_node();
4573    }
4574
4575    fn parse_value_expression(&mut self, min_binding_power: u8, recovery: &[SyntaxKind]) {
4576        self.eat_value_trivia();
4577        let checkpoint = self.builder.checkpoint();
4578        self.parse_value_prefix(recovery);
4579
4580        loop {
4581            self.eat_value_trivia();
4582            let Some(operator) = self.current_kind() else {
4583                break;
4584            };
4585            if recovery.contains(&operator) {
4586                break;
4587            }
4588            let Some((left_binding_power, right_binding_power)) = infix_binding_power(operator)
4589            else {
4590                break;
4591            };
4592            if left_binding_power < min_binding_power {
4593                break;
4594            }
4595
4596            self.builder
4597                .start_node_at(checkpoint, SyntaxKind::BinaryExpression);
4598            self.token_current();
4599            self.parse_value_expression(right_binding_power, recovery);
4600            self.builder.finish_node();
4601        }
4602    }
4603
4604    fn parse_value_prefix(&mut self, recovery: &[SyntaxKind]) {
4605        match self.current_kind() {
4606            Some(SyntaxKind::Plus | SyntaxKind::Minus) => {
4607                self.builder.start_node(SyntaxKind::UnaryExpression);
4608                self.token_current();
4609                self.parse_value_expression(5, recovery);
4610                self.builder.finish_node();
4611            }
4612            Some(SyntaxKind::Ident)
4613                if self
4614                    .current_text()
4615                    .is_some_and(|text| text.eq_ignore_ascii_case("url"))
4616                    && self.next_kind() == Some(SyntaxKind::LeftParen) =>
4617            {
4618                self.builder.start_node(SyntaxKind::UrlValue);
4619                self.parse_function_call(recovery);
4620                self.builder.finish_node();
4621            }
4622            Some(SyntaxKind::Ident) if self.next_kind() == Some(SyntaxKind::LeftParen) => {
4623                self.parse_function_call(recovery)
4624            }
4625            Some(SyntaxKind::Number) => {
4626                self.builder.start_node(SyntaxKind::NumberValue);
4627                self.token_current();
4628                self.builder.finish_node();
4629            }
4630            Some(SyntaxKind::Percentage) => {
4631                self.builder.start_node(SyntaxKind::PercentageValue);
4632                self.token_current();
4633                self.builder.finish_node();
4634            }
4635            Some(SyntaxKind::Dimension) => {
4636                self.builder.start_node(SyntaxKind::DimensionValue);
4637                self.token_current();
4638                self.builder.finish_node();
4639            }
4640            Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
4641                self.builder.start_node(SyntaxKind::IdentifierValue);
4642                self.token_current();
4643                self.builder.finish_node();
4644            }
4645            Some(SyntaxKind::String | SyntaxKind::LessEscapedString) => {
4646                self.builder.start_node(SyntaxKind::StringValue);
4647                self.token_current();
4648                self.builder.finish_node();
4649            }
4650            Some(SyntaxKind::UnicodeRange) => {
4651                self.builder.start_node(SyntaxKind::UnicodeRangeValue);
4652                self.token_current();
4653                self.builder.finish_node();
4654            }
4655            Some(SyntaxKind::Hash) => {
4656                self.builder.start_node(SyntaxKind::ColorValue);
4657                self.token_current();
4658                self.builder.finish_node();
4659            }
4660            Some(SyntaxKind::Url) => {
4661                self.builder.start_node(SyntaxKind::UrlValue);
4662                self.token_current();
4663                self.builder.finish_node();
4664            }
4665            Some(SyntaxKind::BadUrl) => {
4666                self.builder.start_node(SyntaxKind::BogusValue);
4667                self.token_current();
4668                self.builder.finish_node();
4669            }
4670            Some(SyntaxKind::BadString) => {
4671                self.builder.start_node(SyntaxKind::BogusValue);
4672                self.token_current();
4673                self.builder.finish_node();
4674            }
4675            Some(SyntaxKind::Important) => {
4676                self.builder.start_node(SyntaxKind::ImportantAnnotation);
4677                self.token_current();
4678                self.builder.finish_node();
4679            }
4680            Some(SyntaxKind::Delim) if self.current_split_important_annotation() => {
4681                self.parse_split_important_annotation()
4682            }
4683            Some(SyntaxKind::Delim) if self.current_scss_variable_flag_annotation() => {
4684                self.parse_scss_variable_flag_annotation()
4685            }
4686            Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(kind, recovery),
4687            Some(SyntaxKind::ScssVariable) => {
4688                self.builder.start_node(SyntaxKind::ScssVariableReference);
4689                self.token_current();
4690                self.builder.finish_node();
4691            }
4692            Some(SyntaxKind::LessVariable) => {
4693                self.builder.start_node(SyntaxKind::LessVariableReference);
4694                self.token_current();
4695                self.builder.finish_node();
4696            }
4697            Some(SyntaxKind::LessPropertyVariableToken) => {
4698                self.builder.start_node(SyntaxKind::LessPropertyVariable);
4699                self.token_current();
4700                self.builder.finish_node();
4701            }
4702            Some(SyntaxKind::LeftBrace) => self.parse_simple_block(recovery),
4703            Some(SyntaxKind::LeftParen) => self.parse_parenthesized_expression(recovery),
4704            Some(SyntaxKind::LeftBracket) => self.parse_bracketed_value(recovery),
4705            Some(kind) if recovery.contains(&kind) => {
4706                self.empty_bogus_node(
4707                    SyntaxKind::BogusValue,
4708                    ParseErrorCode::ExpectedValue,
4709                    "expected value",
4710                );
4711            }
4712            Some(SyntaxKind::Delim) => {
4713                self.builder.start_node(SyntaxKind::BogusToken);
4714                self.token_current();
4715                self.builder.finish_node();
4716            }
4717            Some(_) => {
4718                self.builder.start_node(SyntaxKind::BogusValue);
4719                self.error_at_current(ParseErrorCode::ExpectedValue, "expected value");
4720                self.token_current();
4721                self.builder.finish_node();
4722            }
4723            None => {
4724                self.empty_bogus_node(
4725                    SyntaxKind::BogusValue,
4726                    ParseErrorCode::ExpectedValue,
4727                    "expected value",
4728                );
4729            }
4730        }
4731    }
4732
4733    fn parse_split_important_annotation(&mut self) {
4734        self.builder.start_node(SyntaxKind::ImportantAnnotation);
4735        self.token_current();
4736        self.eat_value_trivia();
4737        if self
4738            .current_text()
4739            .is_some_and(|text| text.eq_ignore_ascii_case("important"))
4740        {
4741            self.token_current();
4742        }
4743        self.builder.finish_node();
4744    }
4745
4746    fn parse_scss_variable_flag_annotation(&mut self) {
4747        self.builder.start_node(SyntaxKind::ScssVariableFlag);
4748        self.token_current();
4749        self.eat_value_trivia();
4750        self.token_current();
4751        self.builder.finish_node();
4752    }
4753
4754    fn eat_value_trivia(&mut self) {
4755        while matches!(self.current_kind(), Some(kind) if kind.is_trivia()) {
4756            self.token_current();
4757        }
4758    }
4759
4760    fn parse_function_call(&mut self, recovery: &[SyntaxKind]) {
4761        let function_name = self.current_text().map(str::to_owned);
4762        let function_range = self.current_range();
4763        let argument_count = self.current_function_top_level_argument_count_before(recovery);
4764        let has_empty_argument_slot =
4765            self.current_function_has_empty_top_level_argument_slot_before(recovery);
4766        let argument_head = self.current_function_first_argument_token_before(recovery);
4767        let specialized_kind = function_name.as_deref().and_then(specialized_function_kind);
4768        let closed = self.current_function_has_closing_paren_before(recovery);
4769        let function_kind = if closed {
4770            SyntaxKind::FunctionCall
4771        } else {
4772            SyntaxKind::BogusFunctionCall
4773        };
4774        let arguments_kind = if closed {
4775            SyntaxKind::FunctionArguments
4776        } else {
4777            SyntaxKind::BogusFunctionArguments
4778        };
4779
4780        self.builder.start_node(function_kind);
4781        if let Some(kind) = specialized_kind {
4782            self.builder.start_node(kind);
4783        }
4784        self.token_current();
4785        if self.current_kind() == Some(SyntaxKind::LeftParen) {
4786            self.token_current();
4787            self.builder.start_node(arguments_kind);
4788            let argument_recovery = function_argument_recovery(recovery);
4789            self.parse_value_or_value_list_until(&argument_recovery);
4790            self.builder.finish_node();
4791            if self.current_kind() == Some(SyntaxKind::RightParen) {
4792                self.token_current();
4793            } else {
4794                self.error_at_current(
4795                    ParseErrorCode::UnexpectedCharacter,
4796                    "unterminated function call",
4797                );
4798            }
4799        }
4800        if let Some(function_name) = function_name {
4801            if let Some(argument_count) = argument_count {
4802                self.validate_function_argument_count(
4803                    &function_name,
4804                    argument_count,
4805                    function_range,
4806                );
4807            }
4808            if let Some(true) = has_empty_argument_slot {
4809                self.validate_function_argument_slots(&function_name, function_range);
4810            }
4811            self.validate_function_argument_head(&function_name, argument_head, function_range);
4812        }
4813        if specialized_kind.is_some() {
4814            self.builder.finish_node();
4815        }
4816        self.builder.finish_node();
4817    }
4818
4819    fn current_function_top_level_argument_count_before(
4820        &self,
4821        recovery: &[SyntaxKind],
4822    ) -> Option<usize> {
4823        if self.next_kind() != Some(SyntaxKind::LeftParen) {
4824            return None;
4825        }
4826
4827        let mut index = self.position + 2;
4828        let mut depth = 0usize;
4829        let mut comma_count = 0usize;
4830        let mut saw_argument = false;
4831        while let Some(token) = self.tokens.get(index) {
4832            match token.kind {
4833                kind if depth == 0 && recovery.contains(&kind) => return None,
4834                SyntaxKind::RightParen if depth == 0 => {
4835                    return Some(if saw_argument { comma_count + 1 } else { 0 });
4836                }
4837                SyntaxKind::Comma if depth == 0 => {
4838                    comma_count += 1;
4839                    saw_argument = false;
4840                }
4841                kind if kind.is_trivia() => {}
4842                SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen => {
4843                    depth += 1;
4844                    saw_argument = true;
4845                }
4846                SyntaxKind::RightBrace | SyntaxKind::RightBracket | SyntaxKind::RightParen => {
4847                    depth = depth.saturating_sub(1);
4848                    saw_argument = true;
4849                }
4850                _ => saw_argument = true,
4851            }
4852            index += 1;
4853        }
4854        None
4855    }
4856
4857    fn current_function_has_empty_top_level_argument_slot_before(
4858        &self,
4859        recovery: &[SyntaxKind],
4860    ) -> Option<bool> {
4861        if self.next_kind() != Some(SyntaxKind::LeftParen) {
4862            return None;
4863        }
4864
4865        let mut index = self.position + 2;
4866        let mut depth = 0usize;
4867        let mut expecting_argument = true;
4868        let mut saw_argument = false;
4869        while let Some(token) = self.tokens.get(index) {
4870            match token.kind {
4871                kind if depth == 0 && recovery.contains(&kind) => return None,
4872                SyntaxKind::RightParen if depth == 0 => {
4873                    return Some(expecting_argument && saw_argument);
4874                }
4875                SyntaxKind::Comma if depth == 0 => {
4876                    if expecting_argument {
4877                        return Some(true);
4878                    }
4879                    expecting_argument = true;
4880                }
4881                kind if kind.is_trivia() => {}
4882                SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen => {
4883                    depth += 1;
4884                    expecting_argument = false;
4885                    saw_argument = true;
4886                }
4887                SyntaxKind::RightBrace | SyntaxKind::RightBracket | SyntaxKind::RightParen => {
4888                    depth = depth.saturating_sub(1);
4889                    expecting_argument = false;
4890                    saw_argument = true;
4891                }
4892                _ => {
4893                    expecting_argument = false;
4894                    saw_argument = true;
4895                }
4896            }
4897            index += 1;
4898        }
4899        None
4900    }
4901
4902    fn current_function_first_argument_token_before(
4903        &self,
4904        recovery: &[SyntaxKind],
4905    ) -> Option<Token<'text>> {
4906        if self.next_kind() != Some(SyntaxKind::LeftParen) {
4907            return None;
4908        }
4909
4910        let mut index = self.position + 2;
4911        while let Some(token) = self.tokens.get(index).copied() {
4912            match token.kind {
4913                kind if recovery.contains(&kind) => return None,
4914                SyntaxKind::RightParen => return None,
4915                kind if kind.is_trivia() => {}
4916                _ => return Some(token),
4917            }
4918            index += 1;
4919        }
4920        None
4921    }
4922
4923    fn validate_function_argument_count(
4924        &mut self,
4925        function_name: &str,
4926        argument_count: usize,
4927        range: TextRange,
4928    ) {
4929        if function_argument_count_is_valid(function_name, argument_count) {
4930            return;
4931        }
4932        self.errors.push(ParseError {
4933            code: ParseErrorCode::ExpectedValue,
4934            range,
4935            message: "invalid function argument count",
4936        });
4937    }
4938
4939    fn validate_function_argument_slots(&mut self, function_name: &str, range: TextRange) {
4940        if !function_requires_filled_top_level_arguments(function_name) {
4941            return;
4942        }
4943        self.errors.push(ParseError {
4944            code: ParseErrorCode::ExpectedValue,
4945            range,
4946            message: "empty function argument",
4947        });
4948    }
4949
4950    fn validate_function_argument_head(
4951        &mut self,
4952        function_name: &str,
4953        argument_head: Option<Token<'text>>,
4954        range: TextRange,
4955    ) {
4956        let head_kind = argument_head.map(|token| token.kind);
4957        let valid = if function_name.eq_ignore_ascii_case("var") {
4958            matches!(head_kind, Some(SyntaxKind::CustomPropertyName))
4959                || head_kind.is_some_and(is_dynamic_function_argument_head)
4960        } else if function_name.eq_ignore_ascii_case("env") {
4961            matches!(
4962                head_kind,
4963                Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
4964            ) || head_kind.is_some_and(is_dynamic_function_argument_head)
4965        } else if function_name.eq_ignore_ascii_case("attr") {
4966            matches!(head_kind, Some(SyntaxKind::Ident))
4967                || head_kind.is_some_and(is_dynamic_function_argument_head)
4968        } else if function_name.eq_ignore_ascii_case("color-mix") {
4969            argument_head.is_some_and(|token| token.text.eq_ignore_ascii_case("in"))
4970                || head_kind.is_some_and(is_dynamic_function_argument_head)
4971        } else {
4972            true
4973        };
4974
4975        if valid {
4976            return;
4977        }
4978        self.errors.push(ParseError {
4979            code: ParseErrorCode::ExpectedValue,
4980            range,
4981            message: "invalid function argument head",
4982        });
4983    }
4984
4985    fn parse_bracketed_value(&mut self, recovery: &[SyntaxKind]) {
4986        let closed = self.current_bracketed_value_has_closing_bracket_before(recovery);
4987        self.builder.start_node(if closed {
4988            SyntaxKind::BracketedValue
4989        } else {
4990            SyntaxKind::BogusBracketedValue
4991        });
4992        self.token_current();
4993        let bracket_recovery = bracketed_value_recovery(recovery);
4994        self.parse_value_until(&bracket_recovery);
4995        if self.current_kind() == Some(SyntaxKind::RightBracket) {
4996            self.token_current();
4997        } else {
4998            self.error_at_current(
4999                ParseErrorCode::UnexpectedCharacter,
5000                "unterminated bracketed value",
5001            );
5002        }
5003        self.builder.finish_node();
5004    }
5005
5006    fn parse_parenthesized_expression(&mut self, recovery: &[SyntaxKind]) {
5007        self.builder.start_node(SyntaxKind::ParenthesizedExpression);
5008        self.token_current();
5009        let paren_recovery = function_argument_recovery(recovery);
5010        self.parse_value_until(&paren_recovery);
5011        if self.current_kind() == Some(SyntaxKind::RightParen) {
5012            self.token_current();
5013        }
5014        self.builder.finish_node();
5015    }
5016
5017    fn parse_at_rule(&mut self) {
5018        let spec = self.current_text().and_then(at_rule_spec);
5019        let at_rule_kind = if spec.is_none() && self.current_text() == Some("@") {
5020            SyntaxKind::BogusAtRule
5021        } else {
5022            SyntaxKind::AtRule
5023        };
5024        self.builder.start_node(at_rule_kind);
5025        if at_rule_kind == SyntaxKind::BogusAtRule {
5026            self.error_at_current(ParseErrorCode::UnexpectedCharacter, "expected at-rule name");
5027        }
5028        if let Some(spec) = spec {
5029            self.builder.start_node(spec.node_kind);
5030        }
5031
5032        if self.current_kind() == Some(SyntaxKind::AtKeyword) {
5033            self.token_current();
5034        }
5035        if let Some(spec) = spec {
5036            self.parse_at_rule_prelude(spec.node_kind);
5037        } else {
5038            self.consume_at_rule_prelude_tokens();
5039        }
5040
5041        while !self.at_end() {
5042            match self.current_kind() {
5043                Some(kind) if is_statement_end(kind) => {
5044                    self.token_current();
5045                    break;
5046                }
5047                Some(SyntaxKind::LeftBrace) => {
5048                    match spec
5049                        .map(|spec| spec.block_kind)
5050                        .unwrap_or(AtRuleBlockKind::Raw)
5051                    {
5052                        AtRuleBlockKind::GroupRuleList => self.parse_group_at_rule_block(),
5053                        AtRuleBlockKind::DeclarationList => self.parse_declaration_block(),
5054                        AtRuleBlockKind::Keyframes => self.parse_keyframes_block(),
5055                        AtRuleBlockKind::Raw => self.consume_balanced_block(),
5056                    }
5057                    break;
5058                }
5059                Some(SyntaxKind::SassIndent) => {
5060                    self.parse_sass_indented_at_rule_block(
5061                        spec.map(|spec| spec.block_kind)
5062                            .unwrap_or(AtRuleBlockKind::Raw),
5063                    );
5064                    break;
5065                }
5066                Some(_) => self.token_current(),
5067                None => break,
5068            }
5069        }
5070
5071        if spec.is_some() {
5072            self.builder.finish_node();
5073        }
5074        self.builder.finish_node();
5075    }
5076
5077    fn parse_at_rule_prelude(&mut self, node_kind: SyntaxKind) {
5078        match node_kind {
5079            SyntaxKind::MediaRule => self.parse_media_query_list(),
5080            SyntaxKind::SupportsRule => self.parse_supports_rule_prelude(),
5081            SyntaxKind::ContainerRule => self.parse_container_rule_prelude(),
5082            SyntaxKind::ImportRule => self.parse_import_prelude(),
5083            SyntaxKind::CharsetRule => self.parse_charset_rule_prelude(),
5084            SyntaxKind::NamespaceRule => self.parse_namespace_rule_prelude(),
5085            SyntaxKind::KeyframesRule => self.parse_keyframes_rule_prelude(),
5086            SyntaxKind::PageRule => self.parse_page_rule_prelude(),
5087            SyntaxKind::FontFaceRule
5088            | SyntaxKind::StartingStyleRule
5089            | SyntaxKind::PageMarginRule
5090            | SyntaxKind::FontFeatureValuesStylisticRule
5091            | SyntaxKind::FontFeatureValuesStylesetRule
5092            | SyntaxKind::FontFeatureValuesCharacterVariantRule
5093            | SyntaxKind::FontFeatureValuesSwashRule
5094            | SyntaxKind::FontFeatureValuesOrnamentsRule
5095            | SyntaxKind::FontFeatureValuesAnnotationRule
5096            | SyntaxKind::FontFeatureValuesHistoricalFormsRule
5097            | SyntaxKind::ViewTransitionRule => {
5098                self.parse_empty_at_rule_prelude("unexpected at-rule prelude")
5099            }
5100            SyntaxKind::PropertyRule => self.parse_named_at_rule_prelude(
5101                at_rule_prelude_head_is_custom_property_name,
5102                "invalid @property name",
5103            ),
5104            SyntaxKind::FontPaletteValuesRule
5105            | SyntaxKind::ColorProfileRule
5106            | SyntaxKind::PositionTryRule => self.parse_named_at_rule_prelude(
5107                at_rule_prelude_head_is_custom_property_name,
5108                "invalid at-rule custom property name",
5109            ),
5110            SyntaxKind::CustomMediaRule => self.parse_custom_media_rule_prelude(),
5111            SyntaxKind::CounterStyleRule => self.parse_named_at_rule_prelude(
5112                at_rule_prelude_head_is_custom_ident,
5113                "invalid @counter-style name",
5114            ),
5115            SyntaxKind::FontFeatureValuesRule => self.parse_font_feature_values_prelude(),
5116            SyntaxKind::LayerRule => self.parse_layer_rule_prelude(),
5117            SyntaxKind::ScopeRule => self.parse_scope_rule_prelude(),
5118            _ => self.consume_at_rule_prelude_tokens(),
5119        }
5120    }
5121
5122    fn parse_media_query_list(&mut self) {
5123        self.builder.start_node(SyntaxKind::MediaQueryList);
5124        let mut saw_query = false;
5125        let mut expecting_query = true;
5126        while !self.at_end() {
5127            match self.current_kind() {
5128                Some(kind) if is_at_rule_prelude_boundary(kind) => break,
5129                Some(SyntaxKind::Comma) => {
5130                    if expecting_query {
5131                        self.error_at_current(
5132                            ParseErrorCode::ExpectedValue,
5133                            "invalid @media prelude",
5134                        );
5135                        self.builder.start_node(SyntaxKind::BogusMediaQuery);
5136                        self.token_current();
5137                        self.builder.finish_node();
5138                    } else {
5139                        self.token_current();
5140                        expecting_query = true;
5141                    }
5142                }
5143                Some(_) => {
5144                    let valid = self.current_media_query_is_valid();
5145                    if !valid {
5146                        self.error_at_current(
5147                            ParseErrorCode::ExpectedValue,
5148                            "invalid @media prelude",
5149                        );
5150                    }
5151                    self.parse_media_query(valid);
5152                    saw_query = true;
5153                    expecting_query = false;
5154                }
5155                None => break,
5156            }
5157        }
5158        if !saw_query || expecting_query {
5159            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @media prelude");
5160            self.builder.start_node(SyntaxKind::BogusMediaQuery);
5161            self.builder.finish_node();
5162        }
5163        self.builder.finish_node();
5164    }
5165
5166    fn parse_media_query(&mut self, valid: bool) {
5167        self.builder.start_node(if valid {
5168            SyntaxKind::MediaQuery
5169        } else {
5170            SyntaxKind::BogusMediaQuery
5171        });
5172        while !self.at_end() {
5173            match self.current_kind() {
5174                Some(kind) if is_at_rule_prelude_boundary(kind) || kind == SyntaxKind::Comma => {
5175                    break;
5176                }
5177                Some(SyntaxKind::LeftParen) => self.parse_balanced_parenthesized_prelude_until(
5178                    Some(SyntaxKind::MediaFeature),
5179                    &[
5180                        SyntaxKind::Comma,
5181                        SyntaxKind::LeftBrace,
5182                        SyntaxKind::Semicolon,
5183                    ],
5184                ),
5185                Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
5186                    kind,
5187                    &[
5188                        SyntaxKind::Comma,
5189                        SyntaxKind::LeftBrace,
5190                        SyntaxKind::Semicolon,
5191                    ],
5192                ),
5193                Some(_) => self.token_current(),
5194                None => break,
5195            }
5196        }
5197        self.builder.finish_node();
5198    }
5199
5200    fn current_media_query_is_valid(&self) -> bool {
5201        let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5202            return false;
5203        };
5204        if is_at_rule_prelude_boundary(first_kind) || first_kind == SyntaxKind::Comma {
5205            return false;
5206        }
5207        if !self.current_prelude_parentheses_are_balanced_until(&[
5208            SyntaxKind::Comma,
5209            SyntaxKind::LeftBrace,
5210            SyntaxKind::SassIndent,
5211            SyntaxKind::Semicolon,
5212            SyntaxKind::SassOptionalSemicolon,
5213        ]) {
5214            return false;
5215        }
5216        self.media_query_starts_at(first_index, first_kind)
5217    }
5218
5219    fn media_query_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5220        match kind {
5221            SyntaxKind::Ident | SyntaxKind::LeftParen => true,
5222            SyntaxKind::KeywordNot | SyntaxKind::KeywordOnly => self
5223                .non_trivia_token_from(index + 1)
5224                .is_some_and(|(_, next_kind)| {
5225                    matches!(next_kind, SyntaxKind::Ident | SyntaxKind::LeftParen)
5226                        || is_interpolation_start(next_kind)
5227                }),
5228            kind if is_interpolation_start(kind) => true,
5229            _ => false,
5230        }
5231    }
5232
5233    fn parse_charset_rule_prelude(&mut self) {
5234        if !self.charset_rule_prelude_is_valid() {
5235            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @charset prelude");
5236        }
5237        self.consume_at_rule_prelude_tokens();
5238    }
5239
5240    fn charset_rule_prelude_is_valid(&self) -> bool {
5241        let Some((source_index, SyntaxKind::String)) = self.non_trivia_token_from(self.position)
5242        else {
5243            return false;
5244        };
5245        self.non_trivia_token_from(source_index + 1)
5246            .is_none_or(|(_, kind)| is_at_rule_prelude_boundary(kind))
5247    }
5248
5249    fn parse_namespace_rule_prelude(&mut self) {
5250        if !self.namespace_rule_prelude_is_valid() {
5251            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @namespace prelude");
5252        }
5253        self.consume_at_rule_prelude_tokens();
5254    }
5255
5256    fn parse_custom_media_rule_prelude(&mut self) {
5257        self.eat_trivia();
5258        let valid = self.custom_media_rule_prelude_is_valid();
5259        if !valid {
5260            self.error_at_current(
5261                ParseErrorCode::ExpectedValue,
5262                "invalid @custom-media prelude",
5263            );
5264        }
5265        self.builder.start_node(if valid {
5266            SyntaxKind::AtRulePrelude
5267        } else {
5268            SyntaxKind::BogusAtRulePrelude
5269        });
5270        self.consume_at_rule_prelude_tokens_without_wrapping();
5271        self.builder.finish_node();
5272    }
5273
5274    fn custom_media_rule_prelude_is_valid(&self) -> bool {
5275        let Some((name_index, name_kind)) = self.non_trivia_token_from(self.position) else {
5276            return false;
5277        };
5278        if !self.current_prelude_parentheses_are_balanced_until(&[
5279            SyntaxKind::Semicolon,
5280            SyntaxKind::SassOptionalSemicolon,
5281        ]) {
5282            return false;
5283        }
5284        let tail = if name_kind == SyntaxKind::CustomPropertyName {
5285            self.non_trivia_token_from(name_index + 1)
5286        } else if is_interpolation_start(name_kind) {
5287            self.non_trivia_token_after_interpolation(name_index, name_kind)
5288        } else {
5289            return false;
5290        };
5291        let Some((tail_index, tail_kind)) = tail else {
5292            return false;
5293        };
5294        if is_at_rule_prelude_boundary(tail_kind) {
5295            return false;
5296        }
5297        self.media_query_starts_at(tail_index, tail_kind)
5298    }
5299
5300    fn namespace_rule_prelude_is_valid(&self) -> bool {
5301        let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5302            return false;
5303        };
5304
5305        if self.namespace_source_starts_at(first_index, first_kind) {
5306            return true;
5307        }
5308        if !matches!(
5309            first_kind,
5310            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
5311        ) {
5312            return false;
5313        }
5314        self.non_trivia_token_from(first_index + 1)
5315            .is_some_and(|(source_index, source_kind)| {
5316                self.namespace_source_starts_at(source_index, source_kind)
5317            })
5318    }
5319
5320    fn namespace_source_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5321        matches!(kind, SyntaxKind::String | SyntaxKind::Url)
5322            || is_interpolation_start(kind)
5323            || self.token_starts_url_function(index, kind)
5324    }
5325
5326    fn token_starts_url_function(&self, index: usize, kind: SyntaxKind) -> bool {
5327        kind == SyntaxKind::Ident
5328            && self
5329                .tokens
5330                .get(index)
5331                .is_some_and(|token| token.text.eq_ignore_ascii_case("url"))
5332            && self
5333                .non_trivia_token_from(index + 1)
5334                .is_some_and(|(_, next_kind)| next_kind == SyntaxKind::LeftParen)
5335    }
5336
5337    fn parse_keyframes_rule_prelude(&mut self) {
5338        if !self.keyframes_rule_prelude_is_valid() {
5339            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @keyframes name");
5340        }
5341        self.consume_at_rule_prelude_tokens();
5342    }
5343
5344    fn keyframes_rule_prelude_is_valid(&self) -> bool {
5345        let Some((name_index, name_kind)) = self.non_trivia_token_from(self.position) else {
5346            return false;
5347        };
5348        if is_interpolation_start(name_kind) {
5349            return true;
5350        }
5351        if !matches!(name_kind, SyntaxKind::Ident | SyntaxKind::String) {
5352            return false;
5353        }
5354        self.non_trivia_token_from(name_index + 1)
5355            .is_none_or(|(_, kind)| is_at_rule_prelude_boundary(kind))
5356    }
5357
5358    fn parse_empty_at_rule_prelude(&mut self, message: &'static str) {
5359        self.eat_trivia();
5360        if self
5361            .current_kind()
5362            .is_some_and(|kind| !is_at_rule_prelude_boundary(kind))
5363        {
5364            self.error_at_current(ParseErrorCode::ExpectedValue, message);
5365            self.consume_at_rule_prelude_tokens();
5366        }
5367    }
5368
5369    fn parse_font_feature_values_prelude(&mut self) {
5370        if !self.font_feature_values_prelude_is_valid() {
5371            self.error_at_current(
5372                ParseErrorCode::ExpectedValue,
5373                "invalid @font-feature-values family name",
5374            );
5375        }
5376        self.consume_at_rule_prelude_tokens();
5377    }
5378
5379    fn font_feature_values_prelude_is_valid(&self) -> bool {
5380        self.non_trivia_token_from(self.position)
5381            .is_some_and(|(_, kind)| {
5382                matches!(kind, SyntaxKind::Ident | SyntaxKind::String)
5383                    || is_interpolation_start(kind)
5384            })
5385    }
5386
5387    fn parse_layer_rule_prelude(&mut self) {
5388        self.eat_trivia();
5389        match self.current_kind() {
5390            Some(SyntaxKind::LeftBrace | SyntaxKind::SassIndent) => return,
5391            Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) | None => {
5392                self.empty_bogus_node(
5393                    SyntaxKind::BogusLayerName,
5394                    ParseErrorCode::ExpectedValue,
5395                    "invalid @layer prelude",
5396                );
5397                return;
5398            }
5399            Some(_) => {}
5400        }
5401
5402        let valid = self.layer_rule_prelude_is_valid();
5403        if !valid {
5404            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @layer prelude");
5405        }
5406        self.builder.start_node(if valid {
5407            SyntaxKind::LayerName
5408        } else {
5409            SyntaxKind::BogusLayerName
5410        });
5411        self.consume_at_rule_prelude_tokens_without_wrapping();
5412        self.builder.finish_node();
5413    }
5414
5415    fn layer_rule_prelude_is_valid(&self) -> bool {
5416        let mut saw_name = false;
5417        let mut expecting_segment = true;
5418        let mut index = self.position;
5419
5420        while let Some(token) = self.tokens.get(index) {
5421            if token.kind.is_trivia() {
5422                index += 1;
5423                continue;
5424            }
5425            if is_at_rule_prelude_boundary(token.kind) {
5426                return saw_name && !expecting_segment;
5427            }
5428            if is_interpolation_start(token.kind) {
5429                return true;
5430            }
5431            match token.kind {
5432                SyntaxKind::Ident if expecting_segment => {
5433                    saw_name = true;
5434                    expecting_segment = false;
5435                }
5436                SyntaxKind::Comma if saw_name && !expecting_segment => {
5437                    expecting_segment = true;
5438                }
5439                SyntaxKind::Dot if saw_name && !expecting_segment => {
5440                    expecting_segment = true;
5441                }
5442                _ => return false,
5443            }
5444            index += 1;
5445        }
5446
5447        saw_name && !expecting_segment
5448    }
5449
5450    fn parse_container_rule_prelude(&mut self) {
5451        self.eat_trivia();
5452        let valid = self.container_rule_prelude_is_valid();
5453        if !valid {
5454            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @container prelude");
5455        }
5456        self.builder.start_node(if valid {
5457            SyntaxKind::ContainerCondition
5458        } else {
5459            SyntaxKind::BogusContainerCondition
5460        });
5461        self.consume_at_rule_prelude_tokens_without_wrapping();
5462        self.builder.finish_node();
5463    }
5464
5465    fn container_rule_prelude_is_valid(&self) -> bool {
5466        let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5467            return false;
5468        };
5469        if is_at_rule_prelude_boundary(first_kind) {
5470            return false;
5471        }
5472        if !self.current_prelude_parentheses_are_balanced_until(&[
5473            SyntaxKind::LeftBrace,
5474            SyntaxKind::SassIndent,
5475            SyntaxKind::Semicolon,
5476            SyntaxKind::SassOptionalSemicolon,
5477        ]) {
5478            return false;
5479        }
5480        if self.container_condition_starts_at(first_index, first_kind) {
5481            return true;
5482        }
5483        if first_kind != SyntaxKind::Ident {
5484            return false;
5485        }
5486        self.non_trivia_token_from(first_index + 1).is_some_and(
5487            |(condition_index, condition_kind)| {
5488                self.container_condition_starts_at(condition_index, condition_kind)
5489            },
5490        )
5491    }
5492
5493    fn container_condition_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5494        if matches!(kind, SyntaxKind::LeftParen | SyntaxKind::KeywordNot)
5495            || is_interpolation_start(kind)
5496        {
5497            return true;
5498        }
5499        kind == SyntaxKind::Ident
5500            && self
5501                .non_trivia_token_from(index + 1)
5502                .is_some_and(|(_, next_kind)| next_kind == SyntaxKind::LeftParen)
5503    }
5504
5505    fn parse_supports_rule_prelude(&mut self) {
5506        self.eat_trivia();
5507        let valid = self.supports_rule_prelude_is_valid();
5508        if !valid {
5509            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @supports prelude");
5510        }
5511        self.builder.start_node(if valid {
5512            SyntaxKind::SupportsCondition
5513        } else {
5514            SyntaxKind::BogusSupportsCondition
5515        });
5516        self.consume_at_rule_prelude_tokens_without_wrapping();
5517        self.builder.finish_node();
5518    }
5519
5520    fn supports_rule_prelude_is_valid(&self) -> bool {
5521        let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5522            return false;
5523        };
5524        if is_at_rule_prelude_boundary(first_kind) {
5525            return false;
5526        }
5527        if !self.current_prelude_parentheses_are_balanced_until(&[
5528            SyntaxKind::LeftBrace,
5529            SyntaxKind::SassIndent,
5530            SyntaxKind::Semicolon,
5531            SyntaxKind::SassOptionalSemicolon,
5532        ]) {
5533            return false;
5534        }
5535        self.supports_condition_starts_at(first_index, first_kind)
5536    }
5537
5538    fn supports_condition_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5539        if kind == SyntaxKind::KeywordNot {
5540            return self
5541                .non_trivia_token_from(index + 1)
5542                .is_some_and(|(next_index, next_kind)| {
5543                    self.supports_condition_starts_at(next_index, next_kind)
5544                });
5545        }
5546        if kind == SyntaxKind::LeftParen || is_interpolation_start(kind) {
5547            return true;
5548        }
5549        kind == SyntaxKind::Ident
5550            && self
5551                .non_trivia_token_from(index + 1)
5552                .is_some_and(|(_, next_kind)| next_kind == SyntaxKind::LeftParen)
5553    }
5554
5555    fn parse_scope_rule_prelude(&mut self) {
5556        self.eat_trivia();
5557        let valid = self.scope_rule_prelude_is_valid();
5558        if !valid {
5559            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @scope prelude");
5560        }
5561        self.builder.start_node(if valid {
5562            SyntaxKind::ScopeRange
5563        } else {
5564            SyntaxKind::BogusScopeRange
5565        });
5566        self.consume_at_rule_prelude_tokens_without_wrapping();
5567        self.builder.finish_node();
5568    }
5569
5570    fn scope_rule_prelude_is_valid(&self) -> bool {
5571        let Some((start_index, start_kind)) = self.non_trivia_token_from(self.position) else {
5572            return false;
5573        };
5574        if is_at_rule_prelude_boundary(start_kind) {
5575            return false;
5576        }
5577        if !self.current_prelude_parentheses_are_balanced_until(&[
5578            SyntaxKind::LeftBrace,
5579            SyntaxKind::SassIndent,
5580            SyntaxKind::Semicolon,
5581            SyntaxKind::SassOptionalSemicolon,
5582        ]) {
5583            return false;
5584        }
5585        if is_interpolation_start(start_kind) {
5586            return true;
5587        }
5588        if start_kind != SyntaxKind::LeftParen {
5589            return false;
5590        }
5591
5592        let Some(start_close_index) = self.parenthesized_prelude_close_index(start_index) else {
5593            return false;
5594        };
5595        let Some((after_start_index, after_start_kind)) =
5596            self.non_trivia_token_from(start_close_index + 1)
5597        else {
5598            return true;
5599        };
5600        if is_at_rule_prelude_boundary(after_start_kind) {
5601            return true;
5602        }
5603        if after_start_kind != SyntaxKind::Ident
5604            || !self
5605                .tokens
5606                .get(after_start_index)
5607                .is_some_and(|token| token.text.eq_ignore_ascii_case("to"))
5608        {
5609            return false;
5610        }
5611
5612        let Some((end_index, end_kind)) = self.non_trivia_token_from(after_start_index + 1) else {
5613            return false;
5614        };
5615        if is_interpolation_start(end_kind) {
5616            return true;
5617        }
5618        if end_kind != SyntaxKind::LeftParen {
5619            return false;
5620        }
5621        let Some(end_close_index) = self.parenthesized_prelude_close_index(end_index) else {
5622            return false;
5623        };
5624        self.non_trivia_token_from(end_close_index + 1)
5625            .is_none_or(|(_, kind)| is_at_rule_prelude_boundary(kind))
5626    }
5627
5628    fn parenthesized_prelude_close_index(&self, open_index: usize) -> Option<usize> {
5629        let mut depth = 0usize;
5630        for (index, token) in self.tokens.iter().enumerate().skip(open_index) {
5631            match token.kind {
5632                SyntaxKind::LeftParen => depth += 1,
5633                SyntaxKind::RightParen => {
5634                    depth = depth.saturating_sub(1);
5635                    if depth == 0 {
5636                        return Some(index);
5637                    }
5638                }
5639                kind if depth == 0 && is_at_rule_prelude_boundary(kind) => return None,
5640                _ => {}
5641            }
5642        }
5643        None
5644    }
5645
5646    fn parse_page_rule_prelude(&mut self) {
5647        self.eat_trivia();
5648        if self.current_kind().is_none_or(is_at_rule_prelude_boundary) {
5649            return;
5650        }
5651        let valid = self.page_rule_prelude_is_valid();
5652        if !valid {
5653            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @page prelude");
5654        }
5655        self.builder.start_node(if valid {
5656            SyntaxKind::AtRulePrelude
5657        } else {
5658            SyntaxKind::BogusAtRulePrelude
5659        });
5660        self.consume_at_rule_prelude_tokens_without_wrapping();
5661        self.builder.finish_node();
5662    }
5663
5664    fn page_rule_prelude_is_valid(&self) -> bool {
5665        let mut expecting_selector = true;
5666        let mut expecting_pseudo_name = false;
5667        let mut saw_selector = false;
5668
5669        for token in self.tokens.iter().skip(self.position) {
5670            if token.kind.is_trivia() {
5671                continue;
5672            }
5673            if is_at_rule_prelude_boundary(token.kind) {
5674                return saw_selector && !expecting_selector && !expecting_pseudo_name;
5675            }
5676            if is_interpolation_start(token.kind) {
5677                return true;
5678            }
5679            if expecting_pseudo_name {
5680                if token.kind != SyntaxKind::Ident {
5681                    return false;
5682                }
5683                saw_selector = true;
5684                expecting_selector = false;
5685                expecting_pseudo_name = false;
5686                continue;
5687            }
5688            match token.kind {
5689                SyntaxKind::Ident if expecting_selector => {
5690                    saw_selector = true;
5691                    expecting_selector = false;
5692                }
5693                SyntaxKind::Colon => {
5694                    expecting_pseudo_name = true;
5695                }
5696                SyntaxKind::Comma if saw_selector && !expecting_selector => {
5697                    expecting_selector = true;
5698                }
5699                _ => return false,
5700            }
5701        }
5702
5703        saw_selector && !expecting_selector && !expecting_pseudo_name
5704    }
5705
5706    fn parse_import_prelude(&mut self) {
5707        self.eat_trivia();
5708        if self.dialect == StyleDialect::Less && self.current_kind() == Some(SyntaxKind::LeftParen)
5709        {
5710            self.builder.start_node(SyntaxKind::AtRulePrelude);
5711            self.parse_balanced_parenthesized_prelude(None);
5712            self.builder.finish_node();
5713            self.eat_trivia();
5714        }
5715        if !self.parse_import_source() {
5716            self.parse_bogus_import_prelude();
5717            return;
5718        }
5719        while !self.at_end() {
5720            match self.current_kind() {
5721                Some(kind) if is_at_rule_prelude_boundary(kind) => break,
5722                Some(kind) if kind.is_trivia() => self.token_current(),
5723                Some(SyntaxKind::Ident) if self.current_text() == Some("layer") => {
5724                    self.parse_import_layer_tail_node()
5725                }
5726                Some(SyntaxKind::Ident) if self.current_text() == Some("supports") => {
5727                    self.parse_import_supports_tail_node()
5728                }
5729                Some(_) => {
5730                    self.parse_media_query_list();
5731                    break;
5732                }
5733                None => break,
5734            }
5735        }
5736    }
5737
5738    fn parse_import_source(&mut self) -> bool {
5739        match self.current_kind() {
5740            Some(SyntaxKind::Url) => {
5741                self.builder.start_node(SyntaxKind::UrlValue);
5742                self.token_current();
5743                self.builder.finish_node();
5744                true
5745            }
5746            Some(SyntaxKind::Ident)
5747                if self
5748                    .current_text()
5749                    .is_some_and(|text| text.eq_ignore_ascii_case("url"))
5750                    && self.next_kind() == Some(SyntaxKind::LeftParen) =>
5751            {
5752                self.builder.start_node(SyntaxKind::UrlValue);
5753                self.parse_function_call(&[SyntaxKind::LeftBrace, SyntaxKind::Semicolon]);
5754                self.builder.finish_node();
5755                true
5756            }
5757            Some(SyntaxKind::String) => {
5758                self.token_current();
5759                true
5760            }
5761            Some(kind) if is_interpolation_start(kind) => {
5762                self.parse_interpolation(kind, &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon]);
5763                true
5764            }
5765            Some(_) | None => false,
5766        }
5767    }
5768
5769    fn parse_bogus_import_prelude(&mut self) {
5770        self.builder.start_node(SyntaxKind::BogusAtRulePrelude);
5771        self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @import source");
5772        self.consume_at_rule_prelude_tokens_without_wrapping();
5773        self.builder.finish_node();
5774    }
5775
5776    fn parse_named_at_rule_prelude(
5777        &mut self,
5778        valid_head: fn(SyntaxKind) -> bool,
5779        message: &'static str,
5780    ) {
5781        if self.current_kind().is_none_or(is_at_rule_prelude_boundary) {
5782            return;
5783        }
5784        let valid_name = self
5785            .non_trivia_token_from(self.position)
5786            .is_some_and(|(_, kind)| valid_head(kind));
5787        if !valid_name {
5788            self.error_at_current(ParseErrorCode::ExpectedValue, message);
5789        }
5790        self.consume_at_rule_prelude_tokens();
5791    }
5792
5793    fn parse_import_layer_tail_node(&mut self) {
5794        let valid = self.import_layer_tail_is_valid();
5795        if !valid {
5796            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @import layer tail");
5797        }
5798        self.builder.start_node(if valid {
5799            SyntaxKind::LayerName
5800        } else {
5801            SyntaxKind::BogusLayerName
5802        });
5803        self.token_current();
5804        if self.current_kind() == Some(SyntaxKind::LeftParen) {
5805            self.parse_balanced_parenthesized_prelude(None);
5806        }
5807        self.builder.finish_node();
5808    }
5809
5810    fn import_layer_tail_is_valid(&self) -> bool {
5811        let Some((open_index, next_kind)) = self.non_trivia_token_from(self.position + 1) else {
5812            return true;
5813        };
5814        if next_kind != SyntaxKind::LeftParen {
5815            return true;
5816        }
5817        let Some(close_index) = self.parenthesized_prelude_close_index(open_index) else {
5818            return false;
5819        };
5820        self.layer_name_is_valid_between(open_index + 1, close_index)
5821    }
5822
5823    fn layer_name_is_valid_between(&self, start: usize, end: usize) -> bool {
5824        let mut saw_name = false;
5825        let mut expecting_segment = true;
5826
5827        for token in self.tokens[start..end]
5828            .iter()
5829            .filter(|token| !token.kind.is_trivia())
5830        {
5831            if is_interpolation_start(token.kind) {
5832                return true;
5833            }
5834            match token.kind {
5835                SyntaxKind::Ident if expecting_segment => {
5836                    saw_name = true;
5837                    expecting_segment = false;
5838                }
5839                SyntaxKind::Dot if saw_name && !expecting_segment => {
5840                    expecting_segment = true;
5841                }
5842                _ => return false,
5843            }
5844        }
5845
5846        saw_name && !expecting_segment
5847    }
5848
5849    fn parse_import_supports_tail_node(&mut self) {
5850        let valid = self.import_supports_tail_is_valid();
5851        if !valid {
5852            self.error_at_current(
5853                ParseErrorCode::ExpectedValue,
5854                "invalid @import supports tail",
5855            );
5856        }
5857        self.builder.start_node(if valid {
5858            SyntaxKind::SupportsCondition
5859        } else {
5860            SyntaxKind::BogusSupportsCondition
5861        });
5862        self.token_current();
5863        if self.current_kind() == Some(SyntaxKind::LeftParen) {
5864            self.parse_balanced_parenthesized_prelude(None);
5865        }
5866        self.builder.finish_node();
5867    }
5868
5869    fn import_supports_tail_is_valid(&self) -> bool {
5870        let Some((open_index, SyntaxKind::LeftParen)) =
5871            self.non_trivia_token_from(self.position + 1)
5872        else {
5873            return false;
5874        };
5875        let Some(close_index) = self.parenthesized_prelude_close_index(open_index) else {
5876            return false;
5877        };
5878        self.non_trivia_token_from(open_index + 1)
5879            .is_some_and(|(inner_index, inner_kind)| {
5880                inner_index < close_index && inner_kind != SyntaxKind::RightParen
5881            })
5882    }
5883
5884    fn consume_at_rule_prelude_tokens(&mut self) {
5885        if self.current_kind().is_none_or(is_at_rule_prelude_boundary) {
5886            return;
5887        }
5888        self.builder
5889            .start_node(self.current_generic_at_rule_prelude_node_kind());
5890        self.consume_at_rule_prelude_tokens_without_wrapping();
5891        self.builder.finish_node();
5892    }
5893
5894    fn consume_at_rule_prelude_tokens_without_wrapping(&mut self) {
5895        while !self.at_end() {
5896            match self.current_kind() {
5897                Some(kind) if is_at_rule_prelude_boundary(kind) => break,
5898                Some(SyntaxKind::LeftParen) => self.parse_balanced_parenthesized_prelude(None),
5899                Some(kind) if is_interpolation_start(kind) => {
5900                    self.parse_interpolation(kind, &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon])
5901                }
5902                Some(_) => self.token_current(),
5903                None => break,
5904            }
5905        }
5906    }
5907
5908    fn parse_balanced_parenthesized_prelude(&mut self, node_kind: Option<SyntaxKind>) {
5909        self.parse_balanced_parenthesized_prelude_until(
5910            node_kind,
5911            &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon],
5912        );
5913    }
5914
5915    fn parse_balanced_parenthesized_prelude_until(
5916        &mut self,
5917        node_kind: Option<SyntaxKind>,
5918        recovery: &[SyntaxKind],
5919    ) {
5920        if let Some(kind) = node_kind {
5921            self.builder.start_node(kind);
5922        }
5923        let mut depth = 0usize;
5924        let mut closed = false;
5925        while !self.at_end() {
5926            match self.current_kind() {
5927                Some(SyntaxKind::LeftParen) => {
5928                    depth += 1;
5929                    self.token_current();
5930                }
5931                Some(SyntaxKind::RightParen) => {
5932                    self.token_current();
5933                    depth = depth.saturating_sub(1);
5934                    if depth == 0 {
5935                        closed = true;
5936                        break;
5937                    }
5938                }
5939                Some(kind) if recovery.contains(&kind) => break,
5940                Some(kind) if is_interpolation_start(kind) => {
5941                    self.parse_interpolation(kind, &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon])
5942                }
5943                Some(_) => self.token_current(),
5944                None => break,
5945            }
5946        }
5947        if node_kind.is_some() {
5948            self.builder.finish_node();
5949        }
5950        if !closed {
5951            self.error_at_current(
5952                ParseErrorCode::UnexpectedCharacter,
5953                "unterminated parenthesized prelude",
5954            );
5955        }
5956    }
5957
5958    fn parse_interpolation(&mut self, start_kind: SyntaxKind, recovery: &[SyntaxKind]) {
5959        let Some(end_kind) = interpolation_end_kind(start_kind) else {
5960            self.token_current();
5961            return;
5962        };
5963        let closed = self.find_before_recovery(end_kind, recovery);
5964        self.builder.start_node(if closed {
5965            SyntaxKind::Interpolation
5966        } else {
5967            SyntaxKind::BogusInterpolation
5968        });
5969        if self.current_kind() == Some(start_kind) {
5970            self.token_current();
5971        }
5972        while !self.at_end() {
5973            match self.current_kind() {
5974                Some(kind) if kind == end_kind => {
5975                    self.token_current();
5976                    break;
5977                }
5978                Some(kind) if !closed && recovery.contains(&kind) => break,
5979                Some(_) => self.token_current(),
5980                None => break,
5981            }
5982        }
5983        if !closed {
5984            self.error_at_current(
5985                ParseErrorCode::UnexpectedCharacter,
5986                "unterminated interpolation",
5987            );
5988        }
5989        self.builder.finish_node();
5990    }
5991
5992    fn parse_group_at_rule_block(&mut self) {
5993        self.token_current();
5994        self.builder.start_node(SyntaxKind::RuleList);
5995        self.parse_rule_list_items();
5996        self.builder.finish_node();
5997        if self.current_kind() == Some(SyntaxKind::RightBrace) {
5998            self.token_current();
5999        }
6000    }
6001
6002    fn parse_rule_list_items(&mut self) {
6003        while !self.at_end() {
6004            self.eat_trivia();
6005            match self.current_kind() {
6006                Some(SyntaxKind::RightBrace | SyntaxKind::SassDedent) | None => break,
6007                Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) => {
6008                    self.token_current()
6009                }
6010                Some(SyntaxKind::AtKeyword) if self.current_is_css_module_value_rule() => {
6011                    self.parse_css_module_value_rule()
6012                }
6013                Some(SyntaxKind::AtKeyword) if self.current_dialect_at_rule_spec().is_some() => {
6014                    self.parse_dialect_at_rule()
6015                }
6016                Some(SyntaxKind::AtKeyword) => self.parse_at_rule(),
6017                Some(_) => self.parse_rule(),
6018            }
6019        }
6020    }
6021
6022    fn parse_declaration_block(&mut self) {
6023        self.token_current();
6024        self.builder
6025            .start_node(if self.previous_left_brace_has_match() {
6026                SyntaxKind::DeclarationList
6027            } else {
6028                SyntaxKind::BogusDeclarationList
6029            });
6030        self.parse_declaration_list();
6031        self.builder.finish_node();
6032        if self.current_kind() == Some(SyntaxKind::RightBrace) {
6033            self.token_current();
6034        } else {
6035            self.missing_token_bogus_trivia(
6036                ParseErrorCode::UnexpectedCharacter,
6037                "unterminated declaration block",
6038            );
6039        }
6040    }
6041
6042    fn parse_sass_indented_at_rule_block(&mut self, block_kind: AtRuleBlockKind) {
6043        self.builder.start_node(SyntaxKind::SassIndentedBlock);
6044        if self.current_kind() == Some(SyntaxKind::SassIndent) {
6045            self.token_current();
6046        }
6047        match block_kind {
6048            AtRuleBlockKind::GroupRuleList => {
6049                self.builder.start_node(SyntaxKind::RuleList);
6050                self.parse_rule_list_items();
6051                self.builder.finish_node();
6052            }
6053            AtRuleBlockKind::DeclarationList | AtRuleBlockKind::Keyframes => {
6054                self.builder.start_node(SyntaxKind::DeclarationList);
6055                self.parse_declaration_list();
6056                self.builder.finish_node();
6057            }
6058            AtRuleBlockKind::Raw => self.consume_sass_indented_raw_body(),
6059        }
6060        if self.current_kind() == Some(SyntaxKind::SassDedent) {
6061            self.token_current();
6062        } else {
6063            self.error_at_current(
6064                ParseErrorCode::UnexpectedCharacter,
6065                "unterminated Sass indented at-rule block",
6066            );
6067        }
6068        self.builder.finish_node();
6069    }
6070
6071    fn consume_sass_indented_raw_body(&mut self) {
6072        let mut depth = 0usize;
6073        while !self.at_end() {
6074            match self.current_kind() {
6075                Some(SyntaxKind::SassIndent) => {
6076                    depth += 1;
6077                    self.token_current();
6078                }
6079                Some(SyntaxKind::SassDedent) if depth == 0 => break,
6080                Some(SyntaxKind::SassDedent) => {
6081                    depth = depth.saturating_sub(1);
6082                    self.token_current();
6083                }
6084                Some(_) => self.token_current(),
6085                None => break,
6086            }
6087        }
6088    }
6089
6090    fn parse_keyframes_block(&mut self) {
6091        self.token_current();
6092        while !self.at_end() {
6093            self.eat_trivia();
6094            match self.current_kind() {
6095                Some(SyntaxKind::RightBrace) | None => break,
6096                Some(_) => self.parse_keyframe_block(),
6097            }
6098        }
6099        if self.current_kind() == Some(SyntaxKind::RightBrace) {
6100            self.token_current();
6101        }
6102    }
6103
6104    fn parse_keyframe_block(&mut self) {
6105        let has_block = self.find_before_recovery(SyntaxKind::LeftBrace, &[SyntaxKind::RightBrace]);
6106        self.builder.start_node(if has_block {
6107            SyntaxKind::KeyframeBlock
6108        } else {
6109            SyntaxKind::BogusKeyframeBlock
6110        });
6111        if has_block && !self.keyframe_selector_list_is_valid() {
6112            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid keyframe selector");
6113        }
6114        while !self.at_end() {
6115            match self.current_kind() {
6116                Some(SyntaxKind::LeftBrace) => {
6117                    self.parse_declaration_block();
6118                    break;
6119                }
6120                Some(SyntaxKind::RightBrace) | None => break,
6121                Some(_) => self.token_current(),
6122            }
6123        }
6124        if !has_block {
6125            self.error_at_current(
6126                ParseErrorCode::UnexpectedCharacter,
6127                "expected keyframe declaration block",
6128            );
6129        }
6130        self.builder.finish_node();
6131    }
6132
6133    fn keyframe_selector_list_is_valid(&self) -> bool {
6134        let mut index = self.position;
6135        let mut saw_selector = false;
6136        let mut expect_selector = true;
6137        loop {
6138            let Some((token_index, kind)) = self.non_trivia_token_from(index) else {
6139                return false;
6140            };
6141            if kind == SyntaxKind::LeftBrace {
6142                return saw_selector && !expect_selector;
6143            }
6144            if expect_selector {
6145                if is_interpolation_start(kind) {
6146                    return true;
6147                }
6148                if !keyframe_selector_token_is_valid(self.tokens[token_index]) {
6149                    return false;
6150                }
6151                saw_selector = true;
6152                expect_selector = false;
6153                index = token_index + 1;
6154                continue;
6155            }
6156            if kind != SyntaxKind::Comma {
6157                return false;
6158            }
6159            expect_selector = true;
6160            index = token_index + 1;
6161        }
6162    }
6163
6164    fn consume_balanced_block(&mut self) {
6165        let mut depth = 0usize;
6166        while !self.at_end() {
6167            match self.current_kind() {
6168                Some(SyntaxKind::LeftBrace) => {
6169                    depth += 1;
6170                    self.token_current();
6171                }
6172                Some(SyntaxKind::RightBrace) => {
6173                    self.token_current();
6174                    depth = depth.saturating_sub(1);
6175                    if depth == 0 {
6176                        break;
6177                    }
6178                }
6179                Some(_) => self.token_current(),
6180                None => break,
6181            }
6182        }
6183    }
6184
6185    fn eat_trivia(&mut self) {
6186        while matches!(self.current_kind(), Some(kind) if kind.is_trivia()) {
6187            self.token_current();
6188        }
6189    }
6190
6191    fn consume_until_recovery(&mut self, recovery: &[SyntaxKind]) {
6192        let should_wrap = self
6193            .current_kind()
6194            .is_some_and(|kind| !recovery.contains(&kind));
6195        if should_wrap {
6196            self.builder.start_node(SyntaxKind::BogusRecovery);
6197        }
6198        while !self.at_end() {
6199            match self.current_kind() {
6200                Some(kind) if recovery.contains(&kind) => break,
6201                Some(_) => self.token_current(),
6202                None => break,
6203            }
6204        }
6205        if should_wrap {
6206            self.builder.finish_node();
6207        }
6208    }
6209
6210    fn find_before_recovery(&self, target: SyntaxKind, recovery: &[SyntaxKind]) -> bool {
6211        let mut index = self.position;
6212        while let Some(token) = self.tokens.get(index) {
6213            if token.kind == target {
6214                return true;
6215            }
6216            if recovery.contains(&token.kind) {
6217                return false;
6218            }
6219            index += 1;
6220        }
6221        false
6222    }
6223
6224    fn find_rule_block_open_before_recovery(&self, recovery: &[SyntaxKind]) -> bool {
6225        let mut index = self.position;
6226        while let Some(token) = self.tokens.get(index) {
6227            if token.kind == SyntaxKind::LeftBrace
6228                || (self.dialect == StyleDialect::Sass && token.kind == SyntaxKind::SassIndent)
6229            {
6230                return true;
6231            }
6232            if recovery.contains(&token.kind) {
6233                return false;
6234            }
6235            index += 1;
6236        }
6237        false
6238    }
6239
6240    fn find_text_before_recovery(&self, target: &str, recovery: &[SyntaxKind]) -> bool {
6241        let mut index = self.position;
6242        while let Some(token) = self.tokens.get(index) {
6243            if token.text == target {
6244                return true;
6245            }
6246            if recovery.contains(&token.kind) {
6247                return false;
6248            }
6249            index += 1;
6250        }
6251        false
6252    }
6253
6254    fn current_function_has_closing_paren_before(&self, recovery: &[SyntaxKind]) -> bool {
6255        let Some(open_index) = self.position.checked_add(1) else {
6256            return false;
6257        };
6258        if self
6259            .tokens
6260            .get(open_index)
6261            .is_none_or(|token| token.kind != SyntaxKind::LeftParen)
6262        {
6263            return false;
6264        }
6265
6266        let mut depth = 0usize;
6267        for token in self.tokens.iter().skip(open_index) {
6268            match token.kind {
6269                SyntaxKind::LeftParen => depth += 1,
6270                SyntaxKind::RightParen => {
6271                    depth = depth.saturating_sub(1);
6272                    if depth == 0 {
6273                        return true;
6274                    }
6275                }
6276                kind if depth == 1 && recovery.contains(&kind) => return false,
6277                _ => {}
6278            }
6279        }
6280        false
6281    }
6282
6283    fn current_split_important_annotation(&self) -> bool {
6284        self.current_text() == Some("!")
6285            && self
6286                .non_trivia_token_from(self.position + 1)
6287                .is_some_and(|(index, kind)| {
6288                    matches!(kind, SyntaxKind::Ident | SyntaxKind::KeywordImportant)
6289                        && self
6290                            .tokens
6291                            .get(index)
6292                            .is_some_and(|token| token.text.eq_ignore_ascii_case("important"))
6293                })
6294    }
6295
6296    fn current_scss_variable_flag_annotation(&self) -> bool {
6297        matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass)
6298            && self.current_text() == Some("!")
6299            && self
6300                .non_trivia_token_from(self.position + 1)
6301                .is_some_and(|(index, kind)| {
6302                    kind == SyntaxKind::Ident
6303                        && self.tokens.get(index).is_some_and(|token| {
6304                            token.text.eq_ignore_ascii_case("default")
6305                                || token.text.eq_ignore_ascii_case("global")
6306                        })
6307                })
6308    }
6309
6310    fn current_bracketed_value_has_closing_bracket_before(&self, recovery: &[SyntaxKind]) -> bool {
6311        let mut depth = 0usize;
6312        for token in self.tokens.iter().skip(self.position) {
6313            match token.kind {
6314                SyntaxKind::LeftBracket => depth += 1,
6315                SyntaxKind::RightBracket => {
6316                    depth = depth.saturating_sub(1);
6317                    if depth == 0 {
6318                        return true;
6319                    }
6320                }
6321                kind if depth == 1 && recovery.contains(&kind) => return false,
6322                _ => {}
6323            }
6324        }
6325        false
6326    }
6327
6328    fn current_simple_block_has_matching_close(&self, recovery: &[SyntaxKind]) -> bool {
6329        let Some(open_kind) = self.current_kind() else {
6330            return false;
6331        };
6332        if matching_simple_block_close(open_kind).is_none() {
6333            return false;
6334        }
6335
6336        let mut expected_closes = Vec::new();
6337        for token in self.tokens.iter().skip(self.position) {
6338            if let Some(close_kind) = matching_simple_block_close(token.kind) {
6339                expected_closes.push(close_kind);
6340                continue;
6341            }
6342
6343            if expected_closes.last().copied() == Some(token.kind) {
6344                expected_closes.pop();
6345                if expected_closes.is_empty() {
6346                    return true;
6347                }
6348                continue;
6349            }
6350
6351            if expected_closes.len() == 1 && recovery.contains(&token.kind) {
6352                return false;
6353            }
6354        }
6355        false
6356    }
6357
6358    fn current_dialect_at_rule_node_kind(&self, spec: AtRuleSpec) -> SyntaxKind {
6359        if !self.find_rule_block_open_before_recovery(&[
6360            SyntaxKind::Semicolon,
6361            SyntaxKind::SassOptionalSemicolon,
6362            SyntaxKind::RightBrace,
6363            SyntaxKind::SassDedent,
6364        ]) {
6365            return match spec.node_kind {
6366                SyntaxKind::ScssMixinDeclaration => SyntaxKind::BogusScssMixin,
6367                SyntaxKind::ScssFunctionDeclaration => SyntaxKind::BogusScssFunction,
6368                SyntaxKind::ScssControlIf
6369                | SyntaxKind::ScssControlElse
6370                | SyntaxKind::ScssControlEach
6371                | SyntaxKind::ScssControlFor
6372                | SyntaxKind::ScssControlWhile => SyntaxKind::BogusScssControl,
6373                _ => spec.node_kind,
6374            };
6375        }
6376        spec.node_kind
6377    }
6378
6379    fn current_less_guard_has_condition_before(&self, recovery: &[SyntaxKind]) -> bool {
6380        let mut index = self.position + 1;
6381        while let Some(token) = self.tokens.get(index) {
6382            if recovery.contains(&token.kind) {
6383                return false;
6384            }
6385            if token.kind == SyntaxKind::LeftParen {
6386                return true;
6387            }
6388            index += 1;
6389        }
6390        false
6391    }
6392
6393    fn current_scss_module_config_has_balanced_parens(&self) -> bool {
6394        let Some((_, SyntaxKind::LeftParen)) = self.non_trivia_token_from(self.position + 1) else {
6395            return false;
6396        };
6397        self.current_prelude_parentheses_are_balanced_until(&[
6398            SyntaxKind::Semicolon,
6399            SyntaxKind::SassOptionalSemicolon,
6400            SyntaxKind::LeftBrace,
6401            SyntaxKind::SassIndent,
6402        ])
6403    }
6404
6405    fn current_value_has_top_level_comma_before(&self, recovery: &[SyntaxKind]) -> bool {
6406        let mut paren_depth = 0usize;
6407        let mut bracket_depth = 0usize;
6408        for token in self.tokens.iter().skip(self.position) {
6409            match token.kind {
6410                kind if paren_depth == 0 && bracket_depth == 0 && recovery.contains(&kind) => {
6411                    return false;
6412                }
6413                SyntaxKind::LeftParen => paren_depth += 1,
6414                SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
6415                SyntaxKind::LeftBracket => bracket_depth += 1,
6416                SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
6417                SyntaxKind::Comma if paren_depth == 0 && bracket_depth == 0 => return true,
6418                _ => {}
6419            }
6420        }
6421        false
6422    }
6423
6424    fn current_value_list_is_bogus(&self, recovery: &[SyntaxKind]) -> bool {
6425        let mut paren_depth = 0usize;
6426        let mut bracket_depth = 0usize;
6427        let mut expecting_item = true;
6428        for token in self.tokens.iter().skip(self.position) {
6429            if token.kind.is_trivia() {
6430                continue;
6431            }
6432            match token.kind {
6433                kind if paren_depth == 0 && bracket_depth == 0 && recovery.contains(&kind) => {
6434                    return expecting_item;
6435                }
6436                SyntaxKind::LeftParen => {
6437                    paren_depth += 1;
6438                    expecting_item = false;
6439                }
6440                SyntaxKind::RightParen => {
6441                    paren_depth = paren_depth.saturating_sub(1);
6442                    expecting_item = false;
6443                }
6444                SyntaxKind::LeftBracket => {
6445                    bracket_depth += 1;
6446                    expecting_item = false;
6447                }
6448                SyntaxKind::RightBracket => {
6449                    bracket_depth = bracket_depth.saturating_sub(1);
6450                    expecting_item = false;
6451                }
6452                SyntaxKind::Comma if paren_depth == 0 && bracket_depth == 0 => {
6453                    if expecting_item {
6454                        return true;
6455                    }
6456                    expecting_item = true;
6457                }
6458                _ => expecting_item = false,
6459            }
6460        }
6461        expecting_item
6462    }
6463
6464    fn current_starts_missing_semicolon_declaration(&self, recovery: &[SyntaxKind]) -> bool {
6465        match self.current_kind() {
6466            Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {}
6467            _ => return false,
6468        }
6469
6470        let mut index = self.position + 1;
6471        while let Some(token) = self.tokens.get(index) {
6472            if token.kind.is_trivia() {
6473                index += 1;
6474                continue;
6475            }
6476            if recovery.contains(&token.kind) {
6477                return false;
6478            }
6479            return token.kind == SyntaxKind::Colon;
6480        }
6481        false
6482    }
6483
6484    fn current_selector_item_is_bogus(&self, recovery: &[SyntaxKind]) -> bool {
6485        self.selector_item_is_bogus_from(self.position, recovery)
6486    }
6487
6488    fn selector_item_is_bogus_from(&self, start: usize, recovery: &[SyntaxKind]) -> bool {
6489        let mut paren_depth = 0usize;
6490        let mut bracket_depth = 0usize;
6491        let mut saw_selector_token = false;
6492
6493        for token in self.tokens.iter().skip(start) {
6494            if token.kind.is_trivia() {
6495                continue;
6496            }
6497            if paren_depth == 0
6498                && bracket_depth == 0
6499                && (token.kind == SyntaxKind::Comma
6500                    || is_selector_boundary_until(token.kind, recovery))
6501            {
6502                break;
6503            }
6504
6505            match token.kind {
6506                SyntaxKind::LeftParen => paren_depth += 1,
6507                SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
6508                SyntaxKind::LeftBracket => bracket_depth += 1,
6509                SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
6510                _ => {}
6511            }
6512
6513            if !selector_item_token_is_recoverable(token.kind) {
6514                return true;
6515            }
6516            saw_selector_token = true;
6517        }
6518
6519        !saw_selector_token
6520    }
6521
6522    fn selector_list_contains_bogus_item_until(&self, recovery: &[SyntaxKind]) -> bool {
6523        let mut index = self.position;
6524        while let Some(token) = self.tokens.get(index) {
6525            if token.kind.is_trivia() || token.kind == SyntaxKind::Comma {
6526                index += 1;
6527                continue;
6528            }
6529            if is_selector_boundary_until(token.kind, recovery) {
6530                return false;
6531            }
6532            if self.selector_item_is_bogus_from(index, recovery) {
6533                return true;
6534            }
6535
6536            let mut paren_depth = 0usize;
6537            let mut bracket_depth = 0usize;
6538            while let Some(token) = self.tokens.get(index) {
6539                if paren_depth == 0
6540                    && bracket_depth == 0
6541                    && (token.kind == SyntaxKind::Comma
6542                        || is_selector_boundary_until(token.kind, recovery))
6543                {
6544                    break;
6545                }
6546                match token.kind {
6547                    SyntaxKind::LeftParen => paren_depth += 1,
6548                    SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
6549                    SyntaxKind::LeftBracket => bracket_depth += 1,
6550                    SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
6551                    _ => {}
6552                }
6553                index += 1;
6554            }
6555        }
6556        false
6557    }
6558
6559    fn current_generic_at_rule_prelude_node_kind(&self) -> SyntaxKind {
6560        if self.current_prelude_parentheses_are_balanced_until(&[
6561            SyntaxKind::LeftBrace,
6562            SyntaxKind::Semicolon,
6563        ]) {
6564            SyntaxKind::AtRulePrelude
6565        } else {
6566            SyntaxKind::BogusAtRulePrelude
6567        }
6568    }
6569
6570    fn current_prelude_parentheses_are_balanced_until(&self, recovery: &[SyntaxKind]) -> bool {
6571        let mut depth = 0usize;
6572        for token in self.tokens.iter().skip(self.position) {
6573            match token.kind {
6574                kind if depth == 0 && recovery.contains(&kind) => return true,
6575                SyntaxKind::LeftParen => depth += 1,
6576                SyntaxKind::RightParen => {
6577                    if depth == 0 {
6578                        return false;
6579                    }
6580                    depth -= 1;
6581                }
6582                _ => {}
6583            }
6584        }
6585        depth == 0
6586    }
6587
6588    fn previous_left_brace_has_match(&self) -> bool {
6589        let Some(open_index) = self.position.checked_sub(1) else {
6590            return false;
6591        };
6592        let Some(open) = self.tokens.get(open_index) else {
6593            return false;
6594        };
6595        if open.kind != SyntaxKind::LeftBrace {
6596            return false;
6597        }
6598
6599        let mut depth = 0usize;
6600        for token in self.tokens.iter().skip(open_index) {
6601            match token.kind {
6602                SyntaxKind::LeftBrace => depth += 1,
6603                SyntaxKind::RightBrace => {
6604                    depth = depth.saturating_sub(1);
6605                    if depth == 0 {
6606                        return true;
6607                    }
6608                }
6609                _ => {}
6610            }
6611        }
6612        false
6613    }
6614
6615    fn current_starts_nested_rule(&self) -> bool {
6616        matches!(
6617            self.current_kind(),
6618            Some(
6619                SyntaxKind::Dot
6620                    | SyntaxKind::Hash
6621                    | SyntaxKind::Ampersand
6622                    | SyntaxKind::Colon
6623                    | SyntaxKind::DoubleColon
6624                    | SyntaxKind::LeftBracket
6625            )
6626        ) && self.find_rule_block_open_before_recovery(&[
6627            SyntaxKind::Colon,
6628            SyntaxKind::Semicolon,
6629            SyntaxKind::SassOptionalSemicolon,
6630            SyntaxKind::RightBrace,
6631            SyntaxKind::SassDedent,
6632        ])
6633    }
6634
6635    fn current_starts_scss_nested_property(&self) -> bool {
6636        if !matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass) {
6637            return false;
6638        }
6639        if !matches!(
6640            self.current_kind(),
6641            Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
6642        ) {
6643            return false;
6644        }
6645
6646        let mut saw_colon = false;
6647        for token in self.tokens.iter().skip(self.position) {
6648            match token.kind {
6649                SyntaxKind::Colon => saw_colon = true,
6650                SyntaxKind::LeftBrace if saw_colon => return true,
6651                SyntaxKind::SassIndent if saw_colon && self.dialect == StyleDialect::Sass => {
6652                    return true;
6653                }
6654                SyntaxKind::Semicolon
6655                | SyntaxKind::SassOptionalSemicolon
6656                | SyntaxKind::RightBrace
6657                | SyntaxKind::SassDedent => return false,
6658                _ => {}
6659            }
6660        }
6661        false
6662    }
6663
6664    fn current_starts_less_mixin_declaration(&self) -> bool {
6665        self.dialect == StyleDialect::Less
6666            && self.current_starts_less_callable_signature()
6667            && self.find_before_recovery(
6668                SyntaxKind::LeftBrace,
6669                &[SyntaxKind::Semicolon, SyntaxKind::RightBrace],
6670            )
6671    }
6672
6673    fn current_starts_less_mixin_call(&self) -> bool {
6674        self.dialect == StyleDialect::Less
6675            && self.current_starts_less_callable_signature()
6676            && !self.find_before_recovery(
6677                SyntaxKind::LeftBrace,
6678                &[SyntaxKind::Semicolon, SyntaxKind::RightBrace],
6679            )
6680    }
6681
6682    fn current_starts_less_callable_signature(&self) -> bool {
6683        match self.current_kind() {
6684            Some(SyntaxKind::Dot) => {
6685                let Some((index, SyntaxKind::Ident | SyntaxKind::CustomPropertyName)) =
6686                    self.non_trivia_token_from(self.position + 1)
6687                else {
6688                    return false;
6689                };
6690                self.non_trivia_token_from(index + 1)
6691                    .is_some_and(|(_, kind)| kind == SyntaxKind::LeftParen)
6692            }
6693            Some(SyntaxKind::Hash) => self
6694                .non_trivia_token_from(self.position + 1)
6695                .is_some_and(|(_, kind)| kind == SyntaxKind::LeftParen),
6696            _ => false,
6697        }
6698    }
6699
6700    fn current_starts_less_extend_rule(&self) -> bool {
6701        self.dialect == StyleDialect::Less
6702            && self.current_kind() == Some(SyntaxKind::Colon)
6703            && self
6704                .non_trivia_token_from(self.position + 1)
6705                .is_some_and(|(index, kind)| {
6706                    kind == SyntaxKind::Ident
6707                        && self
6708                            .tokens
6709                            .get(index)
6710                            .is_some_and(|token| token.text == "extend")
6711                })
6712    }
6713
6714    fn current_starts_less_namespace_access(&self) -> bool {
6715        self.dialect == StyleDialect::Less
6716            && matches!(
6717                self.current_kind(),
6718                Some(SyntaxKind::Dot | SyntaxKind::Hash)
6719            )
6720            && self.find_before_recovery(
6721                SyntaxKind::GreaterThan,
6722                &[
6723                    SyntaxKind::Semicolon,
6724                    SyntaxKind::LeftBrace,
6725                    SyntaxKind::RightBrace,
6726                ],
6727            )
6728            && self.find_before_recovery(
6729                SyntaxKind::LeftParen,
6730                &[
6731                    SyntaxKind::Semicolon,
6732                    SyntaxKind::LeftBrace,
6733                    SyntaxKind::RightBrace,
6734                ],
6735            )
6736    }
6737
6738    fn current_left_brace_has_match(&self) -> bool {
6739        let mut depth = 0usize;
6740        for token in self.tokens.iter().skip(self.position) {
6741            match token.kind {
6742                SyntaxKind::LeftBrace => depth += 1,
6743                SyntaxKind::RightBrace => {
6744                    depth = depth.saturating_sub(1);
6745                    if depth == 0 {
6746                        return true;
6747                    }
6748                }
6749                _ => {}
6750            }
6751        }
6752        false
6753    }
6754
6755    fn token_current(&mut self) {
6756        if let Some(token) = self.tokens.get(self.position).copied() {
6757            self.builder.token(token.kind, token.text);
6758            self.position += 1;
6759        }
6760    }
6761
6762    fn empty_bogus_node(&mut self, kind: SyntaxKind, code: ParseErrorCode, message: &'static str) {
6763        self.builder.start_node(kind);
6764        self.builder.finish_node();
6765        self.error_at_current(code, message);
6766    }
6767
6768    fn missing_token_bogus_trivia(&mut self, code: ParseErrorCode, message: &'static str) {
6769        self.builder.start_node(SyntaxKind::BogusTrivia);
6770        self.builder.finish_node();
6771        self.error_at_current(code, message);
6772    }
6773
6774    fn error_at_current(&mut self, code: ParseErrorCode, message: &'static str) {
6775        self.errors.push(ParseError {
6776            code,
6777            range: self.current_range(),
6778            message,
6779        });
6780    }
6781
6782    fn current_kind(&self) -> Option<SyntaxKind> {
6783        self.tokens.get(self.position).map(|token| token.kind)
6784    }
6785
6786    fn current_range(&self) -> TextRange {
6787        if let Some(token) = self.tokens.get(self.position) {
6788            return token.range;
6789        }
6790        let end = self
6791            .tokens
6792            .last()
6793            .map(|token| token.range.end())
6794            .unwrap_or_else(|| TextSize::from(0));
6795        TextRange::new(end, end)
6796    }
6797
6798    fn current_text(&self) -> Option<&'text str> {
6799        self.tokens.get(self.position).map(|token| token.text)
6800    }
6801
6802    fn current_dialect_at_rule_spec(&self) -> Option<AtRuleSpec> {
6803        let text = self.current_text()?;
6804        match self.dialect {
6805            StyleDialect::Scss | StyleDialect::Sass => scss_at_rule_spec(text),
6806            StyleDialect::Css | StyleDialect::Less => None,
6807        }
6808    }
6809
6810    fn current_is_css_module_value_rule(&self) -> bool {
6811        self.current_text() == Some("@value")
6812    }
6813
6814    fn next_kind(&self) -> Option<SyntaxKind> {
6815        self.tokens.get(self.position + 1).map(|token| token.kind)
6816    }
6817
6818    fn next_non_trivia_kind(&self) -> Option<SyntaxKind> {
6819        let mut index = self.position + 1;
6820        while let Some(token) = self.tokens.get(index) {
6821            if !token.kind.is_trivia() {
6822                return Some(token.kind);
6823            }
6824            index += 1;
6825        }
6826        None
6827    }
6828
6829    fn non_trivia_token_from(&self, mut index: usize) -> Option<(usize, SyntaxKind)> {
6830        while let Some(token) = self.tokens.get(index) {
6831            if !token.kind.is_trivia() {
6832                return Some((index, token.kind));
6833            }
6834            index += 1;
6835        }
6836        None
6837    }
6838
6839    fn non_trivia_token_after_interpolation(
6840        &self,
6841        mut index: usize,
6842        start_kind: SyntaxKind,
6843    ) -> Option<(usize, SyntaxKind)> {
6844        let end_kind = interpolation_end_kind(start_kind)?;
6845        index += 1;
6846        while let Some(token) = self.tokens.get(index) {
6847            if token.kind == end_kind {
6848                return self.non_trivia_token_from(index + 1);
6849            }
6850            if is_at_rule_prelude_boundary(token.kind) {
6851                return None;
6852            }
6853            index += 1;
6854        }
6855        None
6856    }
6857
6858    fn current_starts_namespace_qualified_selector(&self, kind: SyntaxKind) -> bool {
6859        match kind {
6860            SyntaxKind::Ident | SyntaxKind::Star => {
6861                self.next_kind() == Some(SyntaxKind::Pipe)
6862                    && self
6863                        .tokens
6864                        .get(self.position + 2)
6865                        .is_some_and(|token| namespace_selector_target_can_start(token.kind))
6866            }
6867            SyntaxKind::Pipe => self
6868                .tokens
6869                .get(self.position + 1)
6870                .is_some_and(|token| namespace_selector_target_can_start(token.kind)),
6871            _ => false,
6872        }
6873    }
6874
6875    fn namespace_qualified_selector_target_kind(&self) -> Option<SyntaxKind> {
6876        let target_index = if self.current_kind() == Some(SyntaxKind::Pipe) {
6877            self.position + 1
6878        } else {
6879            self.position + 2
6880        };
6881        self.tokens.get(target_index).map(|token| token.kind)
6882    }
6883
6884    fn at_end(&self) -> bool {
6885        self.position >= self.tokens.len()
6886    }
6887}
6888
6889impl<'text, 'extension, E> Tokenizer<'text, 'extension, E>
6890where
6891    E: DialectExtension,
6892{
6893    fn new(text: &'text str, extension: &'extension E) -> Self {
6894        Self {
6895            text,
6896            extension,
6897            offset: 0,
6898            scss_interpolation_depth: 0,
6899            less_interpolation_depth: 0,
6900            sass_indent_stack: vec![0],
6901            tokens: Vec::new(),
6902            errors: Vec::new(),
6903        }
6904    }
6905
6906    fn tokenize(&mut self) {
6907        while let Some(current) = self.current_char() {
6908            let start = self.offset;
6909            match current {
6910                '\u{feff}' if start == 0 => self.bump_current(),
6911                '\r' | '\n' if self.extension.dialect() == StyleDialect::Sass => {
6912                    self.consume_sass_indented_newline(start)
6913                }
6914                char if char.is_whitespace() => {
6915                    self.consume_while(SyntaxKind::Whitespace, |c| c.is_whitespace())
6916                }
6917                '/' if self.starts_with("/*") => self.consume_block_comment(),
6918                '/' if self.starts_with("//") && self.extension.dialect() != StyleDialect::Css => {
6919                    self.consume_line_comment()
6920                }
6921                '#' if self.starts_with("#{") && self.supports_scss_interpolation() => {
6922                    self.consume_scss_interpolation_start(start)
6923                }
6924                '@' if self.starts_with("@{") && self.supports_less_interpolation() => {
6925                    self.consume_less_interpolation_start(start)
6926                }
6927                '!' if self.starts_with_ascii_keyword("!important") => {
6928                    self.consume_static(SyntaxKind::Important, start, "!important".len())
6929                }
6930                '<' if self.starts_with("<!--") => {
6931                    self.consume_static(SyntaxKind::Cdo, start, "<!--".len())
6932                }
6933                '-' if self.starts_with("-->") => {
6934                    self.consume_static(SyntaxKind::Cdc, start, "-->".len())
6935                }
6936                '"' | '\'' => self.consume_string(current),
6937                'u' | 'U' if self.starts_unicode_range() => self.consume_unicode_range(),
6938                '0'..='9' => self.consume_number(),
6939                '$' if matches!(
6940                    self.extension.dialect(),
6941                    StyleDialect::Scss | StyleDialect::Sass
6942                ) =>
6943                {
6944                    self.consume_prefixed_name(SyntaxKind::ScssVariable)
6945                }
6946                '@' if self.extension.dialect() == StyleDialect::Less => {
6947                    self.consume_less_at_name()
6948                }
6949                '@' => self.consume_at_keyword(),
6950                '!' => self.consume_static(SyntaxKind::Delim, start, 1),
6951                '.' if self.current_starts_number() => self.consume_number(),
6952                '.' => self.consume_static(SyntaxKind::Dot, start, 1),
6953                ',' => self.consume_static(SyntaxKind::Comma, start, 1),
6954                ':' if self.starts_with("::") => {
6955                    self.consume_static(SyntaxKind::DoubleColon, start, 2)
6956                }
6957                ':' => self.consume_static(SyntaxKind::Colon, start, 1),
6958                ';' => self.consume_static(SyntaxKind::Semicolon, start, 1),
6959                '{' => self.consume_static(SyntaxKind::LeftBrace, start, 1),
6960                '}' if self.scss_interpolation_depth > 0 => {
6961                    self.consume_scss_interpolation_end(start)
6962                }
6963                '}' if self.less_interpolation_depth > 0 => {
6964                    self.consume_less_interpolation_end(start)
6965                }
6966                '}' => self.consume_static(SyntaxKind::RightBrace, start, 1),
6967                '(' => self.consume_static(SyntaxKind::LeftParen, start, 1),
6968                ')' => self.consume_static(SyntaxKind::RightParen, start, 1),
6969                '[' => self.consume_static(SyntaxKind::LeftBracket, start, 1),
6970                ']' => self.consume_static(SyntaxKind::RightBracket, start, 1),
6971                '+' if self.starts_with("+=") => {
6972                    self.consume_static(SyntaxKind::PlusEquals, start, 2)
6973                }
6974                '+' if self.current_starts_number() => self.consume_number(),
6975                '+' => self.consume_static(SyntaxKind::Plus, start, 1),
6976                '-' if self.starts_with("-=") => {
6977                    self.consume_static(SyntaxKind::MinusEquals, start, 2)
6978                }
6979                '-' if self.current_starts_number() => self.consume_number(),
6980                '-' if self.current_starts_ident_sequence() => self.consume_ident_like(),
6981                '-' => self.consume_static(SyntaxKind::Minus, start, 1),
6982                '*' if self.starts_with("*=") => {
6983                    self.consume_static(SyntaxKind::SubstringMatch, start, 2)
6984                }
6985                '*' => self.consume_static(SyntaxKind::Star, start, 1),
6986                '/' if self.starts_with("/=") => {
6987                    self.consume_static(SyntaxKind::SlashEquals, start, 2)
6988                }
6989                '/' => self.consume_static(SyntaxKind::Slash, start, 1),
6990                '%' if self.starts_scss_placeholder() => {
6991                    self.consume_prefixed_name(SyntaxKind::ScssPlaceholder)
6992                }
6993                '%' => self.consume_static(SyntaxKind::Percent, start, 1),
6994                '=' if self.starts_with("=>") => self.consume_static(SyntaxKind::Arrow, start, 2),
6995                '=' => self.consume_static(SyntaxKind::Equals, start, 1),
6996                '~' if self.starts_less_escaped_string() => self.consume_less_escaped_string(start),
6997                '~' if self.starts_with("~=") => {
6998                    self.consume_static(SyntaxKind::IncludesMatch, start, 2)
6999                }
7000                '~' => self.consume_static(SyntaxKind::Tilde, start, 1),
7001                '|' if self.starts_with("|=") => {
7002                    self.consume_static(SyntaxKind::DashMatch, start, 2)
7003                }
7004                '|' if self.starts_with("||") => {
7005                    self.consume_static(SyntaxKind::ColumnCombinator, start, 2)
7006                }
7007                '|' => self.consume_static(SyntaxKind::Pipe, start, 1),
7008                '^' if self.starts_with("^=") => {
7009                    self.consume_static(SyntaxKind::PrefixMatch, start, 2)
7010                }
7011                '^' => self.consume_static(SyntaxKind::Caret, start, 1),
7012                '$' if self.starts_with("$=") => {
7013                    self.consume_static(SyntaxKind::SuffixMatch, start, 2)
7014                }
7015                '$' if self.starts_less_property_variable() => {
7016                    self.consume_prefixed_name(SyntaxKind::LessPropertyVariableToken)
7017                }
7018                '&' if self.starts_with("&&") => {
7019                    self.consume_static(SyntaxKind::DoubleAmpersand, start, 2)
7020                }
7021                '&' => self.consume_static(SyntaxKind::Ampersand, start, 1),
7022                '>' => self.consume_static(SyntaxKind::GreaterThan, start, 1),
7023                '<' => self.consume_static(SyntaxKind::LessThan, start, 1),
7024                '#' if self.current_hash_starts_name() => self.consume_name_like(SyntaxKind::Hash),
7025                '#' => self.consume_static(SyntaxKind::Delim, start, 1),
7026                '\\' if self.current_starts_valid_escape() => {
7027                    self.consume_name_like(SyntaxKind::Ident)
7028                }
7029                char if is_name_start(char) => self.consume_ident_like(),
7030                char => self.consume_unexpected(char),
7031            }
7032        }
7033        self.consume_pending_sass_dedents();
7034    }
7035
7036    fn consume_static(&mut self, kind: SyntaxKind, start: usize, byte_len: usize) {
7037        self.offset += byte_len;
7038        self.push(kind, start, self.offset);
7039    }
7040
7041    fn consume_while(&mut self, kind: SyntaxKind, predicate: impl Fn(char) -> bool) {
7042        let start = self.offset;
7043        while let Some(char) = self.current_char() {
7044            if !predicate(char) {
7045                break;
7046            }
7047            self.bump_char(char);
7048        }
7049        self.push(kind, start, self.offset);
7050    }
7051
7052    fn consume_block_comment(&mut self) {
7053        let start = self.offset;
7054        self.offset += 2;
7055        while self.offset < self.text.len() {
7056            if self.starts_with("*/") {
7057                self.offset += 2;
7058                self.push(SyntaxKind::BlockComment, start, self.offset);
7059                return;
7060            }
7061            match self.current_char() {
7062                Some(char) => self.bump_char(char),
7063                None => break,
7064            }
7065        }
7066        self.push(SyntaxKind::BlockComment, start, self.offset);
7067        self.error(
7068            ParseErrorCode::UnterminatedBlockComment,
7069            start,
7070            self.offset,
7071            "unterminated block comment",
7072        );
7073    }
7074
7075    fn consume_line_comment(&mut self) {
7076        let start = self.offset;
7077        while let Some(char) = self.current_char() {
7078            if char == '\n' {
7079                break;
7080            }
7081            if char == '\r' {
7082                break;
7083            }
7084            self.bump_char(char);
7085        }
7086        self.push(SyntaxKind::LineComment, start, self.offset);
7087    }
7088
7089    fn consume_sass_indented_newline(&mut self, start: usize) {
7090        self.consume_line_break();
7091        let indent = self.consume_sass_line_indent();
7092        let line_start = self.offset;
7093        let current_indent = self.sass_indent_stack.last().copied().unwrap_or(0);
7094
7095        if indent > current_indent {
7096            self.push(SyntaxKind::SassIndentedNewline, start, line_start);
7097            self.sass_indent_stack.push(indent);
7098            self.push(SyntaxKind::SassIndent, line_start, line_start);
7099            return;
7100        }
7101
7102        if self.previous_significant_sass_token_can_end_statement() {
7103            self.push(SyntaxKind::SassOptionalSemicolon, start, start);
7104        }
7105        self.push(SyntaxKind::SassIndentedNewline, start, line_start);
7106
7107        while self.sass_indent_stack.len() > 1
7108            && self
7109                .sass_indent_stack
7110                .last()
7111                .is_some_and(|current| indent < *current)
7112        {
7113            self.sass_indent_stack.pop();
7114            self.push(SyntaxKind::SassDedent, line_start, line_start);
7115        }
7116
7117        if self
7118            .sass_indent_stack
7119            .last()
7120            .is_some_and(|current| indent != *current)
7121        {
7122            self.error(
7123                ParseErrorCode::UnexpectedCharacter,
7124                line_start,
7125                line_start,
7126                "inconsistent Sass indentation",
7127            );
7128        }
7129    }
7130
7131    fn consume_line_break(&mut self) {
7132        if self.starts_with("\r\n") {
7133            self.offset += "\r\n".len();
7134            return;
7135        }
7136        if let Some(char @ ('\r' | '\n')) = self.current_char() {
7137            self.bump_char(char);
7138        }
7139    }
7140
7141    fn consume_sass_line_indent(&mut self) -> usize {
7142        let mut indent = 0usize;
7143        while let Some(char) = self.current_char() {
7144            match char {
7145                ' ' => {
7146                    indent += 1;
7147                    self.bump_char(char);
7148                }
7149                '\t' => {
7150                    indent += 4;
7151                    self.bump_char(char);
7152                }
7153                _ => break,
7154            }
7155        }
7156        indent
7157    }
7158
7159    fn consume_pending_sass_dedents(&mut self) {
7160        if self.extension.dialect() != StyleDialect::Sass {
7161            return;
7162        }
7163        while self.sass_indent_stack.len() > 1 {
7164            self.sass_indent_stack.pop();
7165            self.push(SyntaxKind::SassDedent, self.offset, self.offset);
7166        }
7167    }
7168
7169    fn previous_significant_sass_token_can_end_statement(&self) -> bool {
7170        self.tokens
7171            .iter()
7172            .rev()
7173            .find(|token| !token.kind.is_trivia())
7174            .is_some_and(|token| sass_token_can_end_statement(token.kind))
7175    }
7176
7177    fn consume_scss_interpolation_start(&mut self, start: usize) {
7178        self.offset += "#{".len();
7179        self.scss_interpolation_depth += 1;
7180        self.push(SyntaxKind::ScssInterpolationStart, start, self.offset);
7181    }
7182
7183    fn consume_scss_interpolation_end(&mut self, start: usize) {
7184        self.offset += '}'.len_utf8();
7185        self.scss_interpolation_depth = self.scss_interpolation_depth.saturating_sub(1);
7186        self.push(SyntaxKind::ScssInterpolationEnd, start, self.offset);
7187    }
7188
7189    fn consume_less_interpolation_start(&mut self, start: usize) {
7190        self.offset += "@{".len();
7191        self.less_interpolation_depth += 1;
7192        self.push(SyntaxKind::LessInterpolationStart, start, self.offset);
7193    }
7194
7195    fn consume_less_interpolation_end(&mut self, start: usize) {
7196        self.offset += '}'.len_utf8();
7197        self.less_interpolation_depth = self.less_interpolation_depth.saturating_sub(1);
7198        self.push(SyntaxKind::LessInterpolationEnd, start, self.offset);
7199    }
7200
7201    fn consume_string(&mut self, quote: char) {
7202        let start = self.offset;
7203        self.bump_char(quote);
7204        while let Some(char) = self.current_char() {
7205            self.bump_char(char);
7206            if matches!(char, '\n' | '\r' | '\u{000c}') {
7207                self.push(SyntaxKind::BadString, start, self.offset);
7208                self.error(
7209                    ParseErrorCode::UnterminatedString,
7210                    start,
7211                    self.offset,
7212                    "unterminated string",
7213                );
7214                return;
7215            }
7216            if char == quote {
7217                self.push(SyntaxKind::String, start, self.offset);
7218                return;
7219            }
7220            if char == '\\'
7221                && let Some(escaped) = self.current_char()
7222            {
7223                self.bump_char(escaped);
7224            }
7225        }
7226        self.push(SyntaxKind::BadString, start, self.offset);
7227        self.error(
7228            ParseErrorCode::UnterminatedString,
7229            start,
7230            self.offset,
7231            "unterminated string",
7232        );
7233    }
7234
7235    fn consume_less_escaped_string(&mut self, start: usize) {
7236        self.offset += '~'.len_utf8();
7237        let Some(quote @ ('"' | '\'')) = self.current_char() else {
7238            self.push(SyntaxKind::Tilde, start, self.offset);
7239            return;
7240        };
7241        self.bump_char(quote);
7242        while let Some(char) = self.current_char() {
7243            self.bump_char(char);
7244            if matches!(char, '\n' | '\r' | '\u{000c}') {
7245                self.push(SyntaxKind::BadString, start, self.offset);
7246                self.error(
7247                    ParseErrorCode::UnterminatedString,
7248                    start,
7249                    self.offset,
7250                    "unterminated Less escaped string",
7251                );
7252                return;
7253            }
7254            if char == quote {
7255                self.push(SyntaxKind::LessEscapedString, start, self.offset);
7256                return;
7257            }
7258            if char == '\\'
7259                && let Some(escaped) = self.current_char()
7260            {
7261                self.bump_char(escaped);
7262            }
7263        }
7264        self.push(SyntaxKind::BadString, start, self.offset);
7265        self.error(
7266            ParseErrorCode::UnterminatedString,
7267            start,
7268            self.offset,
7269            "unterminated Less escaped string",
7270        );
7271    }
7272
7273    fn consume_number(&mut self) {
7274        let start = self.offset;
7275        if matches!(self.current_char(), Some('+' | '-')) {
7276            self.bump_current();
7277        }
7278        self.consume_digits();
7279        if self.current_char() == Some('.') && self.char_after_current_is_ascii_digit() {
7280            self.bump_current();
7281            self.consume_digits();
7282        }
7283        if self.current_starts_number_exponent() {
7284            self.bump_current();
7285            if matches!(self.current_char(), Some('+' | '-')) {
7286                self.bump_current();
7287            }
7288            self.consume_digits();
7289        }
7290        if self.current_char() == Some('%') {
7291            self.offset += 1;
7292            self.push(SyntaxKind::Percentage, start, self.offset);
7293            return;
7294        }
7295        if self.current_starts_ident_sequence() {
7296            self.consume_name_continue_sequence();
7297            self.push(SyntaxKind::Dimension, start, self.offset);
7298            return;
7299        }
7300        self.push(SyntaxKind::Number, start, self.offset);
7301    }
7302
7303    fn consume_unicode_range(&mut self) {
7304        let start = self.offset;
7305        self.bump_current();
7306        self.offset += '+'.len_utf8();
7307        self.consume_unicode_range_codepoints(true);
7308        if self.current_char() == Some('-') && self.next_char_is_hex_digit() {
7309            self.bump_current();
7310            self.consume_unicode_range_codepoints(false);
7311        }
7312        self.push(SyntaxKind::UnicodeRange, start, self.offset);
7313    }
7314
7315    fn consume_unicode_range_codepoints(&mut self, allow_question_mark: bool) {
7316        let mut consumed = 0usize;
7317        while consumed < 6 {
7318            match self.current_char() {
7319                Some(char) if char.is_ascii_hexdigit() => {
7320                    self.bump_char(char);
7321                    consumed += 1;
7322                }
7323                Some('?') if allow_question_mark => {
7324                    self.bump_current();
7325                    consumed += 1;
7326                }
7327                _ => break,
7328            }
7329        }
7330    }
7331
7332    fn consume_digits(&mut self) {
7333        while matches!(self.current_char(), Some('0'..='9')) {
7334            self.offset += 1;
7335        }
7336    }
7337
7338    fn consume_prefixed_name(&mut self, preferred_kind: SyntaxKind) {
7339        let start = self.offset;
7340        self.bump_current();
7341        while matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7342            self.bump_current();
7343        }
7344        let text = &self.text[start..self.offset];
7345        let kind = self
7346            .extension
7347            .classify_variable_token(text)
7348            .unwrap_or(preferred_kind);
7349        self.push(kind, start, self.offset);
7350    }
7351
7352    fn consume_less_at_name(&mut self) {
7353        let start = self.offset;
7354        self.bump_current();
7355        while matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7356            self.bump_current();
7357        }
7358        let text = &self.text[start..self.offset];
7359        let kind = if is_css_at_rule_name(text) {
7360            SyntaxKind::AtKeyword
7361        } else {
7362            self.extension
7363                .classify_variable_token(text)
7364                .unwrap_or(SyntaxKind::LessVariable)
7365        };
7366        self.push(kind, start, self.offset);
7367    }
7368
7369    fn consume_at_keyword(&mut self) {
7370        let start = self.offset;
7371        self.bump_current();
7372        while matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7373            self.bump_current();
7374        }
7375        self.push(SyntaxKind::AtKeyword, start, self.offset);
7376    }
7377
7378    fn consume_name_like(&mut self, kind: SyntaxKind) {
7379        let start = self.offset;
7380        self.consume_name_start();
7381        self.consume_name_continue_sequence();
7382        self.push(kind, start, self.offset);
7383    }
7384
7385    fn consume_ident_like(&mut self) {
7386        let start = self.offset;
7387        self.consume_name_continue_sequence();
7388        let ident = &self.text[start..self.offset];
7389        if ident.eq_ignore_ascii_case("url")
7390            && self.current_char() == Some('(')
7391            && !self.url_starts_with_quoted_argument()
7392        {
7393            self.consume_url_token(start);
7394            return;
7395        }
7396        let kind = if is_custom_property_name_text(ident) {
7397            SyntaxKind::CustomPropertyName
7398        } else {
7399            SyntaxKind::Ident
7400        };
7401        self.push(kind, start, self.offset);
7402    }
7403
7404    fn consume_name_start(&mut self) {
7405        if self.current_starts_valid_escape() {
7406            self.consume_name_escape();
7407        } else {
7408            self.bump_current();
7409        }
7410    }
7411
7412    fn consume_name_continue_sequence(&mut self) {
7413        loop {
7414            if self.current_starts_valid_escape() {
7415                self.consume_name_escape();
7416            } else if matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7417                self.bump_current();
7418            } else {
7419                break;
7420            }
7421        }
7422    }
7423
7424    fn consume_name_escape(&mut self) {
7425        self.bump_current();
7426        let mut hex_digits = 0usize;
7427        while hex_digits < 6
7428            && matches!(self.current_char(), Some(char) if char.is_ascii_hexdigit())
7429        {
7430            self.bump_current();
7431            hex_digits += 1;
7432        }
7433        if hex_digits > 0 {
7434            if matches!(self.current_char(), Some(char) if char.is_whitespace()) {
7435                self.bump_current();
7436            }
7437        } else if self.current_char().is_some() {
7438            self.bump_current();
7439        }
7440    }
7441
7442    fn consume_url_token(&mut self, start: usize) {
7443        self.bump_current();
7444        while matches!(self.current_char(), Some(char) if char.is_whitespace()) {
7445            self.bump_current();
7446        }
7447        while let Some(char) = self.current_char() {
7448            match char {
7449                ')' => {
7450                    self.bump_current();
7451                    self.push(SyntaxKind::Url, start, self.offset);
7452                    return;
7453                }
7454                char if char.is_whitespace() => {
7455                    self.bump_current();
7456                    while matches!(self.current_char(), Some(char) if char.is_whitespace()) {
7457                        self.bump_current();
7458                    }
7459                    if self.current_char() == Some(')') {
7460                        self.bump_current();
7461                        self.push(SyntaxKind::Url, start, self.offset);
7462                        return;
7463                    }
7464                    self.consume_bad_url(start);
7465                    return;
7466                }
7467                '"' | '\'' | '(' => {
7468                    self.consume_bad_url(start);
7469                    return;
7470                }
7471                '\\' if self.current_starts_valid_escape() => {
7472                    self.consume_name_escape();
7473                }
7474                '\\' => {
7475                    self.consume_bad_url(start);
7476                    return;
7477                }
7478                char if is_non_printable_code_point(char) => {
7479                    self.consume_bad_url(start);
7480                    return;
7481                }
7482                _ => self.bump_current(),
7483            }
7484        }
7485        self.push(SyntaxKind::BadUrl, start, self.offset);
7486        self.error(
7487            ParseErrorCode::UnexpectedCharacter,
7488            start,
7489            self.offset,
7490            "unterminated url token",
7491        );
7492    }
7493
7494    fn consume_bad_url(&mut self, start: usize) {
7495        while let Some(char) = self.current_char() {
7496            if char == ')' {
7497                self.bump_current();
7498                break;
7499            }
7500            if self.current_starts_valid_escape() {
7501                self.consume_name_escape();
7502            } else {
7503                self.bump_current();
7504            }
7505        }
7506        self.push(SyntaxKind::BadUrl, start, self.offset);
7507        self.error(
7508            ParseErrorCode::UnexpectedCharacter,
7509            start,
7510            self.offset,
7511            "bad url token",
7512        );
7513    }
7514
7515    fn url_starts_with_quoted_argument(&self) -> bool {
7516        let Some(mut rest) = self.text.get(self.offset + '('.len_utf8()..) else {
7517            return false;
7518        };
7519        rest = rest.trim_start_matches(char::is_whitespace);
7520        matches!(rest.chars().next(), Some('"' | '\''))
7521    }
7522
7523    fn starts_less_property_variable(&self) -> bool {
7524        self.extension.dialect() == StyleDialect::Less
7525            && self.text[self.offset + '$'.len_utf8()..]
7526                .chars()
7527                .next()
7528                .is_some_and(is_name_start)
7529    }
7530
7531    fn starts_scss_placeholder(&self) -> bool {
7532        matches!(
7533            self.extension.dialect(),
7534            StyleDialect::Scss | StyleDialect::Sass
7535        ) && self.text[self.offset + '%'.len_utf8()..]
7536            .chars()
7537            .next()
7538            .is_some_and(is_name_start)
7539    }
7540
7541    fn current_hash_starts_name(&self) -> bool {
7542        if self.current_char() != Some('#') {
7543            return false;
7544        }
7545        let next_offset = self.offset + '#'.len_utf8();
7546        self.text[next_offset..]
7547            .chars()
7548            .next()
7549            .is_some_and(is_name_continue)
7550            || self.escape_starts_at(next_offset)
7551    }
7552
7553    fn consume_unexpected(&mut self, char: char) {
7554        let start = self.offset;
7555        self.bump_char(char);
7556        self.push(SyntaxKind::Delim, start, self.offset);
7557        self.error(
7558            ParseErrorCode::UnexpectedCharacter,
7559            start,
7560            self.offset,
7561            "unexpected character",
7562        );
7563    }
7564
7565    fn push(&mut self, kind: SyntaxKind, start: usize, end: usize) {
7566        self.tokens.push(Token {
7567            kind,
7568            text: &self.text[start..end],
7569            range: text_range(start, end),
7570        });
7571    }
7572
7573    fn error(&mut self, code: ParseErrorCode, start: usize, end: usize, message: &'static str) {
7574        self.errors.push(ParseError {
7575            code,
7576            range: text_range(start, end),
7577            message,
7578        });
7579    }
7580
7581    fn starts_with(&self, pattern: &str) -> bool {
7582        self.text[self.offset..].starts_with(pattern)
7583    }
7584
7585    fn current_starts_valid_escape(&self) -> bool {
7586        self.escape_starts_at(self.offset)
7587    }
7588
7589    fn current_starts_number(&self) -> bool {
7590        self.starts_number_at(self.offset)
7591    }
7592
7593    fn current_starts_number_exponent(&self) -> bool {
7594        let Some('e' | 'E') = self.current_char() else {
7595            return false;
7596        };
7597        let exponent_offset = self.offset + 'e'.len_utf8();
7598        self.char_at(exponent_offset)
7599            .is_some_and(|char| char.is_ascii_digit())
7600            || (matches!(self.char_at(exponent_offset), Some('+' | '-'))
7601                && self.char_after_offset_is_ascii_digit(exponent_offset))
7602    }
7603
7604    fn starts_number_at(&self, offset: usize) -> bool {
7605        let Some(first) = self.char_at(offset) else {
7606            return false;
7607        };
7608        let second_offset = offset + first.len_utf8();
7609        match first {
7610            '+' | '-' => {
7611                self.char_at(second_offset)
7612                    .is_some_and(|char| char.is_ascii_digit())
7613                    || (self.char_at(second_offset) == Some('.')
7614                        && self.char_after_offset_is_ascii_digit(second_offset))
7615            }
7616            '.' => self.char_after_offset_is_ascii_digit(offset),
7617            char => char.is_ascii_digit(),
7618        }
7619    }
7620
7621    fn current_starts_ident_sequence(&self) -> bool {
7622        self.starts_ident_sequence_at(self.offset)
7623    }
7624
7625    fn starts_ident_sequence_at(&self, offset: usize) -> bool {
7626        let Some(first) = self.char_at(offset) else {
7627            return false;
7628        };
7629        let second_offset = offset + first.len_utf8();
7630        match first {
7631            '-' => {
7632                self.char_at(second_offset)
7633                    .is_some_and(|char| char == '-' || is_name_start(char))
7634                    || self.escape_starts_at(second_offset)
7635            }
7636            '\\' => self.escape_starts_at(offset),
7637            char => is_name_start(char),
7638        }
7639    }
7640
7641    fn escape_starts_at(&self, offset: usize) -> bool {
7642        if !self
7643            .text
7644            .get(offset..)
7645            .is_some_and(|remaining| remaining.starts_with('\\'))
7646        {
7647            return false;
7648        }
7649        self.text[offset + '\\'.len_utf8()..]
7650            .chars()
7651            .next()
7652            .is_some_and(|char| !matches!(char, '\n' | '\r' | '\u{000c}'))
7653    }
7654
7655    fn char_at(&self, offset: usize) -> Option<char> {
7656        self.text.get(offset..)?.chars().next()
7657    }
7658
7659    fn char_after_current_is_ascii_digit(&self) -> bool {
7660        self.char_after_offset_is_ascii_digit(self.offset)
7661    }
7662
7663    fn char_after_offset_is_ascii_digit(&self, offset: usize) -> bool {
7664        let Some(char) = self.char_at(offset) else {
7665            return false;
7666        };
7667        self.char_at(offset + char.len_utf8())
7668            .is_some_and(|char| char.is_ascii_digit())
7669    }
7670
7671    fn starts_with_ascii_keyword(&self, keyword: &str) -> bool {
7672        let remaining = &self.text[self.offset..];
7673        let Some(prefix) = remaining.get(..keyword.len()) else {
7674            return false;
7675        };
7676        if !prefix.eq_ignore_ascii_case(keyword) {
7677            return false;
7678        }
7679        remaining[keyword.len()..]
7680            .chars()
7681            .next()
7682            .is_none_or(|char| !is_name_continue(char))
7683    }
7684
7685    fn supports_scss_interpolation(&self) -> bool {
7686        matches!(
7687            self.extension.dialect(),
7688            StyleDialect::Scss | StyleDialect::Sass
7689        )
7690    }
7691
7692    fn supports_less_interpolation(&self) -> bool {
7693        self.extension.dialect() == StyleDialect::Less
7694    }
7695
7696    fn starts_less_escaped_string(&self) -> bool {
7697        self.extension.dialect() == StyleDialect::Less
7698            && (self.starts_with("~\"") || self.starts_with("~'"))
7699    }
7700
7701    fn starts_unicode_range(&self) -> bool {
7702        let mut chars = self.text[self.offset..].chars();
7703        matches!(chars.next(), Some('u' | 'U'))
7704            && chars.next() == Some('+')
7705            && chars
7706                .next()
7707                .is_some_and(|char| char.is_ascii_hexdigit() || char == '?')
7708    }
7709
7710    fn current_char(&self) -> Option<char> {
7711        self.text[self.offset..].chars().next()
7712    }
7713
7714    fn next_char_is_hex_digit(&self) -> bool {
7715        let offset = self.offset + '-'.len_utf8();
7716        self.text
7717            .get(offset..)
7718            .and_then(|tail| tail.chars().next())
7719            .is_some_and(|char| char.is_ascii_hexdigit())
7720    }
7721
7722    fn bump_current(&mut self) {
7723        if let Some(char) = self.current_char() {
7724            self.bump_char(char);
7725        }
7726    }
7727
7728    fn bump_char(&mut self, char: char) {
7729        self.offset += char.len_utf8();
7730    }
7731}
7732
7733fn public_token_text(text: &str) -> String {
7734    text.chars()
7735        .map(css_syntax_preprocessed_char)
7736        .collect::<String>()
7737}
7738
7739fn css_syntax_preprocessed_char(char: char) -> char {
7740    if char == '\0' { '\u{fffd}' } else { char }
7741}
7742
7743fn is_name_start(char: char) -> bool {
7744    let char = css_syntax_preprocessed_char(char);
7745    char == '_' || char == '-' || char.is_alphabetic() || !char.is_ascii()
7746}
7747
7748fn is_name_continue(char: char) -> bool {
7749    is_name_start(char) || char.is_ascii_digit()
7750}
7751
7752fn is_non_printable_code_point(char: char) -> bool {
7753    let char = css_syntax_preprocessed_char(char);
7754    matches!(char, '\u{0000}'..='\u{0008}' | '\u{000b}' | '\u{000e}'..='\u{001f}' | '\u{007f}')
7755}
7756
7757fn is_custom_property_name_text(text: &str) -> bool {
7758    let Some(rest) = text.strip_prefix("--") else {
7759        return false;
7760    };
7761    let Some(first) = rest.chars().next() else {
7762        return false;
7763    };
7764    first == '-' || is_name_start(first) || starts_valid_escape_text(rest)
7765}
7766
7767fn starts_valid_escape_text(text: &str) -> bool {
7768    text.starts_with('\\')
7769        && text['\\'.len_utf8()..]
7770            .chars()
7771            .next()
7772            .is_some_and(|char| !matches!(char, '\n' | '\r' | '\u{000c}'))
7773}
7774
7775fn is_css_at_rule_name(text: &str) -> bool {
7776    matches_ignore_ascii_case(
7777        text,
7778        &[
7779            "@charset",
7780            "@container",
7781            "@font-face",
7782            "@font-feature-values",
7783            "@font-palette-values",
7784            "@import",
7785            "@keyframes",
7786            "@layer",
7787            "@media",
7788            "@namespace",
7789            "@page",
7790            "@property",
7791            "@scope",
7792            "@starting-style",
7793            "@supports",
7794            "@counter-style",
7795            "@custom-media",
7796            "@color-profile",
7797            "@nest",
7798            "@position-try",
7799            "@view-transition",
7800            "@stylistic",
7801            "@styleset",
7802            "@character-variant",
7803            "@swash",
7804            "@ornaments",
7805            "@annotation",
7806            "@historical-forms",
7807            "@when",
7808            "@else",
7809        ],
7810    )
7811}
7812
7813fn is_interpolation_start(kind: SyntaxKind) -> bool {
7814    matches!(
7815        kind,
7816        SyntaxKind::ScssInterpolationStart | SyntaxKind::LessInterpolationStart
7817    )
7818}
7819
7820fn is_component_value_atom_start(kind: SyntaxKind) -> bool {
7821    matches!(
7822        kind,
7823        SyntaxKind::Ident
7824            | SyntaxKind::CustomPropertyName
7825            | SyntaxKind::Number
7826            | SyntaxKind::Percentage
7827            | SyntaxKind::Dimension
7828            | SyntaxKind::String
7829            | SyntaxKind::LessEscapedString
7830            | SyntaxKind::UnicodeRange
7831            | SyntaxKind::Hash
7832            | SyntaxKind::Url
7833            | SyntaxKind::BadUrl
7834            | SyntaxKind::BadString
7835            | SyntaxKind::Important
7836            | SyntaxKind::ScssVariable
7837            | SyntaxKind::LessVariable
7838            | SyntaxKind::LessPropertyVariableToken
7839            | SyntaxKind::ScssInterpolationStart
7840            | SyntaxKind::LessInterpolationStart
7841    )
7842}
7843
7844fn interpolation_end_kind(start_kind: SyntaxKind) -> Option<SyntaxKind> {
7845    match start_kind {
7846        SyntaxKind::ScssInterpolationStart => Some(SyntaxKind::ScssInterpolationEnd),
7847        SyntaxKind::LessInterpolationStart => Some(SyntaxKind::LessInterpolationEnd),
7848        _ => None,
7849    }
7850}
7851
7852#[derive(Debug, Clone, PartialEq, Eq)]
7853struct SelectorBranch {
7854    name: String,
7855    range: TextRange,
7856    bare_suffix_base: bool,
7857}
7858
7859fn collect_selector_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedSelectorFact> {
7860    let mut selectors = Vec::new();
7861    let mut seen = BTreeSet::new();
7862    collect_selector_facts_in_range(
7863        tokens,
7864        0,
7865        tokens.len(),
7866        &[],
7867        None,
7868        &mut seen,
7869        &mut selectors,
7870    );
7871    selectors
7872}
7873
7874fn collect_selector_facts_in_range(
7875    tokens: &[Token<'_>],
7876    start: usize,
7877    end: usize,
7878    parent_branches: &[SelectorBranch],
7879    css_module_scope: Option<&'static str>,
7880    seen: &mut BTreeSet<(ParsedSelectorFactKind, String, u32, u32)>,
7881    selectors: &mut Vec<ParsedSelectorFact>,
7882) {
7883    let mut index = start;
7884    while index < end {
7885        index = skip_trivia_tokens(tokens, index, end);
7886        if index >= end {
7887            break;
7888        }
7889
7890        if tokens[index].kind == SyntaxKind::AtKeyword {
7891            let block = find_block_after_header(tokens, index, end);
7892            if let Some((open, close)) = block {
7893                if tokens[index].text == "@nest" {
7894                    if css_module_scope == Some("global") {
7895                        collect_selector_facts_in_range(
7896                            tokens,
7897                            open + 1,
7898                            close,
7899                            &[],
7900                            css_module_scope,
7901                            seen,
7902                            selectors,
7903                        );
7904                    } else {
7905                        let branches =
7906                            resolve_selector_header(tokens, index + 1, open, parent_branches);
7907                        push_class_selector_facts_from_header(
7908                            selectors,
7909                            seen,
7910                            tokens,
7911                            index + 1,
7912                            open,
7913                        );
7914                        for branch in &branches {
7915                            push_selector_fact(
7916                                selectors,
7917                                seen,
7918                                ParsedSelectorFactKind::Class,
7919                                branch.name.clone(),
7920                                branch.range,
7921                            );
7922                        }
7923                        collect_selector_facts_in_range(
7924                            tokens,
7925                            open + 1,
7926                            close,
7927                            &branches,
7928                            css_module_scope,
7929                            seen,
7930                            selectors,
7931                        );
7932                    }
7933                } else if style_wrapper_at_rule(tokens[index].text) {
7934                    collect_selector_facts_in_range(
7935                        tokens,
7936                        open + 1,
7937                        close,
7938                        parent_branches,
7939                        css_module_scope,
7940                        seen,
7941                        selectors,
7942                    );
7943                }
7944                index = close + 1;
7945            } else {
7946                index = skip_statement(tokens, index, end);
7947            }
7948            continue;
7949        }
7950
7951        let Some((open, close)) = find_block_after_header(tokens, index, end) else {
7952            index = skip_statement(tokens, index, end);
7953            continue;
7954        };
7955
7956        let effective_scope = css_module_scope
7957            .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
7958        if effective_scope == Some("global") {
7959            collect_selector_facts_in_range(
7960                tokens,
7961                open + 1,
7962                close,
7963                &[],
7964                effective_scope,
7965                seen,
7966                selectors,
7967            );
7968        } else {
7969            let branches = resolve_selector_header(tokens, index, open, parent_branches);
7970            push_class_selector_facts_from_header(selectors, seen, tokens, index, open);
7971            for branch in &branches {
7972                push_selector_fact(
7973                    selectors,
7974                    seen,
7975                    ParsedSelectorFactKind::Class,
7976                    branch.name.clone(),
7977                    branch.range,
7978                );
7979            }
7980            for id in collect_id_selector_facts_from_header(tokens, index, open)
7981                .into_iter()
7982                .chain(collect_local_function_id_selector_facts_from_header(
7983                    tokens, index, open,
7984                ))
7985            {
7986                push_selector_fact(selectors, seen, ParsedSelectorFactKind::Id, id.0, id.1);
7987            }
7988            for placeholder in collect_placeholder_selector_facts_from_header(tokens, index, open) {
7989                push_selector_fact(
7990                    selectors,
7991                    seen,
7992                    ParsedSelectorFactKind::Placeholder,
7993                    placeholder.0,
7994                    placeholder.1,
7995                );
7996            }
7997
7998            collect_selector_facts_in_range(
7999                tokens,
8000                open + 1,
8001                close,
8002                &branches,
8003                effective_scope,
8004                seen,
8005                selectors,
8006            );
8007        }
8008        index = close + 1;
8009    }
8010}
8011
8012fn push_class_selector_facts_from_header(
8013    selectors: &mut Vec<ParsedSelectorFact>,
8014    seen: &mut BTreeSet<(ParsedSelectorFactKind, String, u32, u32)>,
8015    tokens: &[Token<'_>],
8016    start: usize,
8017    end: usize,
8018) {
8019    for (name, range) in collect_class_selector_names_from_header(tokens, start, end) {
8020        push_selector_fact(selectors, seen, ParsedSelectorFactKind::Class, name, range);
8021    }
8022}
8023
8024fn push_selector_fact(
8025    selectors: &mut Vec<ParsedSelectorFact>,
8026    seen: &mut BTreeSet<(ParsedSelectorFactKind, String, u32, u32)>,
8027    kind: ParsedSelectorFactKind,
8028    name: String,
8029    range: TextRange,
8030) {
8031    if seen.insert((
8032        kind,
8033        name.clone(),
8034        u32::from(range.start()),
8035        u32::from(range.end()),
8036    )) {
8037        selectors.push(ParsedSelectorFact { kind, name, range });
8038    }
8039}
8040
8041fn resolve_selector_header(
8042    tokens: &[Token<'_>],
8043    start: usize,
8044    end: usize,
8045    parent_branches: &[SelectorBranch],
8046) -> Vec<SelectorBranch> {
8047    split_selector_groups(tokens, start, end)
8048        .into_iter()
8049        .flat_map(|(group_start, group_end)| {
8050            resolve_selector_group(tokens, group_start, group_end, parent_branches)
8051        })
8052        .collect()
8053}
8054
8055fn resolve_selector_group(
8056    tokens: &[Token<'_>],
8057    start: usize,
8058    end: usize,
8059    parent_branches: &[SelectorBranch],
8060) -> Vec<SelectorBranch> {
8061    if let Some(mut local_names) = collect_local_function_selector_names(tokens, start, end) {
8062        local_names.extend(collect_class_selector_names_from_header(tokens, start, end));
8063        let bare_suffix_base = parent_branches.is_empty() && local_names.len() == 1;
8064        return local_names
8065            .into_iter()
8066            .map(|(name, range)| SelectorBranch {
8067                name,
8068                range,
8069                bare_suffix_base,
8070            })
8071            .collect();
8072    }
8073
8074    let (tail_start, tail_end) = selector_group_tail_range(tokens, start, end);
8075    let tail_start = skip_trivia_tokens(tokens, tail_start, tail_end);
8076
8077    if let Some((suffix, range)) = ampersand_suffix_selector(tokens, tail_start, tail_end) {
8078        let bases: Vec<&SelectorBranch> = if parent_branches.is_empty() {
8079            Vec::new()
8080        } else {
8081            parent_branches
8082                .iter()
8083                .filter(|parent| parent.bare_suffix_base)
8084                .collect()
8085        };
8086        return bases
8087            .into_iter()
8088            .map(|parent| SelectorBranch {
8089                name: format!("{}{}", parent.name, suffix),
8090                range,
8091                bare_suffix_base: parent.bare_suffix_base,
8092            })
8093            .collect();
8094    }
8095
8096    let class_names = collect_class_selector_names_from_header(tokens, tail_start, tail_end);
8097    if class_names.is_empty() {
8098        return Vec::new();
8099    }
8100
8101    let bare_suffix_base = parent_branches.is_empty()
8102        && class_names.len() == 1
8103        && is_bare_class_selector_group(tokens, tail_start, tail_end);
8104    class_names
8105        .into_iter()
8106        .map(|(name, range)| SelectorBranch {
8107            name,
8108            range,
8109            bare_suffix_base,
8110        })
8111        .collect()
8112}
8113
8114fn is_bare_class_selector_group(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
8115    let dot_index = skip_trivia_tokens(tokens, start, end);
8116    if tokens.get(dot_index).map(|token| token.kind) != Some(SyntaxKind::Dot) {
8117        return false;
8118    }
8119    let name_index = skip_trivia_tokens(tokens, dot_index + 1, end);
8120    if !tokens.get(name_index).is_some_and(|token| {
8121        matches!(
8122            token.kind,
8123            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
8124        )
8125    }) {
8126        return false;
8127    }
8128    skip_trivia_tokens(tokens, name_index + 1, end) >= end
8129}
8130
8131fn split_selector_groups(tokens: &[Token<'_>], start: usize, end: usize) -> Vec<(usize, usize)> {
8132    let mut groups = Vec::new();
8133    let mut group_start = start;
8134    let mut paren_depth = 0usize;
8135    let mut bracket_depth = 0usize;
8136    let mut index = start;
8137    while index < end {
8138        match tokens[index].kind {
8139            SyntaxKind::LeftParen => paren_depth += 1,
8140            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8141            SyntaxKind::LeftBracket => bracket_depth += 1,
8142            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8143            SyntaxKind::Comma if paren_depth == 0 && bracket_depth == 0 => {
8144                groups.push((group_start, index));
8145                group_start = index + 1;
8146            }
8147            _ => {}
8148        }
8149        index += 1;
8150    }
8151    groups.push((group_start, end));
8152    groups
8153}
8154
8155fn selector_group_tail_range(tokens: &[Token<'_>], start: usize, end: usize) -> (usize, usize) {
8156    let mut paren_depth = 0usize;
8157    let mut bracket_depth = 0usize;
8158    let mut tail_start = start;
8159    let mut index = start;
8160    while index < end {
8161        match tokens[index].kind {
8162            SyntaxKind::LeftParen => paren_depth += 1,
8163            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8164            SyntaxKind::LeftBracket => bracket_depth += 1,
8165            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8166            kind if paren_depth == 0 && bracket_depth == 0 && is_selector_combinator_kind(kind) => {
8167                tail_start = index + 1;
8168            }
8169            SyntaxKind::Whitespace if paren_depth == 0 && bracket_depth == 0 => {
8170                let previous = previous_non_trivia_token(tokens, start, index);
8171                let next = next_non_trivia_token_until(tokens, index + 1, end);
8172                if previous.is_some_and(|token| selector_component_can_end(token.kind))
8173                    && next.is_some_and(|token| selector_component_can_start(token.kind))
8174                {
8175                    tail_start = index + 1;
8176                }
8177            }
8178            _ => {}
8179        }
8180        index += 1;
8181    }
8182    (tail_start, end)
8183}
8184
8185fn ampersand_suffix_selector(
8186    tokens: &[Token<'_>],
8187    start: usize,
8188    end: usize,
8189) -> Option<(String, TextRange)> {
8190    let ampersand_index = skip_trivia_tokens(tokens, start, end);
8191    if tokens.get(ampersand_index)?.kind != SyntaxKind::Ampersand {
8192        return None;
8193    }
8194    let suffix = next_non_trivia_token_until(tokens, ampersand_index + 1, end)?;
8195    if matches!(
8196        suffix.kind,
8197        SyntaxKind::Ident | SyntaxKind::CustomPropertyName
8198    ) {
8199        return Some((suffix.text.to_string(), suffix.range));
8200    }
8201    None
8202}
8203
8204fn collect_class_selector_names_from_header(
8205    tokens: &[Token<'_>],
8206    start: usize,
8207    end: usize,
8208) -> Vec<(String, TextRange)> {
8209    let mut names = Vec::new();
8210    let mut index = start;
8211    let mut paren_depth = 0usize;
8212    let mut bracket_depth = 0usize;
8213    while index < end {
8214        match tokens[index].kind {
8215            SyntaxKind::LeftParen => paren_depth += 1,
8216            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8217            SyntaxKind::LeftBracket => bracket_depth += 1,
8218            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8219            _ => {}
8220        }
8221        if paren_depth == 0
8222            && bracket_depth == 0
8223            && tokens[index].kind == SyntaxKind::Dot
8224            && let Some(name) = next_non_trivia_token_until(tokens, index + 1, end)
8225            && matches!(
8226                name.kind,
8227                SyntaxKind::Ident | SyntaxKind::CustomPropertyName
8228            )
8229        {
8230            names.push((name.text.to_string(), name.range));
8231        }
8232        index += 1;
8233    }
8234    names
8235}
8236
8237fn collect_local_function_selector_names(
8238    tokens: &[Token<'_>],
8239    start: usize,
8240    end: usize,
8241) -> Option<Vec<(String, TextRange)>> {
8242    let colon_index = skip_trivia_tokens(tokens, start, end);
8243    if tokens.get(colon_index)?.kind != SyntaxKind::Colon {
8244        return None;
8245    }
8246    let ident = next_non_trivia_token_until(tokens, colon_index + 1, end)?;
8247    if ident.kind != SyntaxKind::Ident || ident.text != "local" {
8248        return None;
8249    }
8250    let open_index = skip_trivia_tokens(tokens, colon_index + 2, end);
8251    if tokens.get(open_index)?.kind != SyntaxKind::LeftParen {
8252        return None;
8253    }
8254    Some(collect_class_selector_names_from_header(
8255        tokens,
8256        open_index + 1,
8257        end.saturating_sub(1),
8258    ))
8259}
8260
8261fn collect_local_function_id_selector_facts_from_header(
8262    tokens: &[Token<'_>],
8263    start: usize,
8264    end: usize,
8265) -> Vec<(String, TextRange)> {
8266    let mut ids = Vec::new();
8267    let mut index = start;
8268    while index < end {
8269        if tokens[index].kind == SyntaxKind::Colon
8270            && let Some(scope) = next_non_trivia_token_until(tokens, index + 1, end)
8271            && scope.kind == SyntaxKind::Ident
8272            && scope.text == "local"
8273            && let Some(open) = next_non_trivia_token_after_range(tokens, scope.range, end)
8274            && open.kind == SyntaxKind::LeftParen
8275            && let Some(close) = matching_right_paren_from_range(tokens, open.range, end)
8276        {
8277            ids.extend(collect_id_selector_facts_from_header(
8278                tokens,
8279                token_index_by_range(tokens, open.range).map_or(index + 1, |value| value + 1),
8280                close,
8281            ));
8282            index = close.saturating_add(1);
8283            continue;
8284        }
8285        index += 1;
8286    }
8287    ids
8288}
8289
8290fn css_module_block_scope_marker_in_header(
8291    tokens: &[Token<'_>],
8292    start: usize,
8293    end: usize,
8294) -> Option<&'static str> {
8295    if next_non_trivia_token_until(tokens, start, end)
8296        .is_some_and(|token| token.kind == SyntaxKind::AtKeyword)
8297    {
8298        return None;
8299    }
8300
8301    css_module_scope_marker_after_colon(tokens, start, end)
8302        .filter(|_| !css_module_scope_marker_is_function(tokens, start, end))
8303}
8304
8305fn css_module_header_is_global_only(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
8306    if next_non_trivia_token_until(tokens, start, end)
8307        .is_some_and(|token| token.kind == SyntaxKind::AtKeyword)
8308    {
8309        return false;
8310    }
8311    css_module_header_contains_scope(tokens, start, end, "global")
8312        && collect_class_selector_names_from_header(tokens, start, end).is_empty()
8313        && collect_local_function_selector_names(tokens, start, end)
8314            .map(|names| names.is_empty())
8315            .unwrap_or(true)
8316}
8317
8318fn css_module_header_contains_scope(
8319    tokens: &[Token<'_>],
8320    start: usize,
8321    end: usize,
8322    expected_scope: &str,
8323) -> bool {
8324    let mut index = start;
8325    while index < end {
8326        if tokens[index].kind == SyntaxKind::Colon
8327            && let Some(scope) = next_non_trivia_token_until(tokens, index + 1, end)
8328            && scope.kind == SyntaxKind::Ident
8329            && scope.text == expected_scope
8330        {
8331            return true;
8332        }
8333        index += 1;
8334    }
8335    false
8336}
8337
8338fn css_module_scope_marker_after_colon(
8339    tokens: &[Token<'_>],
8340    start: usize,
8341    end: usize,
8342) -> Option<&'static str> {
8343    let colon = skip_trivia_tokens(tokens, start, end);
8344    if tokens.get(colon)?.kind != SyntaxKind::Colon {
8345        return None;
8346    }
8347    let scope = next_non_trivia_token_until(tokens, colon + 1, end)?;
8348    if scope.kind != SyntaxKind::Ident {
8349        return None;
8350    }
8351    match scope.text {
8352        "global" => Some("global"),
8353        "local" => Some("local"),
8354        _ => None,
8355    }
8356}
8357
8358fn css_module_scope_marker_is_function(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
8359    let colon = skip_trivia_tokens(tokens, start, end);
8360    let mut index = colon + 1;
8361    let Some(scope) = next_non_trivia_token_until(tokens, index, end) else {
8362        return false;
8363    };
8364    while index < end {
8365        if tokens[index].range == scope.range {
8366            break;
8367        }
8368        index += 1;
8369    }
8370    let Some(next) = next_non_trivia_token_until(tokens, index + 1, end) else {
8371        return false;
8372    };
8373    scope.kind == SyntaxKind::Ident && next.kind == SyntaxKind::LeftParen
8374}
8375
8376fn collect_id_selector_facts_from_header(
8377    tokens: &[Token<'_>],
8378    start: usize,
8379    end: usize,
8380) -> Vec<(String, TextRange)> {
8381    let mut names = Vec::new();
8382    let mut index = start;
8383    let mut paren_depth = 0usize;
8384    let mut bracket_depth = 0usize;
8385    while index < end {
8386        match tokens[index].kind {
8387            SyntaxKind::LeftParen => paren_depth += 1,
8388            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8389            SyntaxKind::LeftBracket => bracket_depth += 1,
8390            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8391            _ => {}
8392        }
8393        let token = tokens[index];
8394        if paren_depth == 0 && bracket_depth == 0 && token.kind == SyntaxKind::Hash {
8395            names.push((token.text.trim_start_matches('#').to_string(), token.range));
8396        }
8397        index += 1;
8398    }
8399    names
8400}
8401
8402fn collect_placeholder_selector_facts_from_header(
8403    tokens: &[Token<'_>],
8404    start: usize,
8405    end: usize,
8406) -> Vec<(String, TextRange)> {
8407    let mut names = Vec::new();
8408    let mut index = start;
8409    let mut paren_depth = 0usize;
8410    let mut bracket_depth = 0usize;
8411    while index < end {
8412        match tokens[index].kind {
8413            SyntaxKind::LeftParen => paren_depth += 1,
8414            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8415            SyntaxKind::LeftBracket => bracket_depth += 1,
8416            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8417            _ => {}
8418        }
8419        let token = tokens[index];
8420        if paren_depth == 0 && bracket_depth == 0 && token.kind == SyntaxKind::ScssPlaceholder {
8421            names.push((token.text.trim_start_matches('%').to_string(), token.range));
8422        }
8423        index += 1;
8424    }
8425    names
8426}
8427
8428fn collect_variable_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedVariableFact> {
8429    let mut variables = Vec::new();
8430    for (index, token) in tokens.iter().enumerate() {
8431        let kind = match token.kind {
8432            SyntaxKind::ScssVariable => {
8433                if scss_variable_token_is_declaration(tokens, index) {
8434                    ParsedVariableFactKind::ScssDeclaration
8435                } else {
8436                    ParsedVariableFactKind::ScssReference
8437                }
8438            }
8439            SyntaxKind::LessVariable => {
8440                if next_non_trivia_token(tokens, index + 1)
8441                    .is_some_and(|candidate| candidate.kind == SyntaxKind::Colon)
8442                {
8443                    ParsedVariableFactKind::LessDeclaration
8444                } else {
8445                    ParsedVariableFactKind::LessReference
8446                }
8447            }
8448            SyntaxKind::CustomPropertyName => {
8449                if previous_non_trivia_token(tokens, 0, index).is_some_and(|candidate| {
8450                    matches!(candidate.kind, SyntaxKind::Ampersand | SyntaxKind::Dot)
8451                }) {
8452                    continue;
8453                }
8454                if let Some(at_rule_name) = containing_at_rule_header_name(tokens, index) {
8455                    if at_rule_name == "@property" {
8456                        ParsedVariableFactKind::CustomPropertyDeclaration
8457                    } else {
8458                        continue;
8459                    }
8460                } else if next_non_trivia_token(tokens, index + 1)
8461                    .is_some_and(|candidate| candidate.kind == SyntaxKind::Colon)
8462                {
8463                    ParsedVariableFactKind::CustomPropertyDeclaration
8464                } else {
8465                    ParsedVariableFactKind::CustomPropertyReference
8466                }
8467            }
8468            _ => continue,
8469        };
8470        variables.push(ParsedVariableFact {
8471            kind,
8472            name: token.text.to_string(),
8473            range: token.range,
8474        });
8475    }
8476    variables
8477}
8478
8479fn scss_variable_token_is_declaration(tokens: &[Token<'_>], index: usize) -> bool {
8480    next_non_trivia_token(tokens, index + 1).is_some_and(|candidate| {
8481        candidate.kind == SyntaxKind::Colon
8482            || (matches!(candidate.kind, SyntaxKind::Comma | SyntaxKind::RightParen)
8483                && containing_at_rule_header_name(tokens, index).is_some_and(|name| {
8484                    name.eq_ignore_ascii_case("@mixin") || name.eq_ignore_ascii_case("@function")
8485                }))
8486    })
8487}
8488
8489fn collect_sass_symbol_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedSassSymbolFact> {
8490    let declared_functions = collect_sass_callable_declaration_names(tokens, "@function");
8491    let mut symbols = Vec::new();
8492
8493    for (index, token) in tokens.iter().enumerate() {
8494        match token.kind {
8495            SyntaxKind::ScssVariable => {
8496                let kind = if scss_variable_token_is_declaration(tokens, index) {
8497                    ParsedSassSymbolFactKind::VariableDeclaration
8498                } else {
8499                    ParsedSassSymbolFactKind::VariableReference
8500                };
8501                let namespace = (!scss_variable_token_is_declaration(tokens, index))
8502                    .then(|| sass_member_namespace_before(tokens, index))
8503                    .flatten();
8504                symbols.push(ParsedSassSymbolFact {
8505                    kind,
8506                    symbol_kind: "variable",
8507                    name: token.text.trim_start_matches('$').to_string(),
8508                    role: match kind {
8509                        ParsedSassSymbolFactKind::VariableDeclaration => "declaration",
8510                        _ => "reference",
8511                    },
8512                    namespace,
8513                    range: sass_symbol_variable_range(token, kind),
8514                });
8515            }
8516            SyntaxKind::AtKeyword if token.text.eq_ignore_ascii_case("@mixin") => {
8517                if let Some(name) = sass_callable_name_after_at_rule(tokens, index) {
8518                    symbols.push(ParsedSassSymbolFact {
8519                        kind: ParsedSassSymbolFactKind::MixinDeclaration,
8520                        symbol_kind: "mixin",
8521                        name: name.text.to_string(),
8522                        role: "declaration",
8523                        namespace: None,
8524                        range: name.range,
8525                    });
8526                }
8527            }
8528            SyntaxKind::AtKeyword if token.text.eq_ignore_ascii_case("@include") => {
8529                if let Some((name, namespace)) = sass_include_name_after_at_rule(tokens, index) {
8530                    symbols.push(ParsedSassSymbolFact {
8531                        kind: ParsedSassSymbolFactKind::MixinInclude,
8532                        symbol_kind: "mixin",
8533                        name: name.text.to_string(),
8534                        role: "include",
8535                        namespace,
8536                        range: name.range,
8537                    });
8538                }
8539            }
8540            SyntaxKind::AtKeyword if token.text.eq_ignore_ascii_case("@function") => {
8541                if let Some(name) = sass_callable_name_after_at_rule(tokens, index) {
8542                    symbols.push(ParsedSassSymbolFact {
8543                        kind: ParsedSassSymbolFactKind::FunctionDeclaration,
8544                        symbol_kind: "function",
8545                        name: name.text.to_string(),
8546                        role: "declaration",
8547                        namespace: None,
8548                        range: name.range,
8549                    });
8550                }
8551            }
8552            SyntaxKind::Ident
8553                if (declared_functions.contains(token.text)
8554                    || sass_member_namespace_before(tokens, index).is_some())
8555                    && next_non_trivia_token(tokens, index + 1)
8556                        .is_some_and(|candidate| candidate.kind == SyntaxKind::LeftParen)
8557                    && !containing_at_rule_header_name(tokens, index)
8558                        .is_some_and(|name| name.eq_ignore_ascii_case("@include"))
8559                    && previous_non_trivia_token(tokens, 0, index).is_none_or(|candidate| {
8560                        !matches!(candidate.kind, SyntaxKind::AtKeyword)
8561                    }) =>
8562            {
8563                symbols.push(ParsedSassSymbolFact {
8564                    kind: ParsedSassSymbolFactKind::FunctionCall,
8565                    symbol_kind: "function",
8566                    name: token.text.to_string(),
8567                    role: "call",
8568                    namespace: sass_member_namespace_before(tokens, index),
8569                    range: token.range,
8570                });
8571            }
8572            _ => {}
8573        }
8574    }
8575
8576    symbols
8577}
8578
8579fn sass_symbol_variable_range(token: &Token<'_>, kind: ParsedSassSymbolFactKind) -> TextRange {
8580    if kind == ParsedSassSymbolFactKind::VariableDeclaration && token.text.starts_with('$') {
8581        let start = u32::from(token.range.start());
8582        let end = u32::from(token.range.end());
8583        if start < end {
8584            return TextRange::new(TextSize::from(start + 1), TextSize::from(end));
8585        }
8586    }
8587    token.range
8588}
8589
8590fn collect_sass_callable_declaration_names(
8591    tokens: &[Token<'_>],
8592    at_keyword: &str,
8593) -> BTreeSet<String> {
8594    tokens
8595        .iter()
8596        .enumerate()
8597        .filter_map(|(index, token)| {
8598            (token.kind == SyntaxKind::AtKeyword && token.text.eq_ignore_ascii_case(at_keyword))
8599                .then(|| sass_callable_name_after_at_rule(tokens, index))
8600                .flatten()
8601                .map(|name| name.text.to_string())
8602        })
8603        .collect()
8604}
8605
8606fn sass_callable_name_after_at_rule<'text>(
8607    tokens: &[Token<'text>],
8608    at_rule_index: usize,
8609) -> Option<Token<'text>> {
8610    let statement_end = css_module_value_statement_end(tokens, at_rule_index + 1);
8611    let name_index = next_non_trivia_token_index_until(tokens, at_rule_index + 1, statement_end)?;
8612    let name = tokens[name_index];
8613    if name.kind != SyntaxKind::Ident {
8614        return None;
8615    }
8616    if next_non_trivia_token_index_until(tokens, name_index + 1, statement_end)
8617        .is_some_and(|next| tokens[next].kind == SyntaxKind::Dot)
8618    {
8619        return None;
8620    }
8621    Some(name)
8622}
8623
8624fn sass_include_name_after_at_rule<'text>(
8625    tokens: &[Token<'text>],
8626    at_rule_index: usize,
8627) -> Option<(Token<'text>, Option<String>)> {
8628    let statement_end = css_module_value_statement_end(tokens, at_rule_index + 1);
8629    let first_index = next_non_trivia_token_index_until(tokens, at_rule_index + 1, statement_end)?;
8630    let first = tokens[first_index];
8631    if first.kind != SyntaxKind::Ident {
8632        return None;
8633    }
8634    let Some(dot_index) = next_non_trivia_token_index_until(tokens, first_index + 1, statement_end)
8635    else {
8636        return Some((first, None));
8637    };
8638    if tokens[dot_index].kind != SyntaxKind::Dot {
8639        return Some((first, None));
8640    }
8641    let member_index = next_non_trivia_token_index_until(tokens, dot_index + 1, statement_end)?;
8642    let member = tokens[member_index];
8643    (member.kind == SyntaxKind::Ident).then(|| (member, Some(first.text.to_string())))
8644}
8645
8646fn sass_member_namespace_before(tokens: &[Token<'_>], member_index: usize) -> Option<String> {
8647    let dot_index = previous_non_trivia_token_index(tokens, member_index, 0)?;
8648    if tokens[dot_index].kind != SyntaxKind::Dot {
8649        return None;
8650    }
8651    let namespace = tokens[previous_non_trivia_token_index(tokens, dot_index, 0)?];
8652    (namespace.kind == SyntaxKind::Ident).then(|| namespace.text.to_string())
8653}
8654
8655fn collect_sass_include_facts_from_tokens(
8656    source: &str,
8657    tokens: &[Token<'_>],
8658) -> Vec<ParsedSassIncludeFact> {
8659    let mut includes = Vec::new();
8660    for (index, token) in tokens.iter().enumerate() {
8661        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@include") {
8662            continue;
8663        }
8664        let statement_end = css_module_value_statement_end(tokens, index + 1);
8665        let Some((name, namespace)) = sass_include_name_after_at_rule(tokens, index) else {
8666            continue;
8667        };
8668        let header_end = previous_non_trivia_token_index(tokens, statement_end, index + 1)
8669            .map(|previous| tokens[previous].range.end())
8670            .unwrap_or(name.range.end());
8671        let params = source
8672            .get(u32::from(name.range.end()) as usize..u32::from(header_end) as usize)
8673            .unwrap_or_default()
8674            .trim()
8675            .to_string();
8676        includes.push(ParsedSassIncludeFact {
8677            name: name.text.to_string(),
8678            namespace,
8679            params,
8680            range: TextRange::new(token.range.start(), header_end),
8681        });
8682    }
8683    includes
8684}
8685
8686fn collect_sass_module_edge_facts_from_tokens(
8687    tokens: &[Token<'_>],
8688) -> Vec<ParsedSassModuleEdgeFact> {
8689    let mut edges = Vec::new();
8690    let mut seen = BTreeSet::new();
8691
8692    for (index, token) in tokens.iter().enumerate() {
8693        if token.kind != SyntaxKind::AtKeyword {
8694            continue;
8695        }
8696        let Some(kind) = sass_module_edge_kind(token.text) else {
8697            continue;
8698        };
8699        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
8700        let end = css_module_value_statement_end(tokens, start);
8701        if kind == ParsedSassModuleEdgeFactKind::Import {
8702            collect_sass_import_module_edges(tokens, start, end, &mut edges, &mut seen);
8703            continue;
8704        }
8705        let Some(source_index) = next_non_trivia_token_index_until(tokens, start, end) else {
8706            continue;
8707        };
8708        let source = tokens[source_index];
8709        if !matches!(source.kind, SyntaxKind::String | SyntaxKind::Url) {
8710            continue;
8711        }
8712        let source_name = css_module_value_source_name(source);
8713        let (namespace_kind, namespace) = if kind == ParsedSassModuleEdgeFactKind::Use {
8714            sass_module_use_namespace(tokens, source_name.as_str(), source_index + 1, end)
8715        } else {
8716            (None, None)
8717        };
8718        let (visibility_filter_kind, visibility_filter_names) =
8719            if kind == ParsedSassModuleEdgeFactKind::Forward {
8720                sass_module_forward_visibility_filter(tokens, source_index + 1, end)
8721            } else {
8722                (None, Vec::new())
8723            };
8724        push_sass_module_edge_fact(
8725            &mut edges,
8726            &mut seen,
8727            ParsedSassModuleEdgeFact {
8728                kind,
8729                source: source_name,
8730                namespace_kind,
8731                namespace,
8732                visibility_filter_kind,
8733                visibility_filter_names,
8734                range: source.range,
8735            },
8736        );
8737    }
8738
8739    edges
8740}
8741
8742fn sass_module_edge_kind(text: &str) -> Option<ParsedSassModuleEdgeFactKind> {
8743    match text {
8744        text if text.eq_ignore_ascii_case("@use") => Some(ParsedSassModuleEdgeFactKind::Use),
8745        text if text.eq_ignore_ascii_case("@forward") => {
8746            Some(ParsedSassModuleEdgeFactKind::Forward)
8747        }
8748        text if text.eq_ignore_ascii_case("@import") => Some(ParsedSassModuleEdgeFactKind::Import),
8749        _ => None,
8750    }
8751}
8752
8753fn collect_sass_import_module_edges(
8754    tokens: &[Token<'_>],
8755    start: usize,
8756    end: usize,
8757    edges: &mut Vec<ParsedSassModuleEdgeFact>,
8758    seen: &mut BTreeSet<(ParsedSassModuleEdgeFactKind, String, u32, u32)>,
8759) {
8760    for token in &tokens[start..end] {
8761        if !matches!(token.kind, SyntaxKind::String | SyntaxKind::Url) {
8762            continue;
8763        }
8764        push_sass_module_edge_fact(
8765            edges,
8766            seen,
8767            ParsedSassModuleEdgeFact {
8768                kind: ParsedSassModuleEdgeFactKind::Import,
8769                source: css_module_value_source_name(*token),
8770                namespace_kind: None,
8771                namespace: None,
8772                visibility_filter_kind: None,
8773                visibility_filter_names: Vec::new(),
8774                range: token.range,
8775            },
8776        );
8777    }
8778}
8779
8780fn sass_module_use_namespace(
8781    tokens: &[Token<'_>],
8782    source: &str,
8783    start: usize,
8784    end: usize,
8785) -> (Option<&'static str>, Option<String>) {
8786    let Some(as_index) = top_level_token_text_index(tokens, start, end, "as") else {
8787        return (
8788            Some("default"),
8789            sass_module_default_namespace(source).map(str::to_string),
8790        );
8791    };
8792    let Some(namespace_index) = next_non_trivia_token_index_until(tokens, as_index + 1, end) else {
8793        return (Some("invalid"), None);
8794    };
8795    let namespace = tokens[namespace_index];
8796    match namespace.kind {
8797        SyntaxKind::Star => (Some("wildcard"), None),
8798        SyntaxKind::Ident => (Some("alias"), Some(namespace.text.to_string())),
8799        _ => (Some("invalid"), None),
8800    }
8801}
8802
8803fn sass_module_forward_visibility_filter(
8804    tokens: &[Token<'_>],
8805    start: usize,
8806    end: usize,
8807) -> (Option<&'static str>, Vec<String>) {
8808    let show_index = top_level_token_text_index(tokens, start, end, "show");
8809    let hide_index = top_level_token_text_index(tokens, start, end, "hide");
8810    let (filter_kind, filter_index) = match (show_index, hide_index) {
8811        (Some(show_index), Some(hide_index)) if show_index <= hide_index => ("show", show_index),
8812        (Some(_), Some(hide_index)) => ("hide", hide_index),
8813        (Some(show_index), None) => ("show", show_index),
8814        (None, Some(hide_index)) => ("hide", hide_index),
8815        (None, None) => return (None, Vec::new()),
8816    };
8817    let clause_end =
8818        top_level_token_text_index(tokens, filter_index + 1, end, "with").unwrap_or(end);
8819    (
8820        Some(filter_kind),
8821        sass_module_visibility_filter_names(tokens, filter_index + 1, clause_end),
8822    )
8823}
8824
8825fn sass_module_visibility_filter_names(
8826    tokens: &[Token<'_>],
8827    start: usize,
8828    end: usize,
8829) -> Vec<String> {
8830    let mut names = BTreeSet::new();
8831    for token in &tokens[start..end] {
8832        match token.kind {
8833            SyntaxKind::Ident | SyntaxKind::ScssVariable => {
8834                if matches_ignore_ascii_case(token.text, &["show", "hide", "with", "as"]) {
8835                    continue;
8836                }
8837                let name = token.text.trim_start_matches('$');
8838                if !name.is_empty() {
8839                    names.insert(name.to_string());
8840                }
8841            }
8842            _ => {}
8843        }
8844    }
8845    names.into_iter().collect()
8846}
8847
8848fn sass_module_default_namespace(source: &str) -> Option<&str> {
8849    let basename = source
8850        .rsplit(['/', '\\', ':'])
8851        .next()
8852        .unwrap_or(source)
8853        .trim_start_matches('_');
8854    let namespace = basename.split('.').next().unwrap_or(basename);
8855    (!namespace.is_empty()).then_some(namespace)
8856}
8857
8858fn push_sass_module_edge_fact(
8859    edges: &mut Vec<ParsedSassModuleEdgeFact>,
8860    seen: &mut BTreeSet<(ParsedSassModuleEdgeFactKind, String, u32, u32)>,
8861    edge: ParsedSassModuleEdgeFact,
8862) {
8863    let start: u32 = edge.range.start().into();
8864    let end: u32 = edge.range.end().into();
8865    if seen.insert((edge.kind, edge.source.clone(), start, end)) {
8866        edges.push(edge);
8867    }
8868}
8869
8870fn collect_css_module_value_facts_from_tokens(
8871    tokens: &[Token<'_>],
8872) -> Vec<ParsedCssModuleValueFact> {
8873    let mut values = Vec::new();
8874    let mut seen = BTreeSet::new();
8875    let value_path_aliases = collect_css_module_value_path_aliases_from_tokens(tokens);
8876    for (index, token) in tokens.iter().enumerate() {
8877        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
8878            continue;
8879        }
8880
8881        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
8882        let end = css_module_value_statement_end(tokens, start);
8883        let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
8884        let from_index = top_level_token_text_index(tokens, start, end, "from");
8885
8886        if let Some(from_index) = from_index
8887            && match colon_index {
8888                Some(colon_index) => from_index < colon_index,
8889                None => true,
8890            }
8891        {
8892            collect_css_module_value_import_facts(
8893                tokens,
8894                start,
8895                from_index,
8896                end,
8897                &value_path_aliases,
8898                &mut values,
8899                &mut seen,
8900            );
8901            continue;
8902        }
8903
8904        if let Some(colon_index) = colon_index {
8905            if css_module_value_path_alias_from_tokens(tokens, start, colon_index, end).is_some() {
8906                continue;
8907            }
8908            collect_css_module_value_definition_facts(
8909                tokens,
8910                start,
8911                colon_index,
8912                &mut values,
8913                &mut seen,
8914            );
8915            collect_css_module_value_reference_facts(
8916                tokens,
8917                colon_index + 1,
8918                end,
8919                &mut values,
8920                &mut seen,
8921            );
8922        } else {
8923            collect_css_module_value_definition_facts(tokens, start, end, &mut values, &mut seen);
8924        }
8925    }
8926    let local_value_names = values
8927        .iter()
8928        .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
8929        .map(|value| value.name.clone())
8930        .collect::<BTreeSet<_>>();
8931    collect_css_module_value_declaration_reference_facts(
8932        tokens,
8933        0,
8934        tokens.len(),
8935        &local_value_names,
8936        &mut values,
8937        &mut seen,
8938    );
8939    values
8940}
8941
8942fn collect_css_module_value_path_aliases_from_tokens(
8943    tokens: &[Token<'_>],
8944) -> BTreeMap<String, String> {
8945    let mut aliases = BTreeMap::new();
8946    for (index, token) in tokens.iter().enumerate() {
8947        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
8948            continue;
8949        }
8950
8951        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
8952        let end = css_module_value_statement_end(tokens, start);
8953        let Some(colon_index) = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon)
8954        else {
8955            continue;
8956        };
8957        if top_level_token_text_index(tokens, start, end, "from").is_some() {
8958            continue;
8959        }
8960        if let Some((name, target)) =
8961            css_module_value_path_alias_from_tokens(tokens, start, colon_index, end)
8962        {
8963            aliases.insert(name, target);
8964        }
8965    }
8966    aliases
8967}
8968
8969fn css_module_value_path_alias_from_tokens(
8970    tokens: &[Token<'_>],
8971    start: usize,
8972    colon_index: usize,
8973    end: usize,
8974) -> Option<(String, String)> {
8975    let name_index = next_non_trivia_token_index_until(tokens, start, colon_index)?;
8976    let name_token = tokens[name_index];
8977    if !css_module_value_name_token_can_define(name_token) {
8978        return None;
8979    }
8980    let source_index = next_non_trivia_token_index_until(tokens, colon_index + 1, end)?;
8981    let source_token = tokens[source_index];
8982    if !matches!(source_token.kind, SyntaxKind::String | SyntaxKind::Url) {
8983        return None;
8984    }
8985    let source = css_module_value_source_name(source_token);
8986    css_module_value_source_looks_like_style_request(&source)
8987        .then(|| (name_token.text.to_string(), source))
8988}
8989
8990fn css_module_value_statement_end(tokens: &[Token<'_>], start: usize) -> usize {
8991    let mut index = start;
8992    let mut paren_depth = 0usize;
8993    let mut bracket_depth = 0usize;
8994    while index < tokens.len() {
8995        match tokens[index].kind {
8996            SyntaxKind::LeftParen => paren_depth += 1,
8997            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8998            SyntaxKind::LeftBracket => bracket_depth += 1,
8999            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9000            SyntaxKind::Semicolon
9001            | SyntaxKind::SassOptionalSemicolon
9002            | SyntaxKind::LeftBrace
9003            | SyntaxKind::RightBrace
9004            | SyntaxKind::SassIndent
9005            | SyntaxKind::SassDedent
9006                if paren_depth == 0 && bracket_depth == 0 =>
9007            {
9008                return index;
9009            }
9010            _ => {}
9011        }
9012        index += 1;
9013    }
9014    index
9015}
9016
9017fn collect_css_module_value_import_facts(
9018    tokens: &[Token<'_>],
9019    start: usize,
9020    from_index: usize,
9021    end: usize,
9022    value_path_aliases: &BTreeMap<String, String>,
9023    values: &mut Vec<ParsedCssModuleValueFact>,
9024    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9025) {
9026    collect_css_module_value_import_names(tokens, start, from_index, values, seen);
9027    if let Some((source_name, source_range)) =
9028        css_module_value_import_edge_source(tokens, from_index + 1, end, value_path_aliases)
9029    {
9030        push_css_module_value_fact(
9031            values,
9032            seen,
9033            ParsedCssModuleValueFactKind::ImportSource,
9034            source_name,
9035            source_range,
9036        );
9037    }
9038}
9039
9040fn collect_css_module_value_import_edge_facts_from_tokens(
9041    tokens: &[Token<'_>],
9042) -> Vec<ParsedCssModuleValueImportEdgeFact> {
9043    let mut edges = Vec::new();
9044    let value_path_aliases = collect_css_module_value_path_aliases_from_tokens(tokens);
9045    for (index, token) in tokens.iter().enumerate() {
9046        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9047            continue;
9048        }
9049
9050        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9051        let end = css_module_value_statement_end(tokens, start);
9052        let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9053        let from_index = top_level_token_text_index(tokens, start, end, "from");
9054        let Some(from_index) = from_index else {
9055            continue;
9056        };
9057        if colon_index.is_some_and(|colon_index| from_index > colon_index) {
9058            continue;
9059        }
9060        let Some((import_source, _source_range)) =
9061            css_module_value_import_edge_source(tokens, from_index + 1, end, &value_path_aliases)
9062        else {
9063            continue;
9064        };
9065
9066        collect_css_module_value_import_edges(tokens, start, from_index, import_source, &mut edges);
9067    }
9068    edges
9069}
9070
9071fn collect_css_module_value_definition_edge_facts_from_tokens(
9072    tokens: &[Token<'_>],
9073) -> Vec<ParsedCssModuleValueDefinitionEdgeFact> {
9074    let mut edges = Vec::new();
9075    for (index, token) in tokens.iter().enumerate() {
9076        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9077            continue;
9078        }
9079
9080        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9081        let end = css_module_value_statement_end(tokens, start);
9082        let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9083        let from_index = top_level_token_text_index(tokens, start, end, "from");
9084        let Some(colon_index) = colon_index else {
9085            continue;
9086        };
9087        if from_index.is_some_and(|from_index| from_index < colon_index) {
9088            continue;
9089        }
9090
9091        let definition_names = collect_css_module_value_definition_edge_names(
9092            tokens,
9093            start,
9094            colon_index,
9095            |tokens, index| css_module_value_name_token_can_define(tokens[index]),
9096        );
9097        let reference_names = collect_css_module_value_definition_edge_names(
9098            tokens,
9099            colon_index + 1,
9100            end,
9101            css_module_value_reference_token_can_be_name,
9102        );
9103        if reference_names.is_empty() {
9104            continue;
9105        }
9106        let range_end = end
9107            .checked_sub(1)
9108            .and_then(|end| tokens.get(end))
9109            .map(|token| token.range.end())
9110            .unwrap_or_else(|| tokens[index].range.end());
9111
9112        for definition_name in definition_names {
9113            edges.push(ParsedCssModuleValueDefinitionEdgeFact {
9114                definition_name,
9115                reference_names: reference_names.clone(),
9116                range: TextRange::new(tokens[index].range.start(), range_end),
9117            });
9118        }
9119    }
9120    edges
9121}
9122
9123fn collect_css_module_value_definition_edge_names(
9124    tokens: &[Token<'_>],
9125    start: usize,
9126    end: usize,
9127    predicate: impl Fn(&[Token<'_>], usize) -> bool,
9128) -> Vec<String> {
9129    let mut names = Vec::new();
9130    let mut index = start;
9131    while index < end {
9132        if predicate(tokens, index) && !names.iter().any(|name| name == tokens[index].text) {
9133            names.push(tokens[index].text.to_string());
9134        }
9135        index += 1;
9136    }
9137    names
9138}
9139
9140fn css_module_value_import_edge_source(
9141    tokens: &[Token<'_>],
9142    start: usize,
9143    end: usize,
9144    value_path_aliases: &BTreeMap<String, String>,
9145) -> Option<(String, TextRange)> {
9146    let source_index = next_non_trivia_token_index_until(tokens, start, end)?;
9147    let token = tokens[source_index];
9148    if matches!(token.kind, SyntaxKind::String | SyntaxKind::Url) {
9149        return Some((css_module_value_source_name(token), token.range));
9150    }
9151    if css_module_value_name_token_can_define(token) {
9152        return css_module_value_source_alias_target(token.text, token.range, value_path_aliases);
9153    }
9154    None
9155}
9156
9157fn css_module_value_source_alias_target(
9158    name: &str,
9159    range: TextRange,
9160    value_path_aliases: &BTreeMap<String, String>,
9161) -> Option<(String, TextRange)> {
9162    value_path_aliases
9163        .get(name)
9164        .map(|source| (source.clone(), range))
9165}
9166
9167fn collect_css_module_value_import_edges(
9168    tokens: &[Token<'_>],
9169    start: usize,
9170    end: usize,
9171    import_source: String,
9172    edges: &mut Vec<ParsedCssModuleValueImportEdgeFact>,
9173) {
9174    let mut index = start;
9175    while index < end {
9176        let token = tokens[index];
9177        if !css_module_value_name_token_can_define(token) {
9178            index += 1;
9179            continue;
9180        }
9181        if previous_non_trivia_token_index(tokens, index, start)
9182            .is_some_and(|previous| tokens[previous].text == "as")
9183        {
9184            index += 1;
9185            continue;
9186        }
9187        let remote_name = token.text.to_string();
9188        let mut local_name = remote_name.clone();
9189        let mut local_range = token.range;
9190        if let Some(as_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
9191            && tokens[as_index].text == "as"
9192            && let Some(local_index) = next_non_trivia_token_index_until(tokens, as_index + 1, end)
9193            && css_module_value_name_token_can_define(tokens[local_index])
9194        {
9195            local_name = tokens[local_index].text.to_string();
9196            local_range = tokens[local_index].range;
9197            index = local_index + 1;
9198        } else {
9199            index += 1;
9200        }
9201        edges.push(ParsedCssModuleValueImportEdgeFact {
9202            remote_name,
9203            local_name,
9204            import_source: import_source.clone(),
9205            local_range,
9206            remote_range: token.range,
9207            range: token.range,
9208        });
9209    }
9210}
9211
9212fn collect_css_module_value_import_names(
9213    tokens: &[Token<'_>],
9214    start: usize,
9215    end: usize,
9216    values: &mut Vec<ParsedCssModuleValueFact>,
9217    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9218) {
9219    let mut index = start;
9220    while index < end {
9221        let token = tokens[index];
9222        if css_module_value_name_token_can_define(token) {
9223            let previous = previous_non_trivia_token_index(tokens, index, start);
9224            let next = next_non_trivia_token_index_until(tokens, index + 1, end);
9225            let kind = if previous.is_some_and(|previous| tokens[previous].text == "as") {
9226                Some(ParsedCssModuleValueFactKind::Definition)
9227            } else if next.is_some_and(|next| tokens[next].text == "as") {
9228                Some(ParsedCssModuleValueFactKind::Reference)
9229            } else {
9230                Some(ParsedCssModuleValueFactKind::Definition)
9231            };
9232            if let Some(kind) = kind {
9233                push_css_module_value_fact(values, seen, kind, token.text.to_string(), token.range);
9234            }
9235        }
9236        index += 1;
9237    }
9238}
9239
9240fn collect_css_module_value_definition_facts(
9241    tokens: &[Token<'_>],
9242    start: usize,
9243    end: usize,
9244    values: &mut Vec<ParsedCssModuleValueFact>,
9245    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9246) {
9247    let mut index = start;
9248    while index < end {
9249        let token = tokens[index];
9250        if css_module_value_name_token_can_define(token) {
9251            push_css_module_value_fact(
9252                values,
9253                seen,
9254                ParsedCssModuleValueFactKind::Definition,
9255                token.text.to_string(),
9256                token.range,
9257            );
9258        }
9259        index += 1;
9260    }
9261}
9262
9263fn collect_css_module_value_reference_facts(
9264    tokens: &[Token<'_>],
9265    start: usize,
9266    end: usize,
9267    values: &mut Vec<ParsedCssModuleValueFact>,
9268    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9269) {
9270    let mut index = start;
9271    let mut paren_depth = 0usize;
9272    let mut bracket_depth = 0usize;
9273    while index < end {
9274        match tokens[index].kind {
9275            SyntaxKind::LeftParen => paren_depth += 1,
9276            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9277            SyntaxKind::LeftBracket => bracket_depth += 1,
9278            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9279            _ => {}
9280        }
9281        if paren_depth == 0
9282            && bracket_depth == 0
9283            && css_module_value_reference_token_can_be_name(tokens, index)
9284        {
9285            push_css_module_value_fact(
9286                values,
9287                seen,
9288                ParsedCssModuleValueFactKind::Reference,
9289                tokens[index].text.to_string(),
9290                tokens[index].range,
9291            );
9292        }
9293        index += 1;
9294    }
9295}
9296
9297fn collect_css_module_value_declaration_reference_facts(
9298    tokens: &[Token<'_>],
9299    start: usize,
9300    end: usize,
9301    local_value_names: &BTreeSet<String>,
9302    values: &mut Vec<ParsedCssModuleValueFact>,
9303    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9304) {
9305    if local_value_names.is_empty() {
9306        return;
9307    }
9308
9309    let mut index = start;
9310    while index < end {
9311        index = skip_trivia_tokens(tokens, index, end);
9312        if index >= end {
9313            break;
9314        }
9315
9316        if tokens[index].kind == SyntaxKind::AtKeyword {
9317            let block = find_block_after_header(tokens, index, end);
9318            if let Some((open, close)) = block {
9319                if style_wrapper_at_rule(tokens[index].text) {
9320                    collect_css_module_value_declaration_reference_facts(
9321                        tokens,
9322                        open + 1,
9323                        close,
9324                        local_value_names,
9325                        values,
9326                        seen,
9327                    );
9328                }
9329                index = close + 1;
9330            } else {
9331                index = skip_statement(tokens, index, end);
9332            }
9333            continue;
9334        }
9335
9336        let statement_end = css_module_value_statement_end(tokens, index);
9337        if statement_end < end && tokens[statement_end].kind == SyntaxKind::LeftBrace {
9338            if let Some(close) = matching_right_brace(tokens, statement_end, end) {
9339                collect_css_module_value_declaration_reference_facts(
9340                    tokens,
9341                    statement_end + 1,
9342                    close,
9343                    local_value_names,
9344                    values,
9345                    seen,
9346                );
9347                index = close + 1;
9348            } else {
9349                index = statement_end + 1;
9350            }
9351            continue;
9352        }
9353
9354        if let Some(colon_index) = declaration_colon_index(tokens, index, statement_end.min(end)) {
9355            collect_known_css_module_value_reference_facts(
9356                tokens,
9357                colon_index + 1,
9358                statement_end.min(end),
9359                local_value_names,
9360                values,
9361                seen,
9362            );
9363        }
9364
9365        if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
9366            break;
9367        }
9368        index = statement_end + 1;
9369    }
9370}
9371
9372fn declaration_colon_index(tokens: &[Token<'_>], start: usize, end: usize) -> Option<usize> {
9373    let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon)?;
9374    let property_index = previous_non_trivia_token_index(tokens, colon_index, start)?;
9375    if !matches!(
9376        tokens[property_index].kind,
9377        SyntaxKind::Ident
9378            | SyntaxKind::CustomPropertyName
9379            | SyntaxKind::ScssVariable
9380            | SyntaxKind::LessVariable
9381            | SyntaxKind::LessPropertyVariableToken
9382    ) {
9383        return None;
9384    }
9385    let value_index = next_non_trivia_token_index_until(tokens, colon_index + 1, end)?;
9386    if matches!(
9387        tokens[value_index].kind,
9388        SyntaxKind::LeftBrace | SyntaxKind::LeftParen | SyntaxKind::LeftBracket
9389    ) {
9390        return None;
9391    }
9392    Some(colon_index)
9393}
9394
9395fn collect_known_css_module_value_reference_facts(
9396    tokens: &[Token<'_>],
9397    start: usize,
9398    end: usize,
9399    local_value_names: &BTreeSet<String>,
9400    values: &mut Vec<ParsedCssModuleValueFact>,
9401    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9402) {
9403    let mut index = start;
9404    let mut paren_depth = 0usize;
9405    let mut bracket_depth = 0usize;
9406    while index < end {
9407        match tokens[index].kind {
9408            SyntaxKind::LeftParen => paren_depth += 1,
9409            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9410            SyntaxKind::LeftBracket => bracket_depth += 1,
9411            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9412            _ => {}
9413        }
9414        if paren_depth == 0
9415            && bracket_depth == 0
9416            && css_module_value_reference_token_can_be_name(tokens, index)
9417            && local_value_names.contains(tokens[index].text)
9418        {
9419            push_css_module_value_fact(
9420                values,
9421                seen,
9422                ParsedCssModuleValueFactKind::Reference,
9423                tokens[index].text.to_string(),
9424                tokens[index].range,
9425            );
9426        }
9427        index += 1;
9428    }
9429}
9430
9431fn push_css_module_value_fact(
9432    values: &mut Vec<ParsedCssModuleValueFact>,
9433    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9434    kind: ParsedCssModuleValueFactKind,
9435    name: String,
9436    range: TextRange,
9437) {
9438    if seen.insert((
9439        kind,
9440        name.clone(),
9441        u32::from(range.start()),
9442        u32::from(range.end()),
9443    )) {
9444        values.push(ParsedCssModuleValueFact { kind, name, range });
9445    }
9446}
9447
9448fn top_level_token_kind_index(
9449    tokens: &[Token<'_>],
9450    start: usize,
9451    end: usize,
9452    expected: SyntaxKind,
9453) -> Option<usize> {
9454    let mut index = start;
9455    let mut paren_depth = 0usize;
9456    let mut bracket_depth = 0usize;
9457    while index < end {
9458        match tokens[index].kind {
9459            SyntaxKind::LeftParen => paren_depth += 1,
9460            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9461            SyntaxKind::LeftBracket => bracket_depth += 1,
9462            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9463            kind if kind == expected && paren_depth == 0 && bracket_depth == 0 => {
9464                return Some(index);
9465            }
9466            _ => {}
9467        }
9468        index += 1;
9469    }
9470    None
9471}
9472
9473fn top_level_token_text_index(
9474    tokens: &[Token<'_>],
9475    start: usize,
9476    end: usize,
9477    expected: &str,
9478) -> Option<usize> {
9479    let mut index = start;
9480    let mut paren_depth = 0usize;
9481    let mut bracket_depth = 0usize;
9482    while index < end {
9483        match tokens[index].kind {
9484            SyntaxKind::LeftParen => paren_depth += 1,
9485            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9486            SyntaxKind::LeftBracket => bracket_depth += 1,
9487            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9488            SyntaxKind::Ident
9489                if paren_depth == 0
9490                    && bracket_depth == 0
9491                    && tokens[index].text.eq_ignore_ascii_case(expected) =>
9492            {
9493                return Some(index);
9494            }
9495            _ => {}
9496        }
9497        index += 1;
9498    }
9499    None
9500}
9501
9502fn previous_non_trivia_token_index(
9503    tokens: &[Token<'_>],
9504    mut index: usize,
9505    start: usize,
9506) -> Option<usize> {
9507    while index > start {
9508        index -= 1;
9509        if !tokens[index].kind.is_trivia() {
9510            return Some(index);
9511        }
9512    }
9513    None
9514}
9515
9516fn css_module_value_name_token_can_define(token: Token<'_>) -> bool {
9517    matches!(
9518        token.kind,
9519        SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9520    ) && !matches!(token.text, "as" | "from")
9521}
9522
9523fn css_module_value_reference_token_can_be_name(tokens: &[Token<'_>], index: usize) -> bool {
9524    let token = tokens[index];
9525    if !matches!(
9526        token.kind,
9527        SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9528    ) {
9529        return false;
9530    }
9531    if let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9532        && tokens[next_index].kind == SyntaxKind::LeftParen
9533    {
9534        return false;
9535    }
9536    !css_module_value_literal_ident_is_not_reference(token.text)
9537}
9538
9539fn css_module_value_literal_ident_is_not_reference(name: &str) -> bool {
9540    matches!(
9541        name.to_ascii_lowercase().as_str(),
9542        "initial"
9543            | "inherit"
9544            | "unset"
9545            | "revert"
9546            | "revert-layer"
9547            | "none"
9548            | "auto"
9549            | "normal"
9550            | "transparent"
9551            | "currentcolor"
9552            | "black"
9553            | "white"
9554            | "red"
9555            | "green"
9556            | "blue"
9557            | "yellow"
9558            | "magenta"
9559            | "cyan"
9560            | "solid"
9561            | "dashed"
9562            | "block"
9563            | "inline"
9564            | "flex"
9565            | "grid"
9566    )
9567}
9568
9569fn css_module_value_source_name(token: Token<'_>) -> String {
9570    token
9571        .text
9572        .trim_matches(|character| character == '"' || character == '\'')
9573        .to_string()
9574}
9575
9576fn css_module_value_source_looks_like_style_request(source: &str) -> bool {
9577    let lower = source.to_ascii_lowercase();
9578    (lower.starts_with('/') || lower.starts_with("./") || lower.starts_with("../"))
9579        && (lower.ends_with(".css")
9580            || lower.ends_with(".scss")
9581            || lower.ends_with(".sass")
9582            || lower.ends_with(".less"))
9583}
9584
9585fn collect_css_module_composes_facts_from_tokens(
9586    tokens: &[Token<'_>],
9587) -> Vec<ParsedCssModuleComposesFact> {
9588    let mut composes = Vec::new();
9589    let mut seen = BTreeSet::new();
9590    for (index, token) in tokens.iter().enumerate() {
9591        if token.kind != SyntaxKind::Ident || !token.text.eq_ignore_ascii_case("composes") {
9592            continue;
9593        }
9594        let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9595        else {
9596            continue;
9597        };
9598        if tokens[colon_index].kind != SyntaxKind::Colon {
9599            continue;
9600        }
9601
9602        let start = colon_index + 1;
9603        let end = css_module_value_statement_end(tokens, start);
9604        let from_index = top_level_token_text_index(tokens, start, end, "from");
9605        let target_end = from_index.unwrap_or(end);
9606        collect_css_module_composes_targets(tokens, start, target_end, &mut composes, &mut seen);
9607        if let Some(from_index) = from_index {
9608            collect_css_module_composes_import_source(
9609                tokens,
9610                from_index + 1,
9611                end,
9612                &mut composes,
9613                &mut seen,
9614            );
9615        }
9616    }
9617    composes
9618}
9619
9620fn collect_css_module_composes_edge_facts_from_tokens(
9621    tokens: &[Token<'_>],
9622) -> Vec<ParsedCssModuleComposesEdgeFact> {
9623    let mut edges = Vec::new();
9624    collect_css_module_composes_edge_facts_in_range(tokens, 0, tokens.len(), &[], None, &mut edges);
9625    edges
9626}
9627
9628fn collect_css_module_composes_edge_facts_in_range(
9629    tokens: &[Token<'_>],
9630    start: usize,
9631    end: usize,
9632    parent_branches: &[SelectorBranch],
9633    css_module_scope: Option<&'static str>,
9634    edges: &mut Vec<ParsedCssModuleComposesEdgeFact>,
9635) {
9636    let mut index = start;
9637    while index < end {
9638        index = skip_trivia_tokens(tokens, index, end);
9639        if index >= end {
9640            break;
9641        }
9642
9643        if tokens[index].kind == SyntaxKind::AtKeyword {
9644            let block = find_block_after_header(tokens, index, end);
9645            if let Some((open, close)) = block {
9646                if tokens[index].text == "@nest" {
9647                    if css_module_scope == Some("global") {
9648                        collect_css_module_composes_edge_facts_in_range(
9649                            tokens,
9650                            open + 1,
9651                            close,
9652                            &[],
9653                            css_module_scope,
9654                            edges,
9655                        );
9656                    } else {
9657                        let branches =
9658                            resolve_selector_header(tokens, index + 1, open, parent_branches);
9659                        collect_immediate_css_module_composes_edge_facts(
9660                            tokens,
9661                            open + 1,
9662                            close,
9663                            &branches,
9664                            edges,
9665                        );
9666                        collect_css_module_composes_edge_facts_in_range(
9667                            tokens,
9668                            open + 1,
9669                            close,
9670                            &branches,
9671                            css_module_scope,
9672                            edges,
9673                        );
9674                    }
9675                } else if style_wrapper_at_rule(tokens[index].text) {
9676                    collect_css_module_composes_edge_facts_in_range(
9677                        tokens,
9678                        open + 1,
9679                        close,
9680                        parent_branches,
9681                        css_module_scope,
9682                        edges,
9683                    );
9684                }
9685                index = close + 1;
9686            } else {
9687                index = skip_statement(tokens, index, end);
9688            }
9689            continue;
9690        }
9691
9692        let Some((open, close)) = find_block_after_header(tokens, index, end) else {
9693            index = skip_statement(tokens, index, end);
9694            continue;
9695        };
9696
9697        let effective_scope = css_module_scope
9698            .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
9699        if effective_scope == Some("global") {
9700            collect_css_module_composes_edge_facts_in_range(
9701                tokens,
9702                open + 1,
9703                close,
9704                &[],
9705                effective_scope,
9706                edges,
9707            );
9708        } else {
9709            let branches = resolve_selector_header(tokens, index, open, parent_branches);
9710            collect_immediate_css_module_composes_edge_facts(
9711                tokens,
9712                open + 1,
9713                close,
9714                &branches,
9715                edges,
9716            );
9717            collect_css_module_composes_edge_facts_in_range(
9718                tokens,
9719                open + 1,
9720                close,
9721                &branches,
9722                effective_scope,
9723                edges,
9724            );
9725        }
9726        index = close + 1;
9727    }
9728}
9729
9730fn collect_immediate_css_module_composes_edge_facts(
9731    tokens: &[Token<'_>],
9732    start: usize,
9733    end: usize,
9734    owner_branches: &[SelectorBranch],
9735    edges: &mut Vec<ParsedCssModuleComposesEdgeFact>,
9736) {
9737    let owner_selector_names = sorted_selector_branch_names(owner_branches);
9738    let mut index = start;
9739    let mut block_depth = 0usize;
9740    while index < end {
9741        match tokens[index].kind {
9742            SyntaxKind::LeftBrace | SyntaxKind::SassIndent => {
9743                block_depth += 1;
9744                index += 1;
9745                continue;
9746            }
9747            SyntaxKind::RightBrace | SyntaxKind::SassDedent => {
9748                block_depth = block_depth.saturating_sub(1);
9749                index += 1;
9750                continue;
9751            }
9752            _ => {}
9753        }
9754        if block_depth > 0
9755            || tokens[index].kind != SyntaxKind::Ident
9756            || !tokens[index].text.eq_ignore_ascii_case("composes")
9757        {
9758            index += 1;
9759            continue;
9760        }
9761        let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end) else {
9762            index += 1;
9763            continue;
9764        };
9765        if tokens[colon_index].kind != SyntaxKind::Colon {
9766            index += 1;
9767            continue;
9768        }
9769
9770        let value_start = colon_index + 1;
9771        let value_end = css_module_value_statement_end(tokens, value_start).min(end);
9772        let from_index = top_level_token_text_index(tokens, value_start, value_end, "from");
9773        let target_end = from_index.unwrap_or(value_end);
9774        let target_names =
9775            collect_css_module_composes_target_names(tokens, value_start, target_end);
9776        if target_names.is_empty() {
9777            index = value_end;
9778            continue;
9779        }
9780
9781        let (kind, import_source) = from_index
9782            .and_then(|from_index| {
9783                css_module_composes_import_edge_source(tokens, from_index + 1, value_end)
9784            })
9785            .map(|source| {
9786                if source == "global" {
9787                    (ParsedCssModuleComposesEdgeKind::Global, Some(source))
9788                } else {
9789                    (ParsedCssModuleComposesEdgeKind::External, Some(source))
9790                }
9791            })
9792            .unwrap_or((ParsedCssModuleComposesEdgeKind::Local, None));
9793        let range_end = value_end
9794            .checked_sub(1)
9795            .and_then(|end| tokens.get(end))
9796            .map(|token| token.range.end())
9797            .unwrap_or_else(|| tokens[index].range.end());
9798
9799        edges.push(ParsedCssModuleComposesEdgeFact {
9800            kind,
9801            owner_selector_names: owner_selector_names.clone(),
9802            target_names,
9803            import_source,
9804            range: TextRange::new(tokens[index].range.start(), range_end),
9805        });
9806        index = value_end;
9807    }
9808}
9809
9810fn sorted_selector_branch_names(branches: &[SelectorBranch]) -> Vec<String> {
9811    branches
9812        .iter()
9813        .map(|branch| branch.name.clone())
9814        .collect::<BTreeSet<_>>()
9815        .into_iter()
9816        .collect()
9817}
9818
9819fn collect_css_module_composes_target_names(
9820    tokens: &[Token<'_>],
9821    start: usize,
9822    end: usize,
9823) -> Vec<String> {
9824    let mut names = Vec::new();
9825    let mut index = start;
9826    while index < end {
9827        if matches!(
9828            tokens[index].kind,
9829            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9830        ) && !tokens[index].text.eq_ignore_ascii_case("from")
9831            && !names.iter().any(|name| name == tokens[index].text)
9832        {
9833            names.push(tokens[index].text.to_string());
9834        }
9835        index += 1;
9836    }
9837    names
9838}
9839
9840fn css_module_composes_import_edge_source(
9841    tokens: &[Token<'_>],
9842    start: usize,
9843    end: usize,
9844) -> Option<String> {
9845    let source_index = next_non_trivia_token_index_until(tokens, start, end)?;
9846    let token = tokens[source_index];
9847    matches!(
9848        token.kind,
9849        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
9850    )
9851    .then(|| css_module_value_source_name(token))
9852}
9853
9854fn collect_css_module_composes_targets(
9855    tokens: &[Token<'_>],
9856    start: usize,
9857    end: usize,
9858    composes: &mut Vec<ParsedCssModuleComposesFact>,
9859    seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
9860) {
9861    let mut index = start;
9862    while index < end {
9863        if matches!(
9864            tokens[index].kind,
9865            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9866        ) && !tokens[index].text.eq_ignore_ascii_case("from")
9867        {
9868            push_css_module_composes_fact(
9869                composes,
9870                seen,
9871                ParsedCssModuleComposesFactKind::Target,
9872                tokens[index].text.to_string(),
9873                tokens[index].range,
9874            );
9875        }
9876        index += 1;
9877    }
9878}
9879
9880fn collect_css_module_composes_import_source(
9881    tokens: &[Token<'_>],
9882    start: usize,
9883    end: usize,
9884    composes: &mut Vec<ParsedCssModuleComposesFact>,
9885    seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
9886) {
9887    if let Some(source_index) = next_non_trivia_token_index_until(tokens, start, end) {
9888        let token = tokens[source_index];
9889        if matches!(
9890            token.kind,
9891            SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
9892        ) {
9893            push_css_module_composes_fact(
9894                composes,
9895                seen,
9896                ParsedCssModuleComposesFactKind::ImportSource,
9897                css_module_value_source_name(token),
9898                token.range,
9899            );
9900        }
9901    }
9902}
9903
9904fn push_css_module_composes_fact(
9905    composes: &mut Vec<ParsedCssModuleComposesFact>,
9906    seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
9907    kind: ParsedCssModuleComposesFactKind,
9908    name: String,
9909    range: TextRange,
9910) {
9911    if seen.insert((
9912        kind,
9913        name.clone(),
9914        u32::from(range.start()),
9915        u32::from(range.end()),
9916    )) {
9917        composes.push(ParsedCssModuleComposesFact { kind, name, range });
9918    }
9919}
9920
9921fn collect_icss_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedIcssFact> {
9922    let mut icss = Vec::new();
9923    let mut seen = BTreeSet::new();
9924    for (index, token) in tokens.iter().enumerate() {
9925        if token.kind != SyntaxKind::Colon {
9926            continue;
9927        }
9928        let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9929        else {
9930            continue;
9931        };
9932        let name = tokens[name_index].text;
9933        if !matches!(tokens[name_index].kind, SyntaxKind::Ident) {
9934            continue;
9935        }
9936        if name.eq_ignore_ascii_case("export") {
9937            if let Some((open, close)) =
9938                find_block_after_header(tokens, name_index + 1, tokens.len())
9939            {
9940                collect_icss_export_names(tokens, open + 1, close, &mut icss, &mut seen);
9941            }
9942            continue;
9943        }
9944        if name.eq_ignore_ascii_case("import") {
9945            collect_icss_import_source(tokens, name_index + 1, &mut icss, &mut seen);
9946            if let Some((open, close)) =
9947                find_block_after_header(tokens, name_index + 1, tokens.len())
9948            {
9949                collect_icss_import_names(tokens, open + 1, close, &mut icss, &mut seen);
9950            }
9951        }
9952    }
9953    icss
9954}
9955
9956fn collect_icss_import_edge_facts_from_tokens(
9957    tokens: &[Token<'_>],
9958) -> Vec<ParsedIcssImportEdgeFact> {
9959    let mut edges = Vec::new();
9960    for (index, token) in tokens.iter().enumerate() {
9961        if token.kind != SyntaxKind::Colon {
9962            continue;
9963        }
9964        let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9965        else {
9966            continue;
9967        };
9968        if tokens[name_index].kind != SyntaxKind::Ident
9969            || !tokens[name_index].text.eq_ignore_ascii_case("import")
9970        {
9971            continue;
9972        }
9973        let Some(import_source) = icss_import_edge_source(tokens, name_index + 1) else {
9974            continue;
9975        };
9976        if let Some((open, close)) = find_block_after_header(tokens, name_index + 1, tokens.len()) {
9977            collect_icss_import_edges(tokens, open + 1, close, import_source, &mut edges);
9978        }
9979    }
9980    edges
9981}
9982
9983fn collect_icss_export_edge_facts_from_tokens(
9984    tokens: &[Token<'_>],
9985) -> Vec<ParsedIcssExportEdgeFact> {
9986    let mut edges = Vec::new();
9987    for (index, token) in tokens.iter().enumerate() {
9988        if token.kind != SyntaxKind::Colon {
9989            continue;
9990        }
9991        let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9992        else {
9993            continue;
9994        };
9995        if tokens[name_index].kind != SyntaxKind::Ident
9996            || !tokens[name_index].text.eq_ignore_ascii_case("export")
9997        {
9998            continue;
9999        }
10000        if let Some((open, close)) = find_block_after_header(tokens, name_index + 1, tokens.len()) {
10001            collect_icss_export_edges(tokens, open + 1, close, &mut edges);
10002        }
10003    }
10004    edges
10005}
10006
10007fn collect_icss_export_edges(
10008    tokens: &[Token<'_>],
10009    start: usize,
10010    end: usize,
10011    edges: &mut Vec<ParsedIcssExportEdgeFact>,
10012) {
10013    let mut index = start;
10014    while index < end {
10015        let token = tokens[index];
10016        if matches!(
10017            token.kind,
10018            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10019        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10020            && tokens[colon_index].kind == SyntaxKind::Colon
10021        {
10022            let value_end = css_module_value_statement_end(tokens, colon_index + 1).min(end);
10023            let reference_names = collect_css_module_value_definition_edge_names(
10024                tokens,
10025                colon_index + 1,
10026                value_end,
10027                css_module_value_reference_token_can_be_name,
10028            );
10029            if !reference_names.is_empty() {
10030                let range_end = value_end
10031                    .checked_sub(1)
10032                    .and_then(|end| tokens.get(end))
10033                    .map(|token| token.range.end())
10034                    .unwrap_or_else(|| token.range.end());
10035                edges.push(ParsedIcssExportEdgeFact {
10036                    export_name: token.text.to_string(),
10037                    reference_names,
10038                    range: TextRange::new(token.range.start(), range_end),
10039                });
10040            }
10041            index = value_end;
10042            continue;
10043        }
10044        index += 1;
10045    }
10046}
10047
10048fn icss_import_edge_source(tokens: &[Token<'_>], start: usize) -> Option<String> {
10049    let open_index = next_non_trivia_token_index_until(tokens, start, tokens.len())?;
10050    if tokens[open_index].kind != SyntaxKind::LeftParen {
10051        return None;
10052    }
10053    let source_index = next_non_trivia_token_index_until(tokens, open_index + 1, tokens.len())?;
10054    let token = tokens[source_index];
10055    matches!(
10056        token.kind,
10057        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10058    )
10059    .then(|| css_module_value_source_name(token))
10060}
10061
10062fn collect_icss_import_edges(
10063    tokens: &[Token<'_>],
10064    start: usize,
10065    end: usize,
10066    import_source: String,
10067    edges: &mut Vec<ParsedIcssImportEdgeFact>,
10068) {
10069    let mut index = start;
10070    while index < end {
10071        let token = tokens[index];
10072        if matches!(
10073            token.kind,
10074            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10075        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10076            && tokens[colon_index].kind == SyntaxKind::Colon
10077            && let Some(remote_index) =
10078                next_non_trivia_token_index_until(tokens, colon_index + 1, end)
10079            && matches!(
10080                tokens[remote_index].kind,
10081                SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10082            )
10083        {
10084            edges.push(ParsedIcssImportEdgeFact {
10085                local_name: token.text.to_string(),
10086                remote_name: tokens[remote_index].text.to_string(),
10087                import_source: import_source.clone(),
10088                range: token.range,
10089            });
10090            index = css_module_value_statement_end(tokens, colon_index + 1);
10091            continue;
10092        }
10093        index += 1;
10094    }
10095}
10096
10097fn collect_icss_export_names(
10098    tokens: &[Token<'_>],
10099    start: usize,
10100    end: usize,
10101    icss: &mut Vec<ParsedIcssFact>,
10102    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10103) {
10104    let mut index = start;
10105    while index < end {
10106        let token = tokens[index];
10107        if matches!(
10108            token.kind,
10109            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10110        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10111            && tokens[colon_index].kind == SyntaxKind::Colon
10112        {
10113            push_icss_fact(
10114                icss,
10115                seen,
10116                ParsedIcssFactKind::ExportName,
10117                token.text.to_string(),
10118                token.range,
10119            );
10120            index = css_module_value_statement_end(tokens, colon_index + 1);
10121            continue;
10122        }
10123        index += 1;
10124    }
10125}
10126
10127fn collect_icss_import_source(
10128    tokens: &[Token<'_>],
10129    start: usize,
10130    icss: &mut Vec<ParsedIcssFact>,
10131    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10132) {
10133    let Some(open_index) = next_non_trivia_token_index_until(tokens, start, tokens.len()) else {
10134        return;
10135    };
10136    if tokens[open_index].kind != SyntaxKind::LeftParen {
10137        return;
10138    }
10139    let Some(source_index) =
10140        next_non_trivia_token_index_until(tokens, open_index + 1, tokens.len())
10141    else {
10142        return;
10143    };
10144    let token = tokens[source_index];
10145    if matches!(
10146        token.kind,
10147        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10148    ) {
10149        push_icss_fact(
10150            icss,
10151            seen,
10152            ParsedIcssFactKind::ImportSource,
10153            css_module_value_source_name(token),
10154            token.range,
10155        );
10156    }
10157}
10158
10159fn collect_icss_import_names(
10160    tokens: &[Token<'_>],
10161    start: usize,
10162    end: usize,
10163    icss: &mut Vec<ParsedIcssFact>,
10164    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10165) {
10166    let mut index = start;
10167    while index < end {
10168        let token = tokens[index];
10169        if matches!(
10170            token.kind,
10171            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10172        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10173            && tokens[colon_index].kind == SyntaxKind::Colon
10174        {
10175            push_icss_fact(
10176                icss,
10177                seen,
10178                ParsedIcssFactKind::ImportLocalName,
10179                token.text.to_string(),
10180                token.range,
10181            );
10182            if let Some(remote_index) =
10183                next_non_trivia_token_index_until(tokens, colon_index + 1, end)
10184                && matches!(
10185                    tokens[remote_index].kind,
10186                    SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10187                )
10188            {
10189                push_icss_fact(
10190                    icss,
10191                    seen,
10192                    ParsedIcssFactKind::ImportRemoteName,
10193                    tokens[remote_index].text.to_string(),
10194                    tokens[remote_index].range,
10195                );
10196            }
10197            index = css_module_value_statement_end(tokens, colon_index + 1);
10198            continue;
10199        }
10200        index += 1;
10201    }
10202}
10203
10204fn push_icss_fact(
10205    icss: &mut Vec<ParsedIcssFact>,
10206    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10207    kind: ParsedIcssFactKind,
10208    name: String,
10209    range: TextRange,
10210) {
10211    if seen.insert((
10212        kind,
10213        name.clone(),
10214        u32::from(range.start()),
10215        u32::from(range.end()),
10216    )) {
10217        icss.push(ParsedIcssFact { kind, name, range });
10218    }
10219}
10220
10221fn collect_animation_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedAnimationFact> {
10222    let mut animations = Vec::new();
10223    let mut seen = BTreeSet::new();
10224    for (index, token) in tokens.iter().enumerate() {
10225        if token.kind == SyntaxKind::AtKeyword && token.text.eq_ignore_ascii_case("@keyframes") {
10226            if let Some(name_index) =
10227                next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10228                && let Some(name) = animation_name_from_token(tokens[name_index])
10229            {
10230                push_animation_fact(
10231                    &mut animations,
10232                    &mut seen,
10233                    ParsedAnimationFactKind::KeyframesDeclaration,
10234                    name,
10235                    tokens[name_index].range,
10236                );
10237            }
10238            continue;
10239        }
10240
10241        if token.kind == SyntaxKind::Ident
10242            && token.text.eq_ignore_ascii_case("animation-name")
10243            && let Some(colon_index) =
10244                next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10245            && tokens[colon_index].kind == SyntaxKind::Colon
10246        {
10247            collect_animation_name_references_until(
10248                tokens,
10249                colon_index + 1,
10250                &mut animations,
10251                &mut seen,
10252            );
10253        }
10254
10255        if token.kind == SyntaxKind::Ident
10256            && token.text.eq_ignore_ascii_case("animation")
10257            && let Some(colon_index) =
10258                next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10259            && tokens[colon_index].kind == SyntaxKind::Colon
10260        {
10261            collect_animation_shorthand_references_until(
10262                tokens,
10263                colon_index + 1,
10264                &mut animations,
10265                &mut seen,
10266            );
10267        }
10268    }
10269    animations
10270}
10271
10272fn collect_animation_name_references_until(
10273    tokens: &[Token<'_>],
10274    start: usize,
10275    animations: &mut Vec<ParsedAnimationFact>,
10276    seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10277) {
10278    let mut index = start;
10279    let mut paren_depth = 0usize;
10280    let mut bracket_depth = 0usize;
10281    while index < tokens.len() {
10282        match tokens[index].kind {
10283            SyntaxKind::LeftParen => paren_depth += 1,
10284            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
10285            SyntaxKind::LeftBracket => bracket_depth += 1,
10286            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
10287            SyntaxKind::Semicolon
10288            | SyntaxKind::SassOptionalSemicolon
10289            | SyntaxKind::RightBrace
10290            | SyntaxKind::SassDedent
10291                if paren_depth == 0 && bracket_depth == 0 =>
10292            {
10293                break;
10294            }
10295            _ => {}
10296        }
10297
10298        if paren_depth == 0
10299            && bracket_depth == 0
10300            && let Some(name) = animation_name_from_token(tokens[index])
10301        {
10302            push_animation_fact(
10303                animations,
10304                seen,
10305                ParsedAnimationFactKind::AnimationNameReference,
10306                name,
10307                tokens[index].range,
10308            );
10309        }
10310        index += 1;
10311    }
10312}
10313
10314fn collect_animation_shorthand_references_until(
10315    tokens: &[Token<'_>],
10316    start: usize,
10317    animations: &mut Vec<ParsedAnimationFact>,
10318    seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10319) {
10320    let mut index = start;
10321    let mut paren_depth = 0usize;
10322    let mut bracket_depth = 0usize;
10323    while index < tokens.len() {
10324        match tokens[index].kind {
10325            SyntaxKind::LeftParen => paren_depth += 1,
10326            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
10327            SyntaxKind::LeftBracket => bracket_depth += 1,
10328            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
10329            SyntaxKind::Semicolon
10330            | SyntaxKind::SassOptionalSemicolon
10331            | SyntaxKind::RightBrace
10332            | SyntaxKind::SassDedent
10333                if paren_depth == 0 && bracket_depth == 0 =>
10334            {
10335                break;
10336            }
10337            _ => {}
10338        }
10339
10340        if paren_depth == 0
10341            && bracket_depth == 0
10342            && animation_shorthand_token_can_be_name(tokens, index)
10343            && let Some(name) = animation_name_from_token(tokens[index])
10344        {
10345            push_animation_fact(
10346                animations,
10347                seen,
10348                ParsedAnimationFactKind::AnimationNameReference,
10349                name,
10350                tokens[index].range,
10351            );
10352        }
10353        index += 1;
10354    }
10355}
10356
10357fn animation_shorthand_token_can_be_name(tokens: &[Token<'_>], index: usize) -> bool {
10358    let token = tokens[index];
10359    if token.kind == SyntaxKind::String {
10360        return true;
10361    }
10362    if token.kind != SyntaxKind::Ident {
10363        return false;
10364    }
10365    if let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10366        && tokens[next_index].kind == SyntaxKind::LeftParen
10367    {
10368        return false;
10369    }
10370    !animation_shorthand_ident_is_non_name(token.text)
10371}
10372
10373fn animation_shorthand_ident_is_non_name(name: &str) -> bool {
10374    matches!(
10375        name.to_ascii_lowercase().as_str(),
10376        "ease"
10377            | "ease-in"
10378            | "ease-out"
10379            | "ease-in-out"
10380            | "linear"
10381            | "step-start"
10382            | "step-end"
10383            | "infinite"
10384            | "normal"
10385            | "reverse"
10386            | "alternate"
10387            | "alternate-reverse"
10388            | "running"
10389            | "paused"
10390            | "forwards"
10391            | "backwards"
10392            | "both"
10393            | "replace"
10394            | "add"
10395            | "accumulate"
10396            | "auto"
10397    )
10398}
10399
10400fn push_animation_fact(
10401    animations: &mut Vec<ParsedAnimationFact>,
10402    seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10403    kind: ParsedAnimationFactKind,
10404    name: String,
10405    range: TextRange,
10406) {
10407    if seen.insert((
10408        kind,
10409        name.clone(),
10410        u32::from(range.start()),
10411        u32::from(range.end()),
10412    )) {
10413        animations.push(ParsedAnimationFact { kind, name, range });
10414    }
10415}
10416
10417fn animation_name_from_token(token: Token<'_>) -> Option<String> {
10418    if !matches!(token.kind, SyntaxKind::Ident | SyntaxKind::String) {
10419        return None;
10420    }
10421    let name = token
10422        .text
10423        .trim_matches(|character| character == '"' || character == '\'')
10424        .to_string();
10425    if name.is_empty() || animation_name_is_reserved(&name) {
10426        return None;
10427    }
10428    Some(name)
10429}
10430
10431fn animation_name_is_reserved(name: &str) -> bool {
10432    matches!(
10433        name.to_ascii_lowercase().as_str(),
10434        "none" | "initial" | "inherit" | "unset" | "revert" | "revert-layer"
10435    )
10436}
10437
10438fn containing_at_rule_header_name<'text>(
10439    tokens: &'text [Token<'text>],
10440    index: usize,
10441) -> Option<&'text str> {
10442    let mut current = index;
10443    while current > 0 {
10444        current -= 1;
10445        let token = tokens.get(current)?;
10446        if token.kind.is_trivia() {
10447            continue;
10448        }
10449        if matches!(
10450            token.kind,
10451            SyntaxKind::Semicolon
10452                | SyntaxKind::SassOptionalSemicolon
10453                | SyntaxKind::LeftBrace
10454                | SyntaxKind::RightBrace
10455                | SyntaxKind::SassIndent
10456                | SyntaxKind::SassDedent
10457        ) {
10458            return None;
10459        }
10460        if token.kind == SyntaxKind::AtKeyword {
10461            return Some(token.text);
10462        }
10463    }
10464    None
10465}
10466
10467fn skip_trivia_tokens(tokens: &[Token<'_>], mut index: usize, end: usize) -> usize {
10468    while index < end && tokens[index].kind.is_trivia() {
10469        index += 1;
10470    }
10471    index
10472}
10473
10474fn skip_statement(tokens: &[Token<'_>], mut index: usize, end: usize) -> usize {
10475    while index < end {
10476        match tokens[index].kind {
10477            SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon => return index + 1,
10478            SyntaxKind::RightBrace | SyntaxKind::SassDedent => return index,
10479            _ => index += 1,
10480        }
10481    }
10482    index
10483}
10484
10485fn find_block_after_header(
10486    tokens: &[Token<'_>],
10487    start: usize,
10488    end: usize,
10489) -> Option<(usize, usize)> {
10490    let mut index = start;
10491    while index < end {
10492        match tokens[index].kind {
10493            SyntaxKind::Semicolon
10494            | SyntaxKind::SassOptionalSemicolon
10495            | SyntaxKind::RightBrace
10496            | SyntaxKind::SassDedent => return None,
10497            SyntaxKind::LeftBrace => {
10498                let close = matching_right_brace(tokens, index, end)?;
10499                return Some((index, close));
10500            }
10501            SyntaxKind::SassIndent => {
10502                let close = matching_sass_dedent(tokens, index, end)?;
10503                return Some((index, close));
10504            }
10505            _ => index += 1,
10506        }
10507    }
10508    None
10509}
10510
10511fn matching_right_brace(tokens: &[Token<'_>], open: usize, end: usize) -> Option<usize> {
10512    let mut depth = 0usize;
10513    let mut index = open;
10514    while index < end {
10515        match tokens[index].kind {
10516            SyntaxKind::LeftBrace => depth += 1,
10517            SyntaxKind::RightBrace => {
10518                depth = depth.saturating_sub(1);
10519                if depth == 0 {
10520                    return Some(index);
10521                }
10522            }
10523            _ => {}
10524        }
10525        index += 1;
10526    }
10527    None
10528}
10529
10530fn matching_sass_dedent(tokens: &[Token<'_>], open: usize, end: usize) -> Option<usize> {
10531    let mut depth = 0usize;
10532    let mut index = open;
10533    while index < end {
10534        match tokens[index].kind {
10535            SyntaxKind::SassIndent => depth += 1,
10536            SyntaxKind::SassDedent => {
10537                depth = depth.saturating_sub(1);
10538                if depth == 0 {
10539                    return Some(index);
10540                }
10541            }
10542            _ => {}
10543        }
10544        index += 1;
10545    }
10546    None
10547}
10548
10549fn style_wrapper_at_rule(name: &str) -> bool {
10550    matches_ignore_ascii_case(
10551        name,
10552        &[
10553            "@media",
10554            "@supports",
10555            "@when",
10556            "@else",
10557            "@layer",
10558            "@scope",
10559            "@container",
10560            "@starting-style",
10561            "@if",
10562            "@else",
10563            "@for",
10564            "@each",
10565            "@while",
10566            "@at-root",
10567            "@include",
10568        ],
10569    )
10570}
10571
10572fn is_selector_combinator_kind(kind: SyntaxKind) -> bool {
10573    matches!(
10574        kind,
10575        SyntaxKind::GreaterThan
10576            | SyntaxKind::Plus
10577            | SyntaxKind::Tilde
10578            | SyntaxKind::ColumnCombinator
10579            | SyntaxKind::DoublePipe
10580    )
10581}
10582
10583fn selector_component_can_start(kind: SyntaxKind) -> bool {
10584    matches!(
10585        kind,
10586        SyntaxKind::Dot
10587            | SyntaxKind::Hash
10588            | SyntaxKind::Ident
10589            | SyntaxKind::Star
10590            | SyntaxKind::Ampersand
10591            | SyntaxKind::ScssPlaceholder
10592            | SyntaxKind::LeftBracket
10593            | SyntaxKind::Colon
10594            | SyntaxKind::DoubleColon
10595    )
10596}
10597
10598fn namespace_selector_target_can_start(kind: SyntaxKind) -> bool {
10599    matches!(
10600        kind,
10601        SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star
10602    )
10603}
10604
10605fn keyframe_selector_token_is_valid(token: Token<'_>) -> bool {
10606    token.kind == SyntaxKind::Percentage
10607        || (token.kind == SyntaxKind::Ident
10608            && (token.text.eq_ignore_ascii_case("from") || token.text.eq_ignore_ascii_case("to")))
10609}
10610
10611fn selector_component_can_end(kind: SyntaxKind) -> bool {
10612    matches!(
10613        kind,
10614        SyntaxKind::Ident
10615            | SyntaxKind::CustomPropertyName
10616            | SyntaxKind::Hash
10617            | SyntaxKind::RightBracket
10618            | SyntaxKind::RightParen
10619            | SyntaxKind::Star
10620    )
10621}
10622
10623fn collect_at_rule_facts_from_tokens(
10624    tokens: &[Token<'_>],
10625    dialect: StyleDialect,
10626) -> Vec<ParsedAtRuleFact> {
10627    tokens
10628        .iter()
10629        .filter(|token| token.kind == SyntaxKind::AtKeyword)
10630        .map(|token| {
10631            let css_spec = at_rule_spec(token.text);
10632            let node_kind = css_spec
10633                .or_else(|| match dialect {
10634                    StyleDialect::Scss | StyleDialect::Sass => scss_at_rule_spec(token.text),
10635                    StyleDialect::Css | StyleDialect::Less => None,
10636                })
10637                .map(|spec| spec.node_kind);
10638            let name = if css_spec.is_some() {
10639                token.text.to_ascii_lowercase()
10640            } else {
10641                token.text.to_string()
10642            };
10643            ParsedAtRuleFact {
10644                name,
10645                node_kind,
10646                range: token.range,
10647            }
10648        })
10649        .collect()
10650}
10651
10652fn next_non_trivia_token<'text>(
10653    tokens: &'text [Token<'text>],
10654    mut index: usize,
10655) -> Option<Token<'text>> {
10656    while let Some(token) = tokens.get(index).copied() {
10657        if !token.kind.is_trivia() {
10658            return Some(token);
10659        }
10660        index += 1;
10661    }
10662    None
10663}
10664
10665fn next_non_trivia_token_until<'text>(
10666    tokens: &'text [Token<'text>],
10667    mut index: usize,
10668    end: usize,
10669) -> Option<Token<'text>> {
10670    while index < end {
10671        let token = tokens.get(index).copied()?;
10672        if !token.kind.is_trivia() {
10673            return Some(token);
10674        }
10675        index += 1;
10676    }
10677    None
10678}
10679
10680fn next_non_trivia_token_index_until(
10681    tokens: &[Token<'_>],
10682    mut index: usize,
10683    end: usize,
10684) -> Option<usize> {
10685    while index < end {
10686        let token = tokens.get(index)?;
10687        if !token.kind.is_trivia() {
10688            return Some(index);
10689        }
10690        index += 1;
10691    }
10692    None
10693}
10694
10695fn next_non_trivia_token_after_range<'text>(
10696    tokens: &'text [Token<'text>],
10697    range: TextRange,
10698    end: usize,
10699) -> Option<Token<'text>> {
10700    let index = token_index_by_range(tokens, range)?;
10701    next_non_trivia_token_until(tokens, index + 1, end)
10702}
10703
10704fn token_index_by_range(tokens: &[Token<'_>], range: TextRange) -> Option<usize> {
10705    tokens.iter().position(|token| token.range == range)
10706}
10707
10708fn matching_right_paren_from_range(
10709    tokens: &[Token<'_>],
10710    open_range: TextRange,
10711    end: usize,
10712) -> Option<usize> {
10713    let mut depth = 0usize;
10714    let mut index = token_index_by_range(tokens, open_range)?;
10715    while index < end {
10716        match tokens[index].kind {
10717            SyntaxKind::LeftParen => depth += 1,
10718            SyntaxKind::RightParen => {
10719                depth = depth.saturating_sub(1);
10720                if depth == 0 {
10721                    return Some(index);
10722                }
10723            }
10724            _ => {}
10725        }
10726        index += 1;
10727    }
10728    None
10729}
10730
10731fn previous_non_trivia_token<'text>(
10732    tokens: &'text [Token<'text>],
10733    start: usize,
10734    index: usize,
10735) -> Option<Token<'text>> {
10736    let mut current = index;
10737    while current > start {
10738        current -= 1;
10739        let token = tokens.get(current).copied()?;
10740        if !token.kind.is_trivia() {
10741            return Some(token);
10742        }
10743    }
10744    None
10745}
10746
10747fn at_rule_spec(text: &str) -> Option<AtRuleSpec> {
10748    let lowered = text.to_ascii_lowercase();
10749    let (node_kind, block_kind) = match lowered.as_str() {
10750        "@media" => (SyntaxKind::MediaRule, AtRuleBlockKind::GroupRuleList),
10751        "@supports" => (SyntaxKind::SupportsRule, AtRuleBlockKind::GroupRuleList),
10752        "@when" => (SyntaxKind::WhenRule, AtRuleBlockKind::GroupRuleList),
10753        "@else" => (SyntaxKind::ElseRule, AtRuleBlockKind::GroupRuleList),
10754        "@container" => (SyntaxKind::ContainerRule, AtRuleBlockKind::GroupRuleList),
10755        "@layer" => (SyntaxKind::LayerRule, AtRuleBlockKind::GroupRuleList),
10756        "@scope" => (SyntaxKind::ScopeRule, AtRuleBlockKind::GroupRuleList),
10757        "@starting-style" => (
10758            SyntaxKind::StartingStyleRule,
10759            AtRuleBlockKind::GroupRuleList,
10760        ),
10761        "@nest" => (SyntaxKind::NestRule, AtRuleBlockKind::DeclarationList),
10762        "@keyframes" => (SyntaxKind::KeyframesRule, AtRuleBlockKind::Keyframes),
10763        "@font-face" => (SyntaxKind::FontFaceRule, AtRuleBlockKind::DeclarationList),
10764        "@page" => (SyntaxKind::PageRule, AtRuleBlockKind::DeclarationList),
10765        "@property" => (SyntaxKind::PropertyRule, AtRuleBlockKind::DeclarationList),
10766        "@counter-style" => (
10767            SyntaxKind::CounterStyleRule,
10768            AtRuleBlockKind::DeclarationList,
10769        ),
10770        "@font-palette-values" => (
10771            SyntaxKind::FontPaletteValuesRule,
10772            AtRuleBlockKind::DeclarationList,
10773        ),
10774        "@color-profile" => (
10775            SyntaxKind::ColorProfileRule,
10776            AtRuleBlockKind::DeclarationList,
10777        ),
10778        "@position-try" => (
10779            SyntaxKind::PositionTryRule,
10780            AtRuleBlockKind::DeclarationList,
10781        ),
10782        "@font-feature-values" => (
10783            SyntaxKind::FontFeatureValuesRule,
10784            AtRuleBlockKind::GroupRuleList,
10785        ),
10786        "@stylistic" => (
10787            SyntaxKind::FontFeatureValuesStylisticRule,
10788            AtRuleBlockKind::DeclarationList,
10789        ),
10790        "@styleset" => (
10791            SyntaxKind::FontFeatureValuesStylesetRule,
10792            AtRuleBlockKind::DeclarationList,
10793        ),
10794        "@character-variant" => (
10795            SyntaxKind::FontFeatureValuesCharacterVariantRule,
10796            AtRuleBlockKind::DeclarationList,
10797        ),
10798        "@swash" => (
10799            SyntaxKind::FontFeatureValuesSwashRule,
10800            AtRuleBlockKind::DeclarationList,
10801        ),
10802        "@ornaments" => (
10803            SyntaxKind::FontFeatureValuesOrnamentsRule,
10804            AtRuleBlockKind::DeclarationList,
10805        ),
10806        "@annotation" => (
10807            SyntaxKind::FontFeatureValuesAnnotationRule,
10808            AtRuleBlockKind::DeclarationList,
10809        ),
10810        "@historical-forms" => (
10811            SyntaxKind::FontFeatureValuesHistoricalFormsRule,
10812            AtRuleBlockKind::DeclarationList,
10813        ),
10814        "@view-transition" => (
10815            SyntaxKind::ViewTransitionRule,
10816            AtRuleBlockKind::DeclarationList,
10817        ),
10818        "@charset" => (SyntaxKind::CharsetRule, AtRuleBlockKind::Raw),
10819        "@import" => (SyntaxKind::ImportRule, AtRuleBlockKind::Raw),
10820        "@namespace" => (SyntaxKind::NamespaceRule, AtRuleBlockKind::Raw),
10821        "@custom-media" => (SyntaxKind::CustomMediaRule, AtRuleBlockKind::Raw),
10822        text if is_page_margin_at_rule(text) => {
10823            (SyntaxKind::PageMarginRule, AtRuleBlockKind::DeclarationList)
10824        }
10825        _ => return None,
10826    };
10827    Some(AtRuleSpec {
10828        node_kind,
10829        block_kind,
10830    })
10831}
10832
10833fn is_page_margin_at_rule(text: &str) -> bool {
10834    matches!(
10835        text,
10836        "@top-left-corner"
10837            | "@top-left"
10838            | "@top-center"
10839            | "@top-right"
10840            | "@top-right-corner"
10841            | "@bottom-left-corner"
10842            | "@bottom-left"
10843            | "@bottom-center"
10844            | "@bottom-right"
10845            | "@bottom-right-corner"
10846            | "@left-top"
10847            | "@left-middle"
10848            | "@left-bottom"
10849            | "@right-top"
10850            | "@right-middle"
10851            | "@right-bottom"
10852    )
10853}
10854
10855fn scss_at_rule_spec(text: &str) -> Option<AtRuleSpec> {
10856    let (node_kind, block_kind) = match text {
10857        "@use" => (SyntaxKind::ScssUseRule, AtRuleBlockKind::Raw),
10858        "@forward" => (SyntaxKind::ScssForwardRule, AtRuleBlockKind::Raw),
10859        "@mixin" => (
10860            SyntaxKind::ScssMixinDeclaration,
10861            AtRuleBlockKind::DeclarationList,
10862        ),
10863        "@include" => (
10864            SyntaxKind::ScssIncludeRule,
10865            AtRuleBlockKind::DeclarationList,
10866        ),
10867        "@function" => (
10868            SyntaxKind::ScssFunctionDeclaration,
10869            AtRuleBlockKind::DeclarationList,
10870        ),
10871        "@return" => (SyntaxKind::ScssReturnRule, AtRuleBlockKind::Raw),
10872        "@extend" => (SyntaxKind::ScssExtendRule, AtRuleBlockKind::Raw),
10873        "@if" => (SyntaxKind::ScssControlIf, AtRuleBlockKind::DeclarationList),
10874        "@else" => (
10875            SyntaxKind::ScssControlElse,
10876            AtRuleBlockKind::DeclarationList,
10877        ),
10878        "@each" => (
10879            SyntaxKind::ScssControlEach,
10880            AtRuleBlockKind::DeclarationList,
10881        ),
10882        "@for" => (SyntaxKind::ScssControlFor, AtRuleBlockKind::DeclarationList),
10883        "@while" => (
10884            SyntaxKind::ScssControlWhile,
10885            AtRuleBlockKind::DeclarationList,
10886        ),
10887        "@at-root" => (SyntaxKind::ScssAtRootRule, AtRuleBlockKind::DeclarationList),
10888        "@error" => (SyntaxKind::ScssErrorRule, AtRuleBlockKind::Raw),
10889        "@warn" => (SyntaxKind::ScssWarnRule, AtRuleBlockKind::Raw),
10890        "@debug" => (SyntaxKind::ScssDebugRule, AtRuleBlockKind::Raw),
10891        "@content" => (SyntaxKind::ScssContentRule, AtRuleBlockKind::Raw),
10892        _ => return None,
10893    };
10894    Some(AtRuleSpec {
10895        node_kind,
10896        block_kind,
10897    })
10898}
10899
10900fn is_selector_boundary(kind: SyntaxKind) -> bool {
10901    matches!(
10902        kind,
10903        SyntaxKind::Comma
10904            | SyntaxKind::LeftBrace
10905            | SyntaxKind::SassIndent
10906            | SyntaxKind::RightBrace
10907            | SyntaxKind::SassDedent
10908            | SyntaxKind::Semicolon
10909            | SyntaxKind::SassOptionalSemicolon
10910    )
10911}
10912
10913fn is_selector_boundary_until(kind: SyntaxKind, recovery: &[SyntaxKind]) -> bool {
10914    is_selector_boundary(kind) || recovery.contains(&kind)
10915}
10916
10917fn is_selector_list_pseudo_class(text: &str) -> bool {
10918    matches!(text, "is" | "where" | "local" | "global")
10919}
10920
10921fn is_nth_pseudo_class(text: &str) -> bool {
10922    matches!(
10923        text,
10924        "nth-child" | "nth-last-child" | "nth-of-type" | "nth-last-of-type"
10925    )
10926}
10927
10928fn language_tag_token_can_start(kind: SyntaxKind) -> bool {
10929    matches!(kind, SyntaxKind::Ident | SyntaxKind::String)
10930}
10931
10932fn selector_item_token_is_recoverable(kind: SyntaxKind) -> bool {
10933    matches!(
10934        kind,
10935        SyntaxKind::Whitespace
10936            | SyntaxKind::SassIndentedNewline
10937            | SyntaxKind::Dot
10938            | SyntaxKind::Comma
10939            | SyntaxKind::Hash
10940            | SyntaxKind::Ident
10941            | SyntaxKind::CustomPropertyName
10942            | SyntaxKind::String
10943            | SyntaxKind::Number
10944            | SyntaxKind::Percentage
10945            | SyntaxKind::Dimension
10946            | SyntaxKind::Star
10947            | SyntaxKind::Ampersand
10948            | SyntaxKind::ScssPlaceholder
10949            | SyntaxKind::LeftBracket
10950            | SyntaxKind::RightBracket
10951            | SyntaxKind::Colon
10952            | SyntaxKind::DoubleColon
10953            | SyntaxKind::LeftParen
10954            | SyntaxKind::RightParen
10955            | SyntaxKind::Equals
10956            | SyntaxKind::IncludesMatch
10957            | SyntaxKind::DashMatch
10958            | SyntaxKind::PrefixMatch
10959            | SyntaxKind::SuffixMatch
10960            | SyntaxKind::SubstringMatch
10961            | SyntaxKind::Pipe
10962            | SyntaxKind::ColumnCombinator
10963            | SyntaxKind::GreaterThan
10964            | SyntaxKind::Plus
10965            | SyntaxKind::Minus
10966            | SyntaxKind::Tilde
10967            | SyntaxKind::KeywordAnd
10968            | SyntaxKind::KeywordOr
10969            | SyntaxKind::KeywordNot
10970    )
10971}
10972
10973fn is_at_rule_prelude_boundary(kind: SyntaxKind) -> bool {
10974    matches!(
10975        kind,
10976        SyntaxKind::LeftBrace
10977            | SyntaxKind::SassIndent
10978            | SyntaxKind::Semicolon
10979            | SyntaxKind::SassOptionalSemicolon
10980    )
10981}
10982
10983fn is_statement_end(kind: SyntaxKind) -> bool {
10984    matches!(
10985        kind,
10986        SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon
10987    )
10988}
10989
10990fn sass_token_can_end_statement(kind: SyntaxKind) -> bool {
10991    !matches!(
10992        kind,
10993        SyntaxKind::Whitespace
10994            | SyntaxKind::LineComment
10995            | SyntaxKind::BlockComment
10996            | SyntaxKind::SassIndentedNewline
10997            | SyntaxKind::SassIndent
10998            | SyntaxKind::SassDedent
10999            | SyntaxKind::SassOptionalSemicolon
11000            | SyntaxKind::Comma
11001            | SyntaxKind::Colon
11002            | SyntaxKind::DoubleColon
11003            | SyntaxKind::LeftBrace
11004            | SyntaxKind::LeftParen
11005            | SyntaxKind::LeftBracket
11006            | SyntaxKind::Plus
11007            | SyntaxKind::Minus
11008            | SyntaxKind::Star
11009            | SyntaxKind::Slash
11010            | SyntaxKind::GreaterThan
11011            | SyntaxKind::LessThan
11012            | SyntaxKind::Equals
11013            | SyntaxKind::Arrow
11014            | SyntaxKind::Pipe
11015            | SyntaxKind::Tilde
11016            | SyntaxKind::Caret
11017            | SyntaxKind::Ampersand
11018            | SyntaxKind::DoubleAmpersand
11019            | SyntaxKind::ColumnCombinator
11020            | SyntaxKind::IncludesMatch
11021            | SyntaxKind::DashMatch
11022            | SyntaxKind::PrefixMatch
11023            | SyntaxKind::SuffixMatch
11024            | SyntaxKind::SubstringMatch
11025            | SyntaxKind::PlusEquals
11026            | SyntaxKind::MinusEquals
11027            | SyntaxKind::SlashEquals
11028    )
11029}
11030
11031fn function_argument_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11032    let mut kinds = vec![SyntaxKind::RightParen];
11033    for kind in recovery {
11034        if !kinds.contains(kind) {
11035            kinds.push(*kind);
11036        }
11037    }
11038    kinds
11039}
11040
11041fn bracketed_value_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11042    let mut kinds = vec![SyntaxKind::RightBracket];
11043    for kind in recovery {
11044        if !kinds.contains(kind) {
11045            kinds.push(*kind);
11046        }
11047    }
11048    kinds
11049}
11050
11051fn simple_block_recovery(close_kind: SyntaxKind, recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11052    let mut kinds = vec![close_kind];
11053    for kind in recovery {
11054        if !kinds.contains(kind) {
11055            kinds.push(*kind);
11056        }
11057    }
11058    kinds
11059}
11060
11061fn matching_simple_block_close(open_kind: SyntaxKind) -> Option<SyntaxKind> {
11062    match open_kind {
11063        SyntaxKind::LeftBrace => Some(SyntaxKind::RightBrace),
11064        SyntaxKind::LeftBracket => Some(SyntaxKind::RightBracket),
11065        SyntaxKind::LeftParen => Some(SyntaxKind::RightParen),
11066        _ => None,
11067    }
11068}
11069
11070fn value_list_item_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11071    let mut kinds = vec![SyntaxKind::Comma];
11072    for kind in recovery {
11073        if !kinds.contains(kind) {
11074            kinds.push(*kind);
11075        }
11076    }
11077    kinds
11078}
11079
11080fn comma_separated_component_value_list_item_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11081    let mut kinds = vec![SyntaxKind::Comma];
11082    for kind in recovery {
11083        if !kinds.contains(kind) {
11084            kinds.push(*kind);
11085        }
11086    }
11087    kinds
11088}
11089
11090fn variable_declaration_node_kind(kind: SyntaxKind, has_colon: bool) -> SyntaxKind {
11091    if has_colon {
11092        return kind;
11093    }
11094    match kind {
11095        SyntaxKind::ScssVariableDeclaration => SyntaxKind::BogusScssVariable,
11096        SyntaxKind::LessVariableDeclaration => SyntaxKind::BogusLessVariable,
11097        _ => kind,
11098    }
11099}
11100
11101fn is_attribute_matcher(kind: SyntaxKind) -> bool {
11102    matches!(
11103        kind,
11104        SyntaxKind::Equals
11105            | SyntaxKind::IncludesMatch
11106            | SyntaxKind::DashMatch
11107            | SyntaxKind::PrefixMatch
11108            | SyntaxKind::SuffixMatch
11109            | SyntaxKind::SubstringMatch
11110    )
11111}
11112
11113fn attribute_name_token_can_start(kind: SyntaxKind) -> bool {
11114    matches!(
11115        kind,
11116        SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star
11117    )
11118}
11119
11120fn attribute_name_token_can_continue(kind: SyntaxKind) -> bool {
11121    matches!(
11122        kind,
11123        SyntaxKind::Ident
11124            | SyntaxKind::CustomPropertyName
11125            | SyntaxKind::Star
11126            | SyntaxKind::Pipe
11127            | SyntaxKind::ColumnCombinator
11128    )
11129}
11130
11131fn attribute_value_token_can_start(kind: SyntaxKind) -> bool {
11132    matches!(
11133        kind,
11134        SyntaxKind::Ident
11135            | SyntaxKind::CustomPropertyName
11136            | SyntaxKind::String
11137            | SyntaxKind::Hash
11138            | SyntaxKind::Number
11139            | SyntaxKind::Dimension
11140    )
11141}
11142
11143fn is_combinator(kind: SyntaxKind) -> bool {
11144    matches!(
11145        kind,
11146        SyntaxKind::GreaterThan
11147            | SyntaxKind::Plus
11148            | SyntaxKind::Tilde
11149            | SyntaxKind::ColumnCombinator
11150    )
11151}
11152
11153fn infix_binding_power(kind: SyntaxKind) -> Option<(u8, u8)> {
11154    match kind {
11155        SyntaxKind::Plus | SyntaxKind::Minus => Some((1, 2)),
11156        SyntaxKind::Star | SyntaxKind::Slash | SyntaxKind::Percent => Some((3, 4)),
11157        _ => None,
11158    }
11159}
11160
11161fn specialized_function_kind(text: &str) -> Option<SyntaxKind> {
11162    if text.eq_ignore_ascii_case("var") {
11163        return Some(SyntaxKind::VarFunction);
11164    }
11165    if text.eq_ignore_ascii_case("calc") {
11166        return Some(SyntaxKind::CalcFunction);
11167    }
11168    if text.eq_ignore_ascii_case("env") {
11169        return Some(SyntaxKind::EnvFunction);
11170    }
11171    if text.eq_ignore_ascii_case("attr") {
11172        return Some(SyntaxKind::AttrFunction);
11173    }
11174    if matches_ignore_ascii_case(text, VALUES_L4_MATH_FUNCTION_NAMES) {
11175        return Some(SyntaxKind::MathFunction);
11176    }
11177    if matches_ignore_ascii_case(text, CSS_COLOR_FUNCTION_NAMES) {
11178        return Some(SyntaxKind::ColorValue);
11179    }
11180    if matches_ignore_ascii_case(text, CSS_GRADIENT_FUNCTION_NAMES) {
11181        return Some(SyntaxKind::GradientFunction);
11182    }
11183    if matches_ignore_ascii_case(text, CSS_TRANSFORM_FUNCTION_NAMES) {
11184        return Some(SyntaxKind::TransformFunction);
11185    }
11186    if matches_ignore_ascii_case(text, CSS_FILTER_FUNCTION_NAMES) {
11187        return Some(SyntaxKind::FilterFunction);
11188    }
11189    if matches_ignore_ascii_case(text, CSS_IMAGE_FUNCTION_NAMES) {
11190        return Some(SyntaxKind::ImageFunction);
11191    }
11192    if matches_ignore_ascii_case(text, CSS_SHAPE_FUNCTION_NAMES) {
11193        return Some(SyntaxKind::ShapeFunction);
11194    }
11195    None
11196}
11197
11198fn function_argument_count_is_valid(function_name: &str, argument_count: usize) -> bool {
11199    if function_name.eq_ignore_ascii_case("calc") {
11200        return argument_count == 1;
11201    }
11202    if matches_ignore_ascii_case(function_name, &["min", "max", "hypot"]) {
11203        return argument_count >= 1;
11204    }
11205    if function_name.eq_ignore_ascii_case("clamp") {
11206        return argument_count == 3;
11207    }
11208    if function_name.eq_ignore_ascii_case("round") {
11209        return (2..=3).contains(&argument_count);
11210    }
11211    if function_name.eq_ignore_ascii_case("log") {
11212        return (1..=2).contains(&argument_count);
11213    }
11214    if matches_ignore_ascii_case(function_name, &["mod", "rem", "pow", "atan2"]) {
11215        return argument_count == 2;
11216    }
11217    if matches_ignore_ascii_case(
11218        function_name,
11219        &[
11220            "sin", "cos", "tan", "asin", "acos", "atan", "sqrt", "exp", "abs", "sign",
11221        ],
11222    ) {
11223        return argument_count == 1;
11224    }
11225    if function_name.eq_ignore_ascii_case("color-mix") {
11226        return argument_count == 3;
11227    }
11228    if function_name.eq_ignore_ascii_case("light-dark") {
11229        return argument_count == 2;
11230    }
11231    if function_name.eq_ignore_ascii_case("contrast-color") {
11232        return argument_count == 1;
11233    }
11234    true
11235}
11236
11237fn function_requires_filled_top_level_arguments(function_name: &str) -> bool {
11238    function_name.eq_ignore_ascii_case("calc")
11239        || matches_ignore_ascii_case(function_name, VALUES_L4_MATH_FUNCTION_NAMES)
11240        || matches_ignore_ascii_case(
11241            function_name,
11242            &["color-mix", "light-dark", "contrast-color"],
11243        )
11244}
11245
11246fn at_rule_prelude_head_is_custom_property_name(kind: SyntaxKind) -> bool {
11247    kind == SyntaxKind::CustomPropertyName || is_interpolation_start(kind)
11248}
11249
11250fn at_rule_prelude_head_is_custom_ident(kind: SyntaxKind) -> bool {
11251    kind == SyntaxKind::Ident || is_interpolation_start(kind)
11252}
11253
11254fn is_dynamic_function_argument_head(kind: SyntaxKind) -> bool {
11255    matches!(
11256        kind,
11257        SyntaxKind::ScssVariable
11258            | SyntaxKind::LessVariable
11259            | SyntaxKind::ScssInterpolationStart
11260            | SyntaxKind::LessInterpolationStart
11261    )
11262}
11263
11264fn is_scss_module_source_token(kind: SyntaxKind) -> bool {
11265    matches!(
11266        kind,
11267        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::ScssInterpolationStart
11268    )
11269}
11270
11271fn is_scss_module_namespace_token(kind: SyntaxKind) -> bool {
11272    matches!(
11273        kind,
11274        SyntaxKind::Ident | SyntaxKind::Star | SyntaxKind::ScssInterpolationStart
11275    )
11276}
11277
11278fn is_scss_module_visibility_name_token(kind: SyntaxKind) -> bool {
11279    matches!(
11280        kind,
11281        SyntaxKind::Ident
11282            | SyntaxKind::ScssVariable
11283            | SyntaxKind::ScssPlaceholder
11284            | SyntaxKind::ScssInterpolationStart
11285    )
11286}
11287
11288fn is_css_module_from_source_token(kind: SyntaxKind, text: &str) -> bool {
11289    matches!(
11290        kind,
11291        SyntaxKind::String
11292            | SyntaxKind::Url
11293            | SyntaxKind::ScssInterpolationStart
11294            | SyntaxKind::LessInterpolationStart
11295    ) || (kind == SyntaxKind::Ident && text == "global")
11296}
11297
11298fn is_scss_control_rule_kind(kind: SyntaxKind) -> bool {
11299    matches!(
11300        kind,
11301        SyntaxKind::ScssControlIf
11302            | SyntaxKind::ScssControlElse
11303            | SyntaxKind::ScssControlEach
11304            | SyntaxKind::ScssControlFor
11305            | SyntaxKind::ScssControlWhile
11306    )
11307}
11308
11309fn matches_ignore_ascii_case(value: &str, candidates: &[&str]) -> bool {
11310    candidates
11311        .iter()
11312        .any(|candidate| value.eq_ignore_ascii_case(candidate))
11313}
11314
11315fn css_module_scope_function_kind(text: &str) -> Option<SyntaxKind> {
11316    match text {
11317        "local" => Some(SyntaxKind::CssModuleLocalBlock),
11318        "global" => Some(SyntaxKind::CssModuleGlobalBlock),
11319        _ => None,
11320    }
11321}
11322
11323fn text_range(start: usize, end: usize) -> TextRange {
11324    TextRange::new(TextSize::from(start as u32), TextSize::from(end as u32))
11325}
11326
11327#[cfg(test)]
11328mod tests {
11329    use super::*;
11330
11331    #[test]
11332    fn builds_cst_root_for_plain_css() {
11333        let result = parse(".button { color: red; }", StyleDialect::Css);
11334
11335        assert_eq!(result.syntax().kind(), SyntaxKind::Root);
11336        assert_eq!(result.dialect(), StyleDialect::Css);
11337        assert!(
11338            result.errors().is_empty(),
11339            "unexpected parse errors: {:?}",
11340            result.errors()
11341        );
11342        assert!(result.token_count() > 0);
11343
11344        let kinds = node_kinds(&result.syntax());
11345        assert!(kinds.contains(&SyntaxKind::Rule));
11346        assert!(kinds.contains(&SyntaxKind::SelectorList));
11347        assert!(kinds.contains(&SyntaxKind::DeclarationList));
11348        assert!(kinds.contains(&SyntaxKind::Declaration));
11349        assert!(kinds.contains(&SyntaxKind::PropertyName));
11350        assert!(kinds.contains(&SyntaxKind::Value));
11351    }
11352
11353    #[test]
11354    fn exposes_css_syntax_parser_entry_points() {
11355        let rule_list = parse_entry_point(
11356            ".button { color: red; } @media (width >= 1px) { .card { color: blue; } }",
11357            StyleDialect::Css,
11358            ParseEntryPoint::RuleList,
11359        );
11360        let rule = parse_entry_point(
11361            ".button { color: red; }",
11362            StyleDialect::Css,
11363            ParseEntryPoint::Rule,
11364        );
11365        let declaration_list = parse_entry_point(
11366            "color: red; width: calc(1px + 2px);",
11367            StyleDialect::Css,
11368            ParseEntryPoint::DeclarationList,
11369        );
11370        let declaration = parse_entry_point(
11371            "color: red;",
11372            StyleDialect::Css,
11373            ParseEntryPoint::Declaration,
11374        );
11375        let value = parse_entry_point(
11376            "clamp(1rem, calc(2px + 3px), 4rem)",
11377            StyleDialect::Css,
11378            ParseEntryPoint::Value,
11379        );
11380        let component_value = parse_entry_point(
11381            "calc(100% - var(--gap))",
11382            StyleDialect::Css,
11383            ParseEntryPoint::ComponentValue,
11384        );
11385        let component_value_list = parse_entry_point(
11386            "red + calc(1px + 2px) [data-state]",
11387            StyleDialect::Css,
11388            ParseEntryPoint::ComponentValueList,
11389        );
11390        let comma_separated_component_value_list = parse_entry_point(
11391            "red, calc(1px + 2px), [data-state]",
11392            StyleDialect::Css,
11393            ParseEntryPoint::CommaSeparatedComponentValueList,
11394        );
11395        let simple_block = parse_entry_point(
11396            "{ color: red; [data-state] }",
11397            StyleDialect::Css,
11398            ParseEntryPoint::SimpleBlock,
11399        );
11400        let unclosed_simple_block = parse_entry_point(
11401            "{ color: red",
11402            StyleDialect::Css,
11403            ParseEntryPoint::SimpleBlock,
11404        );
11405
11406        assert!(rule_list.errors().is_empty());
11407        assert!(rule.errors().is_empty());
11408        assert!(declaration_list.errors().is_empty());
11409        assert!(declaration.errors().is_empty());
11410        assert!(value.errors().is_empty());
11411        assert!(component_value.errors().is_empty());
11412        assert!(component_value_list.errors().is_empty());
11413        assert!(comma_separated_component_value_list.errors().is_empty());
11414        assert!(simple_block.errors().is_empty());
11415        assert_eq!(unclosed_simple_block.errors().len(), 1);
11416        assert!(node_kinds(&rule_list.syntax()).contains(&SyntaxKind::RuleList));
11417        assert!(node_kinds(&rule.syntax()).contains(&SyntaxKind::Rule));
11418        assert!(node_kinds(&declaration_list.syntax()).contains(&SyntaxKind::DeclarationList));
11419        assert!(node_kinds(&declaration.syntax()).contains(&SyntaxKind::Declaration));
11420        assert!(node_kinds(&value.syntax()).contains(&SyntaxKind::Value));
11421        assert!(node_kinds(&value.syntax()).contains(&SyntaxKind::CalcFunction));
11422        assert!(node_kinds(&component_value.syntax()).contains(&SyntaxKind::ComponentValue));
11423        assert!(node_kinds(&component_value.syntax()).contains(&SyntaxKind::FunctionCall));
11424        assert!(
11425            node_kinds(&component_value_list.syntax()).contains(&SyntaxKind::ComponentValueList)
11426        );
11427        assert!(
11428            node_kinds(&comma_separated_component_value_list.syntax())
11429                .contains(&SyntaxKind::CommaSeparatedComponentValueList)
11430        );
11431        assert!(node_kinds(&simple_block.syntax()).contains(&SyntaxKind::SimpleBlock));
11432        assert!(node_kinds(&simple_block.syntax()).contains(&SyntaxKind::ComponentValue));
11433        assert!(
11434            node_kinds(&unclosed_simple_block.syntax()).contains(&SyntaxKind::BogusSimpleBlock)
11435        );
11436    }
11437
11438    #[test]
11439    fn tokenizes_multibyte_source_without_boundary_errors() {
11440        let result = parse(".카드 { --간격: \"좋음\"; }", StyleDialect::Css);
11441
11442        assert!(
11443            result.errors().is_empty(),
11444            "unexpected parse errors: {:?}",
11445            result.errors()
11446        );
11447        assert!(result.token_count() >= 8);
11448    }
11449
11450    #[test]
11451    fn reports_unterminated_constructs_without_panicking() {
11452        let comment = parse("/* open", StyleDialect::Css);
11453        let string = parse(".a { content: \"open; }", StyleDialect::Css);
11454        let block = parse(".a { color: red", StyleDialect::Css);
11455
11456        assert_eq!(
11457            comment.errors().first().map(|error| error.code),
11458            Some(ParseErrorCode::UnterminatedBlockComment),
11459        );
11460        assert_eq!(
11461            string.errors().first().map(|error| error.code),
11462            Some(ParseErrorCode::UnterminatedString),
11463        );
11464        assert_eq!(
11465            block.errors().first().map(|error| error.code),
11466            Some(ParseErrorCode::UnexpectedCharacter),
11467        );
11468        assert!(node_kinds(&block.syntax()).contains(&SyntaxKind::BogusTrivia));
11469    }
11470
11471    #[test]
11472    fn classifies_initial_dialect_tokens() {
11473        let scss = parse("$gap: 1rem;", StyleDialect::Scss);
11474        let less = parse("@gap: 1rem;", StyleDialect::Less);
11475        let less_at_rule = parse("@media screen {}", StyleDialect::Less);
11476        let scss_kinds = node_kinds(&scss.syntax());
11477        let less_kinds = node_kinds(&less.syntax());
11478
11479        assert_eq!(scss.syntax().kind(), SyntaxKind::Root);
11480        assert_eq!(less.syntax().kind(), SyntaxKind::Root);
11481        assert_eq!(less_at_rule.syntax().kind(), SyntaxKind::Root);
11482        assert!(scss.errors().is_empty());
11483        assert!(less.errors().is_empty());
11484        assert!(less_at_rule.errors().is_empty());
11485        assert!(scss_kinds.contains(&SyntaxKind::ScssVariableDeclaration));
11486        assert!(less_kinds.contains(&SyntaxKind::LessVariableDeclaration));
11487    }
11488
11489    #[test]
11490    fn exposes_lex_result_for_tokenizer_gates() {
11491        let scss = lex("$gap: 1rem;", StyleDialect::Scss);
11492        let less = lex("@gap: 1rem;", StyleDialect::Less);
11493        let less_at_rule = lex("@media screen {}", StyleDialect::Less);
11494        let css_slashes = lex("// not a css comment", StyleDialect::Css);
11495        let scss_slashes = lex("// scss comment", StyleDialect::Scss);
11496
11497        assert_eq!(
11498            scss.tokens().first().map(|token| token.kind),
11499            Some(SyntaxKind::ScssVariable)
11500        );
11501        assert_eq!(
11502            scss.tokens().first().map(|token| token.text.as_str()),
11503            Some("$gap")
11504        );
11505        assert_eq!(
11506            less.tokens().first().map(|token| token.kind),
11507            Some(SyntaxKind::LessVariable)
11508        );
11509        assert_eq!(
11510            less_at_rule.tokens().first().map(|token| token.kind),
11511            Some(SyntaxKind::AtKeyword),
11512        );
11513        assert_eq!(
11514            css_slashes.tokens().first().map(|token| token.kind),
11515            Some(SyntaxKind::Slash)
11516        );
11517        assert_eq!(
11518            scss_slashes.tokens().first().map(|token| token.kind),
11519            Some(SyntaxKind::LineComment),
11520        );
11521    }
11522
11523    #[test]
11524    fn summarizes_parser_lex_as_parser_owned_product() {
11525        let summary = summarize_omena_parser_lex(".card { color: red; }", StyleDialect::Css);
11526
11527        assert_eq!(summary.schema_version, "0");
11528        assert_eq!(summary.product, "omena-parser.lex-result");
11529        assert_eq!(summary.dialect, "css");
11530        assert_eq!(summary.parser_error_count, 0);
11531        assert!(summary.tokens.iter().any(|token| token.text == "card"));
11532    }
11533
11534    #[test]
11535    fn tokenizes_css_attribute_matchers_as_single_tokens() {
11536        let result = lex(
11537            ".a[data-state~=\"active\"][lang|=\"en\"][href^=\"/docs\"][href$=\".pdf\"][class*=\"btn\"] { width += 1px; }",
11538            StyleDialect::Css,
11539        );
11540        let kinds: Vec<SyntaxKind> = result.tokens().iter().map(|token| token.kind).collect();
11541
11542        assert!(result.errors().is_empty());
11543        assert!(kinds.contains(&SyntaxKind::IncludesMatch));
11544        assert!(kinds.contains(&SyntaxKind::DashMatch));
11545        assert!(kinds.contains(&SyntaxKind::PrefixMatch));
11546        assert!(kinds.contains(&SyntaxKind::SuffixMatch));
11547        assert!(kinds.contains(&SyntaxKind::SubstringMatch));
11548        assert!(kinds.contains(&SyntaxKind::PlusEquals));
11549    }
11550
11551    #[test]
11552    fn tokenizes_important_annotation_as_single_token() {
11553        let result = lex(".a { color: red !IMPORTANT; }", StyleDialect::Css);
11554        let kinds: Vec<SyntaxKind> = result.tokens().iter().map(|token| token.kind).collect();
11555
11556        assert!(result.errors().is_empty());
11557        assert!(kinds.contains(&SyntaxKind::Important));
11558        assert!(!kinds.contains(&SyntaxKind::Delim));
11559    }
11560
11561    #[test]
11562    fn tokenizes_cdo_cdc_and_ignores_them_at_top_level() {
11563        let result = parse("<!-- .a { color: red; } -->", StyleDialect::Css);
11564        let token_kinds = token_kinds(&result.syntax());
11565
11566        assert!(result.errors().is_empty());
11567        assert!(token_kinds.contains(&SyntaxKind::Cdo));
11568        assert!(token_kinds.contains(&SyntaxKind::Cdc));
11569        assert!(node_kinds(&result.syntax()).contains(&SyntaxKind::Rule));
11570    }
11571
11572    #[test]
11573    fn tokenizes_css_identifier_escapes_without_unexpected_errors() {
11574        let result = parse(".\\31 0 { color: var(--\\67 ap); }", StyleDialect::Css);
11575        let token_kinds = token_kinds(&result.syntax());
11576
11577        assert!(result.errors().is_empty());
11578        assert!(token_kinds.contains(&SyntaxKind::Ident));
11579        assert!(token_kinds.contains(&SyntaxKind::CustomPropertyName));
11580        assert!(node_kinds(&result.syntax()).contains(&SyntaxKind::ClassSelector));
11581    }
11582
11583    #[test]
11584    fn tokenizes_bare_hash_as_delim_and_hash_names_as_hash() {
11585        let bare = lex("# { color: red; }", StyleDialect::Css);
11586        let named = lex("#main { color: red; }", StyleDialect::Css);
11587        let escaped = lex("#\\31 0 { color: red; }", StyleDialect::Css);
11588        let bare_kinds: Vec<SyntaxKind> = bare.tokens().iter().map(|token| token.kind).collect();
11589        let named_kinds: Vec<SyntaxKind> = named.tokens().iter().map(|token| token.kind).collect();
11590        let escaped_kinds: Vec<SyntaxKind> =
11591            escaped.tokens().iter().map(|token| token.kind).collect();
11592
11593        assert!(bare.errors().is_empty());
11594        assert!(named.errors().is_empty());
11595        assert!(escaped.errors().is_empty());
11596        assert!(bare_kinds.contains(&SyntaxKind::Delim));
11597        assert!(!bare_kinds.contains(&SyntaxKind::Hash));
11598        assert!(named_kinds.contains(&SyntaxKind::Hash));
11599        assert!(escaped_kinds.contains(&SyntaxKind::Hash));
11600    }
11601
11602    #[test]
11603    fn tokenizes_dash_started_idents_and_custom_properties_by_ident_rules() {
11604        let vendor = lex("-webkit-transform", StyleDialect::Css);
11605        let custom = lex("--brand", StyleDialect::Css);
11606        let escaped_custom = lex("--\\31 0", StyleDialect::Css);
11607        let bare_dash = lex("--:", StyleDialect::Css);
11608        let vendor_kinds: Vec<SyntaxKind> =
11609            vendor.tokens().iter().map(|token| token.kind).collect();
11610        let custom_kinds: Vec<SyntaxKind> =
11611            custom.tokens().iter().map(|token| token.kind).collect();
11612        let escaped_custom_kinds: Vec<SyntaxKind> = escaped_custom
11613            .tokens()
11614            .iter()
11615            .map(|token| token.kind)
11616            .collect();
11617        let bare_dash_kinds: Vec<SyntaxKind> =
11618            bare_dash.tokens().iter().map(|token| token.kind).collect();
11619
11620        assert!(vendor.errors().is_empty());
11621        assert!(custom.errors().is_empty());
11622        assert!(escaped_custom.errors().is_empty());
11623        assert!(bare_dash.errors().is_empty());
11624        assert!(vendor_kinds.contains(&SyntaxKind::Ident));
11625        assert!(!vendor_kinds.contains(&SyntaxKind::Minus));
11626        assert!(custom_kinds.contains(&SyntaxKind::CustomPropertyName));
11627        assert!(escaped_custom_kinds.contains(&SyntaxKind::CustomPropertyName));
11628        assert!(!bare_dash_kinds.contains(&SyntaxKind::CustomPropertyName));
11629        assert!(bare_dash_kinds.contains(&SyntaxKind::Ident));
11630    }
11631
11632    #[test]
11633    fn tokenizes_signed_and_leading_dot_numbers_as_single_numeric_tokens() {
11634        let signed_number = lex("+1.5", StyleDialect::Css);
11635        let signed_dimension = lex("-2px", StyleDialect::Css);
11636        let leading_dot = lex(".5", StyleDialect::Css);
11637        let spaced_plus = lex("+ 1.5", StyleDialect::Css);
11638        let trailing_dot = lex("1.", StyleDialect::Css);
11639        let signed_number_kinds: Vec<SyntaxKind> = signed_number
11640            .tokens()
11641            .iter()
11642            .map(|token| token.kind)
11643            .collect();
11644        let signed_dimension_kinds: Vec<SyntaxKind> = signed_dimension
11645            .tokens()
11646            .iter()
11647            .map(|token| token.kind)
11648            .collect();
11649        let leading_dot_kinds: Vec<SyntaxKind> = leading_dot
11650            .tokens()
11651            .iter()
11652            .map(|token| token.kind)
11653            .collect();
11654        let spaced_plus_kinds: Vec<SyntaxKind> = spaced_plus
11655            .tokens()
11656            .iter()
11657            .map(|token| token.kind)
11658            .collect();
11659        let trailing_dot_kinds: Vec<SyntaxKind> = trailing_dot
11660            .tokens()
11661            .iter()
11662            .map(|token| token.kind)
11663            .collect();
11664
11665        assert!(signed_number.errors().is_empty());
11666        assert!(signed_dimension.errors().is_empty());
11667        assert!(leading_dot.errors().is_empty());
11668        assert!(spaced_plus.errors().is_empty());
11669        assert!(trailing_dot.errors().is_empty());
11670        assert_eq!(signed_number_kinds, vec![SyntaxKind::Number]);
11671        assert_eq!(signed_dimension_kinds, vec![SyntaxKind::Dimension]);
11672        assert_eq!(leading_dot_kinds, vec![SyntaxKind::Number]);
11673        assert!(spaced_plus_kinds.contains(&SyntaxKind::Plus));
11674        assert!(spaced_plus_kinds.contains(&SyntaxKind::Number));
11675        assert_eq!(
11676            trailing_dot_kinds,
11677            vec![SyntaxKind::Number, SyntaxKind::Dot]
11678        );
11679    }
11680
11681    #[test]
11682    fn tokenizes_exponent_numbers_before_dimension_suffixes() {
11683        let exponent = lex("1e3", StyleDialect::Css);
11684        let signed_exponent = lex("1e-3", StyleDialect::Css);
11685        let exponent_dimension = lex("1e3px", StyleDialect::Css);
11686        let plain_dimension = lex("1em", StyleDialect::Css);
11687        let exponent_kinds: Vec<SyntaxKind> =
11688            exponent.tokens().iter().map(|token| token.kind).collect();
11689        let signed_exponent_kinds: Vec<SyntaxKind> = signed_exponent
11690            .tokens()
11691            .iter()
11692            .map(|token| token.kind)
11693            .collect();
11694        let exponent_dimension_kinds: Vec<SyntaxKind> = exponent_dimension
11695            .tokens()
11696            .iter()
11697            .map(|token| token.kind)
11698            .collect();
11699        let plain_dimension_kinds: Vec<SyntaxKind> = plain_dimension
11700            .tokens()
11701            .iter()
11702            .map(|token| token.kind)
11703            .collect();
11704
11705        assert!(exponent.errors().is_empty());
11706        assert!(signed_exponent.errors().is_empty());
11707        assert!(exponent_dimension.errors().is_empty());
11708        assert!(plain_dimension.errors().is_empty());
11709        assert_eq!(exponent_kinds, vec![SyntaxKind::Number]);
11710        assert_eq!(signed_exponent_kinds, vec![SyntaxKind::Number]);
11711        assert_eq!(exponent_dimension_kinds, vec![SyntaxKind::Dimension]);
11712        assert_eq!(plain_dimension_kinds, vec![SyntaxKind::Dimension]);
11713    }
11714
11715    #[test]
11716    fn tokenizes_null_and_bom_without_unexpected_errors() {
11717        let result = parse("\u{feff}.a\0b { content: \0; }", StyleDialect::Css);
11718        let lexed = lex(
11719            "\u{feff}.a\0b { background: url(foo\0bar); }",
11720            StyleDialect::Css,
11721        );
11722        let token_kinds = token_kinds(&result.syntax());
11723        let ident = lexed
11724            .tokens()
11725            .iter()
11726            .find(|token| token.kind == SyntaxKind::Ident)
11727            .map(|token| token.text.as_str());
11728        let url = lexed
11729            .tokens()
11730            .iter()
11731            .find(|token| token.kind == SyntaxKind::Url)
11732            .map(|token| token.text.as_str());
11733
11734        assert!(result.errors().is_empty());
11735        assert!(lexed.errors().is_empty());
11736        assert_eq!(
11737            lexed.tokens().first().map(|token| token.kind),
11738            Some(SyntaxKind::Dot)
11739        );
11740        assert_eq!(ident, Some("a\u{fffd}b"));
11741        assert_eq!(url, Some("url(foo\u{fffd}bar)"));
11742        assert!(
11743            !lexed
11744                .tokens()
11745                .iter()
11746                .any(|token| token.text.contains('\0') || token.text.contains('\u{feff}'))
11747        );
11748        assert!(token_kinds.contains(&SyntaxKind::Whitespace));
11749        assert!(token_kinds.contains(&SyntaxKind::Ident));
11750        assert!(node_kinds(&result.syntax()).contains(&SyntaxKind::ClassSelector));
11751    }
11752
11753    #[test]
11754    fn tokenizes_unquoted_urls_and_bad_urls() {
11755        let good = lex(".a { background: url(images/bg.png); }", StyleDialect::Css);
11756        let bad = lex(".a { background: url(foo\"bar); }", StyleDialect::Css);
11757        let bad_whitespace = lex(".a { background: url(foo bar); }", StyleDialect::Css);
11758        let bad_escape = lex(".a { background: url(foo\\\nbar); }", StyleDialect::Css);
11759        let trailing_whitespace = lex(".a { background: url(foo \n ); }", StyleDialect::Css);
11760        let quoted = lex(
11761            ".a { background: url(\"images/bg.png\"); }",
11762            StyleDialect::Css,
11763        );
11764        let good_kinds: Vec<SyntaxKind> = good.tokens().iter().map(|token| token.kind).collect();
11765        let bad_kinds: Vec<SyntaxKind> = bad.tokens().iter().map(|token| token.kind).collect();
11766        let bad_whitespace_kinds: Vec<SyntaxKind> = bad_whitespace
11767            .tokens()
11768            .iter()
11769            .map(|token| token.kind)
11770            .collect();
11771        let bad_escape_kinds: Vec<SyntaxKind> =
11772            bad_escape.tokens().iter().map(|token| token.kind).collect();
11773        let trailing_whitespace_kinds: Vec<SyntaxKind> = trailing_whitespace
11774            .tokens()
11775            .iter()
11776            .map(|token| token.kind)
11777            .collect();
11778        let quoted_kinds: Vec<SyntaxKind> =
11779            quoted.tokens().iter().map(|token| token.kind).collect();
11780
11781        assert!(good.errors().is_empty());
11782        assert!(good_kinds.contains(&SyntaxKind::Url));
11783        assert!(bad_kinds.contains(&SyntaxKind::BadUrl));
11784        assert!(!bad.errors().is_empty());
11785        assert!(bad_whitespace_kinds.contains(&SyntaxKind::BadUrl));
11786        assert!(!bad_whitespace.errors().is_empty());
11787        assert!(bad_escape_kinds.contains(&SyntaxKind::BadUrl));
11788        assert!(!bad_escape.errors().is_empty());
11789        assert!(trailing_whitespace.errors().is_empty());
11790        assert!(trailing_whitespace_kinds.contains(&SyntaxKind::Url));
11791        assert!(quoted_kinds.contains(&SyntaxKind::Ident));
11792        assert!(quoted_kinds.contains(&SyntaxKind::String));
11793        assert!(!quoted_kinds.contains(&SyntaxKind::Url));
11794    }
11795
11796    #[test]
11797    fn tokenizes_unicode_ranges() {
11798        let result = lex(
11799            "@font-face { unicode-range: U+00A0-00FF, u+4??; }",
11800            StyleDialect::Css,
11801        );
11802        let kinds: Vec<SyntaxKind> = result.tokens().iter().map(|token| token.kind).collect();
11803
11804        assert!(result.errors().is_empty());
11805        assert_eq!(
11806            kinds
11807                .iter()
11808                .filter(|kind| **kind == SyntaxKind::UnicodeRange)
11809                .count(),
11810            2
11811        );
11812    }
11813
11814    #[test]
11815    fn tokenizes_scss_interpolation_delimiters() {
11816        let scss = lex(
11817            ".button-#{$variant} { color: #{$color}; }",
11818            StyleDialect::Scss,
11819        );
11820        let css = lex(".button-#{$variant} { color: red; }", StyleDialect::Css);
11821        let scss_kinds: Vec<SyntaxKind> = scss.tokens().iter().map(|token| token.kind).collect();
11822        let css_kinds: Vec<SyntaxKind> = css.tokens().iter().map(|token| token.kind).collect();
11823
11824        assert!(scss.errors().is_empty());
11825        assert!(scss_kinds.contains(&SyntaxKind::ScssInterpolationStart));
11826        assert!(scss_kinds.contains(&SyntaxKind::ScssInterpolationEnd));
11827        assert!(!css_kinds.contains(&SyntaxKind::ScssInterpolationStart));
11828    }
11829
11830    #[test]
11831    fn tokenizes_scss_placeholder_selectors() {
11832        let scss = lex("%button { color: red; }", StyleDialect::Scss);
11833        let css = lex("%button { color: red; }", StyleDialect::Css);
11834        let scss_kinds: Vec<SyntaxKind> = scss.tokens().iter().map(|token| token.kind).collect();
11835        let css_kinds: Vec<SyntaxKind> = css.tokens().iter().map(|token| token.kind).collect();
11836
11837        assert!(scss.errors().is_empty());
11838        assert!(scss_kinds.contains(&SyntaxKind::ScssPlaceholder));
11839        assert!(css_kinds.contains(&SyntaxKind::Percent));
11840        assert!(!css_kinds.contains(&SyntaxKind::ScssPlaceholder));
11841    }
11842
11843    #[test]
11844    fn tokenizes_sass_indented_block_markers() {
11845        let result = lex(
11846            ".card\n  color: red // comment\n  .title\n    color: blue\n",
11847            StyleDialect::Sass,
11848        );
11849        let kinds: Vec<SyntaxKind> = result.tokens().iter().map(|token| token.kind).collect();
11850
11851        assert!(result.errors().is_empty());
11852        assert!(kinds.contains(&SyntaxKind::LineComment));
11853        assert!(kinds.contains(&SyntaxKind::SassIndentedNewline));
11854        assert!(kinds.contains(&SyntaxKind::SassOptionalSemicolon));
11855        assert_eq!(
11856            kinds
11857                .iter()
11858                .filter(|kind| **kind == SyntaxKind::SassIndent)
11859                .count(),
11860            2
11861        );
11862        assert_eq!(
11863            kinds
11864                .iter()
11865                .filter(|kind| **kind == SyntaxKind::SassDedent)
11866                .count(),
11867            2
11868        );
11869    }
11870
11871    #[test]
11872    fn tokenizes_less_interpolation_delimiters() {
11873        let less = lex(
11874            ".button-@{variant} { color: @{color}; }",
11875            StyleDialect::Less,
11876        );
11877        let css = lex(".button-@{variant} { color: red; }", StyleDialect::Css);
11878        let less_kinds: Vec<SyntaxKind> = less.tokens().iter().map(|token| token.kind).collect();
11879        let css_kinds: Vec<SyntaxKind> = css.tokens().iter().map(|token| token.kind).collect();
11880
11881        assert!(less.errors().is_empty());
11882        assert!(less_kinds.contains(&SyntaxKind::LessInterpolationStart));
11883        assert!(less_kinds.contains(&SyntaxKind::LessInterpolationEnd));
11884        assert!(!css_kinds.contains(&SyntaxKind::LessInterpolationStart));
11885    }
11886
11887    #[test]
11888    fn tokenizes_less_escaped_strings() {
11889        let less = lex(".a { filter: ~\"alpha(opacity=50)\"; }", StyleDialect::Less);
11890        let css = lex(".a { filter: ~\"alpha(opacity=50)\"; }", StyleDialect::Css);
11891        let less_kinds: Vec<SyntaxKind> = less.tokens().iter().map(|token| token.kind).collect();
11892        let css_kinds: Vec<SyntaxKind> = css.tokens().iter().map(|token| token.kind).collect();
11893
11894        assert!(less.errors().is_empty());
11895        assert!(less_kinds.contains(&SyntaxKind::LessEscapedString));
11896        assert!(!css_kinds.contains(&SyntaxKind::LessEscapedString));
11897        assert!(css_kinds.contains(&SyntaxKind::Tilde));
11898        assert!(css_kinds.contains(&SyntaxKind::String));
11899    }
11900
11901    #[test]
11902    fn tokenizes_less_property_variables_without_breaking_suffix_matchers() {
11903        let less = lex(
11904            ".a { background: $color; [data-x$=y] {} }",
11905            StyleDialect::Less,
11906        );
11907        let scss = lex(".a { background: $color; }", StyleDialect::Scss);
11908        let less_kinds: Vec<SyntaxKind> = less.tokens().iter().map(|token| token.kind).collect();
11909        let scss_kinds: Vec<SyntaxKind> = scss.tokens().iter().map(|token| token.kind).collect();
11910
11911        assert!(less.errors().is_empty());
11912        assert!(scss.errors().is_empty());
11913        assert!(less_kinds.contains(&SyntaxKind::LessPropertyVariableToken));
11914        assert!(less_kinds.contains(&SyntaxKind::SuffixMatch));
11915        assert!(!less_kinds.contains(&SyntaxKind::ScssVariable));
11916        assert!(scss_kinds.contains(&SyntaxKind::ScssVariable));
11917    }
11918
11919    #[test]
11920    fn tokenizes_newline_bad_strings() {
11921        let result = lex(".a { content: \"bad\nstill-here: red; }", StyleDialect::Css);
11922        let kinds: Vec<SyntaxKind> = result.tokens().iter().map(|token| token.kind).collect();
11923
11924        assert!(kinds.contains(&SyntaxKind::BadString));
11925        assert!(
11926            result
11927                .errors()
11928                .iter()
11929                .any(|error| error.code == ParseErrorCode::UnterminatedString)
11930        );
11931    }
11932
11933    #[test]
11934    fn exposes_recovery_token_sets() {
11935        assert!(RECOVERY_TOP.contains(SyntaxKind::AtKeyword));
11936        assert!(RECOVERY_DECLARATION.contains(SyntaxKind::Semicolon));
11937        assert!(RECOVERY_SELECTOR.contains(SyntaxKind::LeftBrace));
11938        assert!(!RECOVERY_SELECTOR.is_empty());
11939    }
11940
11941    #[test]
11942    fn builds_at_rule_and_bogus_nodes_for_partial_input() {
11943        let at_rule = parse("@media screen { .a { color: red; } }", StyleDialect::Css);
11944        let missing_colon = parse(".a { color red; }", StyleDialect::Css);
11945        let missing_block = parse(".a color: red;", StyleDialect::Css);
11946
11947        assert!(node_kinds(&at_rule.syntax()).contains(&SyntaxKind::AtRule));
11948        assert!(node_kinds(&missing_colon.syntax()).contains(&SyntaxKind::BogusDeclaration));
11949        assert!(node_kinds(&missing_block.syntax()).contains(&SyntaxKind::BogusRule));
11950    }
11951
11952    #[test]
11953    fn builds_bogus_nodes_for_selector_and_value_recovery() {
11954        let missing_class_name = parse(". { color: red; }", StyleDialect::Css);
11955        let missing_attribute_end = parse(".a[data-active { color: red; }", StyleDialect::Css);
11956        let missing_value_rhs = parse(".a { width: calc(1 + ); }", StyleDialect::Css);
11957        let unexpected_value_token = parse(".a { color: @; }", StyleDialect::Css);
11958
11959        assert_eq!(
11960            missing_class_name.errors().first().map(|error| error.code),
11961            Some(ParseErrorCode::ExpectedSelectorName)
11962        );
11963        assert_eq!(
11964            missing_attribute_end
11965                .errors()
11966                .first()
11967                .map(|error| error.code),
11968            Some(ParseErrorCode::UnterminatedAttributeSelector)
11969        );
11970        assert!(
11971            missing_value_rhs
11972                .errors()
11973                .iter()
11974                .any(|error| error.code == ParseErrorCode::ExpectedValue)
11975        );
11976        assert!(node_kinds(&missing_class_name.syntax()).contains(&SyntaxKind::BogusSelector));
11977        assert!(node_kinds(&missing_attribute_end.syntax()).contains(&SyntaxKind::BogusSelector));
11978        assert!(node_kinds(&missing_value_rhs.syntax()).contains(&SyntaxKind::BogusValue));
11979        assert!(node_kinds(&unexpected_value_token.syntax()).contains(&SyntaxKind::BogusValue));
11980    }
11981
11982    #[test]
11983    fn recovers_empty_declaration_values_without_rejecting_custom_properties() {
11984        let result = parse(".a { color: ; width: ; --empty: ; }", StyleDialect::Css);
11985        let kinds = node_kinds(&result.syntax());
11986        let empty_value_errors = result
11987            .errors()
11988            .iter()
11989            .filter(|error| error.message == "expected declaration value")
11990            .count();
11991        let bogus_value_count = kinds
11992            .iter()
11993            .filter(|kind| **kind == SyntaxKind::BogusValue)
11994            .count();
11995
11996        assert_eq!(empty_value_errors, 2);
11997        assert_eq!(bogus_value_count, 2);
11998        assert!(kinds.contains(&SyntaxKind::CustomPropertyValue));
11999    }
12000
12001    #[test]
12002    fn recovers_empty_variable_values_without_rejecting_less_detached_rulesets() {
12003        let scss = parse("$gap: ;", StyleDialect::Scss);
12004        let less = parse("@gap: ; @ruleset: { color: red; };", StyleDialect::Less);
12005        let scss_kinds = node_kinds(&scss.syntax());
12006        let less_kinds = node_kinds(&less.syntax());
12007        let empty_value_errors = scss
12008            .errors()
12009            .iter()
12010            .chain(less.errors())
12011            .filter(|error| error.message == "expected variable value")
12012            .count();
12013
12014        assert_eq!(empty_value_errors, 2);
12015        assert!(scss_kinds.contains(&SyntaxKind::BogusValue));
12016        assert!(less_kinds.contains(&SyntaxKind::BogusValue));
12017        assert!(less_kinds.contains(&SyntaxKind::LessDetachedRulesetNode));
12018    }
12019
12020    #[test]
12021    fn recovers_missing_semicolons_between_declarations() {
12022        let result = parse(
12023            ".a { color: red background: blue; margin: 0 padding: 1rem; }",
12024            StyleDialect::Css,
12025        );
12026        let custom_property = parse(
12027            ".a { --token: red background: blue; color: red; }",
12028            StyleDialect::Css,
12029        );
12030        let kinds = node_kinds(&result.syntax());
12031        let custom_property_kinds = node_kinds(&custom_property.syntax());
12032        let declaration_count = kinds
12033            .iter()
12034            .filter(|kind| **kind == SyntaxKind::Declaration)
12035            .count();
12036        let custom_property_declaration_count = custom_property_kinds
12037            .iter()
12038            .filter(|kind| **kind == SyntaxKind::Declaration)
12039            .count();
12040        let missing_semicolon_errors = result
12041            .errors()
12042            .iter()
12043            .filter(|error| error.message == "expected semicolon between declarations")
12044            .count();
12045
12046        assert_eq!(declaration_count, 4);
12047        assert_eq!(missing_semicolon_errors, 2);
12048        assert_eq!(custom_property_declaration_count, 2);
12049        assert!(custom_property.errors().is_empty());
12050        assert!(custom_property_kinds.contains(&SyntaxKind::CustomPropertyValue));
12051    }
12052
12053    #[test]
12054    fn populates_core_bogus_nodes_for_recoverable_structures() {
12055        let missing_function_close =
12056            parse(".a { width: calc(1 + ; color: red; }", StyleDialect::Css);
12057        let missing_media_close = parse(
12058            "@media (min-width: { .a { color: red; } }",
12059            StyleDialect::Css,
12060        );
12061        let mixed_media_close = parse(
12062            "@media screen, (min-width: { .a { color: red; } }",
12063            StyleDialect::Css,
12064        );
12065        let missing_supports_close = parse(
12066            "@supports (display: { .a { color: red; } }",
12067            StyleDialect::Css,
12068        );
12069        let missing_container_close = parse(
12070            "@container (inline-size > { .a { color: red; } }",
12071            StyleDialect::Css,
12072        );
12073        let missing_unknown_prelude_close =
12074            parse("@unknown (min-width: { color: red; }", StyleDialect::Css);
12075        let missing_scope_close = parse("@scope (.a { .b { color: red; } }", StyleDialect::Css);
12076        let empty_layer_statement = parse("@layer ;", StyleDialect::Css);
12077        let missing_keyframe_block =
12078            parse("@keyframes fade { from opacity: 0; }", StyleDialect::Css);
12079        let unclosed_rule = parse(".a { color: red;", StyleDialect::Css);
12080
12081        assert!(
12082            node_kinds(&missing_function_close.syntax()).contains(&SyntaxKind::BogusFunctionCall)
12083        );
12084        assert!(
12085            node_kinds(&missing_function_close.syntax())
12086                .contains(&SyntaxKind::BogusFunctionArguments)
12087        );
12088        assert!(node_kinds(&missing_media_close.syntax()).contains(&SyntaxKind::BogusMediaQuery));
12089        assert!(node_kinds(&mixed_media_close.syntax()).contains(&SyntaxKind::MediaQuery));
12090        assert!(node_kinds(&mixed_media_close.syntax()).contains(&SyntaxKind::BogusMediaQuery));
12091        assert!(
12092            node_kinds(&missing_supports_close.syntax())
12093                .contains(&SyntaxKind::BogusSupportsCondition)
12094        );
12095        assert!(
12096            node_kinds(&missing_container_close.syntax())
12097                .contains(&SyntaxKind::BogusContainerCondition)
12098        );
12099        assert!(
12100            node_kinds(&missing_unknown_prelude_close.syntax())
12101                .contains(&SyntaxKind::BogusAtRulePrelude)
12102        );
12103        assert!(node_kinds(&missing_scope_close.syntax()).contains(&SyntaxKind::BogusScopeRange));
12104        assert!(node_kinds(&empty_layer_statement.syntax()).contains(&SyntaxKind::BogusLayerName));
12105        assert!(
12106            node_kinds(&missing_keyframe_block.syntax()).contains(&SyntaxKind::BogusKeyframeBlock)
12107        );
12108        assert!(node_kinds(&unclosed_rule.syntax()).contains(&SyntaxKind::BogusDeclarationList));
12109        assert!(node_kinds(&unclosed_rule.syntax()).contains(&SyntaxKind::BogusTrivia));
12110    }
12111
12112    #[test]
12113    fn populates_dialect_and_selector_bogus_nodes() {
12114        let invalid_compound = parse("%bad { color: red; }", StyleDialect::Css);
12115        let dangling_combinator = parse(".a > { color: red; }", StyleDialect::Css);
12116        let missing_property = parse(".a { : red; }", StyleDialect::Css);
12117        let missing_colon_recovery = parse("$gap 1rem;", StyleDialect::Scss);
12118        let unexpected_value_token = parse(".a { width: ?; }", StyleDialect::Css);
12119        let missing_at_rule_name = parse("@ ;", StyleDialect::Css);
12120        let missing_scss_variable_colon = parse("$gap;", StyleDialect::Scss);
12121        let missing_less_variable_colon = parse("@gap;", StyleDialect::Less);
12122        let missing_scss_blocks =
12123            parse("@mixin card; @function double; @if $x;", StyleDialect::Scss);
12124        let inconsistent_sass_indentation =
12125            parse(".card\n  color: red\n color: blue\n", StyleDialect::Sass);
12126        let missing_less_mixin_block = parse(".theme(@tone);", StyleDialect::Less);
12127        let missing_less_guard_condition =
12128            parse(".theme() when { color: red; }", StyleDialect::Less);
12129
12130        assert!(
12131            node_kinds(&invalid_compound.syntax()).contains(&SyntaxKind::BogusCompoundSelector)
12132        );
12133        assert!(node_kinds(&dangling_combinator.syntax()).contains(&SyntaxKind::BogusCombinator));
12134        assert!(node_kinds(&missing_property.syntax()).contains(&SyntaxKind::BogusPropertyName));
12135        assert!(node_kinds(&missing_colon_recovery.syntax()).contains(&SyntaxKind::BogusRecovery));
12136        assert!(node_kinds(&unexpected_value_token.syntax()).contains(&SyntaxKind::BogusToken));
12137        assert!(node_kinds(&missing_at_rule_name.syntax()).contains(&SyntaxKind::BogusAtRule));
12138        assert!(
12139            node_kinds(&missing_scss_variable_colon.syntax())
12140                .contains(&SyntaxKind::BogusScssVariable)
12141        );
12142        assert!(
12143            node_kinds(&missing_less_variable_colon.syntax())
12144                .contains(&SyntaxKind::BogusLessVariable)
12145        );
12146        assert!(node_kinds(&missing_scss_blocks.syntax()).contains(&SyntaxKind::BogusScssMixin));
12147        assert!(node_kinds(&missing_scss_blocks.syntax()).contains(&SyntaxKind::BogusScssFunction));
12148        assert!(node_kinds(&missing_scss_blocks.syntax()).contains(&SyntaxKind::BogusScssControl));
12149        assert!(
12150            node_kinds(&inconsistent_sass_indentation.syntax())
12151                .contains(&SyntaxKind::BogusSassIndentation)
12152        );
12153        assert!(
12154            node_kinds(&missing_less_mixin_block.syntax()).contains(&SyntaxKind::BogusLessMixin)
12155        );
12156        assert!(
12157            node_kinds(&missing_less_guard_condition.syntax())
12158                .contains(&SyntaxKind::BogusLessGuard)
12159        );
12160    }
12161
12162    #[test]
12163    fn populates_every_declared_bogus_kind_in_recovery_corpus() {
12164        let mut actual = BTreeSet::new();
12165        let mut collect = |result: ParseResult| {
12166            actual.extend(
12167                node_kinds(&result.syntax())
12168                    .into_iter()
12169                    .filter(|kind| kind.is_bogus()),
12170            );
12171        };
12172
12173        collect(parse("{ color: red; }", StyleDialect::Css));
12174        collect(parse(". { color: red; }", StyleDialect::Css));
12175        collect(parse("%bad { color: red; }", StyleDialect::Css));
12176        collect(parse(".a > { color: red; }", StyleDialect::Css));
12177        collect(parse(".a { : red; width: ?; }", StyleDialect::Css));
12178        collect(parse(
12179            ".a { width: ; height: calc(1 + ; }",
12180            StyleDialect::Css,
12181        ));
12182        collect(parse(".a { color: [red; }", StyleDialect::Css));
12183        collect(parse(".a { font-family: system, ; }", StyleDialect::Css));
12184        collect(parse("@ ;", StyleDialect::Css));
12185        collect(parse(
12186            "@unknown (min-width: { color: red; }",
12187            StyleDialect::Css,
12188        ));
12189        collect(parse(
12190            "@media screen, (min-width: { .a { color: red; } }",
12191            StyleDialect::Css,
12192        ));
12193        collect(parse(
12194            "@supports (display: { .a { color: red; } }",
12195            StyleDialect::Css,
12196        ));
12197        collect(parse(
12198            "@container (inline-size > { .a { color: red; } }",
12199            StyleDialect::Css,
12200        ));
12201        collect(parse("@layer ;", StyleDialect::Css));
12202        collect(parse(
12203            "@scope (.a { .b { color: red; } }",
12204            StyleDialect::Css,
12205        ));
12206        collect(parse(
12207            "@keyframes fade { from opacity: 0; }",
12208            StyleDialect::Css,
12209        ));
12210        collect(parse(
12211            "@value from; .bad { composes: from; } .missing { composes base; }",
12212            StyleDialect::Scss,
12213        ));
12214        collect(parse(
12215            "@use \"theme\" with ($gap: 1rem; .card { color: red; }",
12216            StyleDialect::Scss,
12217        ));
12218        collect(parse(
12219            "@mixin card; @function double; @if $x;",
12220            StyleDialect::Scss,
12221        ));
12222        collect(parse("$gap;", StyleDialect::Scss));
12223        collect(parse(".a { content: \"unterminated\n }", StyleDialect::Css));
12224        collect(parse(".a { color: #{$tone; }", StyleDialect::Scss));
12225        collect(parse(
12226            ".card\n  color: red\n color: blue\n",
12227            StyleDialect::Sass,
12228        ));
12229        collect(parse("@gap;", StyleDialect::Less));
12230        collect(parse(".theme(@tone);", StyleDialect::Less));
12231        collect(parse(".theme() when { color: red; }", StyleDialect::Less));
12232        collect(parse("@detached: { .a { color: red; }", StyleDialect::Less));
12233        collect(parse("$gap 1rem;", StyleDialect::Scss));
12234        collect(parse_entry_point(
12235            "[red",
12236            StyleDialect::Css,
12237            ParseEntryPoint::SimpleBlock,
12238        ));
12239        collect(parse_entry_point(
12240            "red, ;",
12241            StyleDialect::Css,
12242            ParseEntryPoint::CommaSeparatedComponentValueList,
12243        ));
12244
12245        let declared = SyntaxKind::ALL
12246            .iter()
12247            .copied()
12248            .filter(|kind| kind.is_bogus())
12249            .collect::<BTreeSet<_>>();
12250        let missing = declared.difference(&actual).copied().collect::<Vec<_>>();
12251
12252        assert!(missing.is_empty(), "missing bogus kinds: {missing:?}");
12253    }
12254
12255    #[test]
12256    fn parses_css_module_value_and_composes_cst_nodes() {
12257        let result = parse(
12258            "@value primary: #fff; @value accent: primary; @value secondary as localSecondary from \"./tokens.module.scss\"; .btn { composes: base utility from \"./base.module.scss\"; }",
12259            StyleDialect::Scss,
12260        );
12261        let kinds = node_kinds(&result.syntax());
12262
12263        assert!(result.errors().is_empty());
12264        assert!(kinds.contains(&SyntaxKind::CssModuleExportBlock));
12265        assert!(kinds.contains(&SyntaxKind::CssModuleImportBlock));
12266        assert!(kinds.contains(&SyntaxKind::TokenDefinition));
12267        assert!(kinds.contains(&SyntaxKind::TokenReference));
12268        assert!(kinds.contains(&SyntaxKind::CssModuleComposesDeclaration));
12269        assert!(kinds.contains(&SyntaxKind::CssModuleComposesTarget));
12270        assert!(kinds.contains(&SyntaxKind::CssModuleFromClause));
12271    }
12272
12273    #[test]
12274    fn extracts_css_module_value_style_facts() {
12275        let facts = collect_style_facts(
12276            "@value primary: #fff; @value accent: primary; @value secondary as localSecondary from \"./tokens.module.scss\"; .btn { color: accent; }",
12277            StyleDialect::Css,
12278        );
12279        let definitions = facts
12280            .css_module_values
12281            .iter()
12282            .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
12283            .map(|value| value.name.as_str())
12284            .collect::<Vec<_>>();
12285        let references = facts
12286            .css_module_values
12287            .iter()
12288            .filter(|value| value.kind == ParsedCssModuleValueFactKind::Reference)
12289            .map(|value| value.name.as_str())
12290            .collect::<Vec<_>>();
12291        let import_sources = facts
12292            .css_module_values
12293            .iter()
12294            .filter(|value| value.kind == ParsedCssModuleValueFactKind::ImportSource)
12295            .map(|value| value.name.as_str())
12296            .collect::<Vec<_>>();
12297
12298        assert_eq!(facts.css_module_value_count, 7);
12299        assert_eq!(definitions, vec!["primary", "accent", "localSecondary"]);
12300        assert_eq!(references, vec!["primary", "secondary", "accent"]);
12301        assert_eq!(import_sources, vec!["./tokens.module.scss"]);
12302        assert_eq!(facts.css_module_value_import_edge_count, 1);
12303        assert_eq!(
12304            facts.css_module_value_import_edges[0].remote_name,
12305            "secondary"
12306        );
12307        assert_eq!(
12308            facts.css_module_value_import_edges[0].local_name,
12309            "localSecondary"
12310        );
12311        assert_eq!(
12312            facts.css_module_value_import_edges[0].import_source,
12313            "./tokens.module.scss"
12314        );
12315        assert_eq!(facts.css_module_value_definition_edge_count, 1);
12316        assert_eq!(
12317            facts.css_module_value_definition_edges[0].definition_name,
12318            "accent"
12319        );
12320        assert_eq!(
12321            facts.css_module_value_definition_edges[0].reference_names,
12322            vec!["primary"]
12323        );
12324    }
12325
12326    #[test]
12327    fn extracts_css_module_value_path_alias_import_edges() {
12328        let facts = collect_style_facts(
12329            "@value colors: \"./colors.module.scss\"; @value primary, secondary as accent from colors; .btn { color: primary; border-color: accent; }",
12330            StyleDialect::Css,
12331        );
12332        let definitions = facts
12333            .css_module_values
12334            .iter()
12335            .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
12336            .map(|value| value.name.as_str())
12337            .collect::<Vec<_>>();
12338        let import_sources = facts
12339            .css_module_values
12340            .iter()
12341            .filter(|value| value.kind == ParsedCssModuleValueFactKind::ImportSource)
12342            .map(|value| value.name.as_str())
12343            .collect::<Vec<_>>();
12344
12345        assert_eq!(definitions, vec!["primary", "accent"]);
12346        assert_eq!(import_sources, vec!["./colors.module.scss"]);
12347        assert_eq!(facts.css_module_value_import_edge_count, 2);
12348        assert_eq!(
12349            facts.css_module_value_import_edges[0].remote_name,
12350            "primary"
12351        );
12352        assert_eq!(facts.css_module_value_import_edges[0].local_name, "primary");
12353        assert_eq!(
12354            facts.css_module_value_import_edges[0].import_source,
12355            "./colors.module.scss"
12356        );
12357        assert_eq!(
12358            facts.css_module_value_import_edges[1].remote_name,
12359            "secondary"
12360        );
12361        assert_eq!(facts.css_module_value_import_edges[1].local_name, "accent");
12362        assert_eq!(
12363            facts.css_module_value_import_edges[1].import_source,
12364            "./colors.module.scss"
12365        );
12366    }
12367
12368    #[test]
12369    fn extracts_css_module_composes_style_facts() {
12370        let facts = collect_style_facts(
12371            ".btn { composes: base utility from \"./base.module.scss\"; } .global { composes: reset from global; }",
12372            StyleDialect::Css,
12373        );
12374        let targets = facts
12375            .css_module_composes
12376            .iter()
12377            .filter(|composes| composes.kind == ParsedCssModuleComposesFactKind::Target)
12378            .map(|composes| composes.name.as_str())
12379            .collect::<Vec<_>>();
12380        let import_sources = facts
12381            .css_module_composes
12382            .iter()
12383            .filter(|composes| composes.kind == ParsedCssModuleComposesFactKind::ImportSource)
12384            .map(|composes| composes.name.as_str())
12385            .collect::<Vec<_>>();
12386
12387        assert_eq!(facts.css_module_composes_count, 5);
12388        assert_eq!(targets, vec!["base", "utility", "reset"]);
12389        assert_eq!(import_sources, vec!["./base.module.scss", "global"]);
12390        assert_eq!(facts.css_module_composes_edge_count, 2);
12391        assert_eq!(
12392            facts.css_module_composes_edges[0].kind,
12393            ParsedCssModuleComposesEdgeKind::External
12394        );
12395        assert_eq!(
12396            facts.css_module_composes_edges[0].owner_selector_names,
12397            vec!["btn"]
12398        );
12399        assert_eq!(
12400            facts.css_module_composes_edges[0].target_names,
12401            vec!["base", "utility"]
12402        );
12403        assert_eq!(
12404            facts.css_module_composes_edges[0].import_source.as_deref(),
12405            Some("./base.module.scss")
12406        );
12407        assert_eq!(
12408            facts.css_module_composes_edges[1].kind,
12409            ParsedCssModuleComposesEdgeKind::Global
12410        );
12411        assert_eq!(
12412            facts.css_module_composes_edges[1].owner_selector_names,
12413            vec!["global"]
12414        );
12415        assert_eq!(
12416            facts.css_module_composes_edges[1].target_names,
12417            vec!["reset"]
12418        );
12419        assert_eq!(
12420            facts.css_module_composes_edges[1].import_source.as_deref(),
12421            Some("global")
12422        );
12423    }
12424
12425    #[test]
12426    fn parses_icss_import_export_blocks() {
12427        let result = parse(
12428            ":export { primary: #fff; } :import(\"./tokens.css\") { imported: primary; } .btn { composes: imported; }",
12429            StyleDialect::Css,
12430        );
12431        let invalid = parse(":import { imported: primary; }", StyleDialect::Css);
12432        let kinds = node_kinds(&result.syntax());
12433
12434        assert!(result.errors().is_empty());
12435        assert!(kinds.contains(&SyntaxKind::CssModuleExportBlock));
12436        assert!(kinds.contains(&SyntaxKind::CssModuleImportBlock));
12437        assert!(
12438            invalid
12439                .errors()
12440                .iter()
12441                .any(|error| error.message == "expected ICSS import source")
12442        );
12443    }
12444
12445    #[test]
12446    fn extracts_icss_style_facts() {
12447        let facts = collect_style_facts(
12448            ":export { primary: #fff; secondary: accent; } :import(\"./tokens.css\") { imported: primary; tone: themeTone; }",
12449            StyleDialect::Css,
12450        );
12451        let export_names = facts
12452            .icss
12453            .iter()
12454            .filter(|icss| icss.kind == ParsedIcssFactKind::ExportName)
12455            .map(|icss| icss.name.as_str())
12456            .collect::<Vec<_>>();
12457        let import_local_names = facts
12458            .icss
12459            .iter()
12460            .filter(|icss| icss.kind == ParsedIcssFactKind::ImportLocalName)
12461            .map(|icss| icss.name.as_str())
12462            .collect::<Vec<_>>();
12463        let import_remote_names = facts
12464            .icss
12465            .iter()
12466            .filter(|icss| icss.kind == ParsedIcssFactKind::ImportRemoteName)
12467            .map(|icss| icss.name.as_str())
12468            .collect::<Vec<_>>();
12469        let import_sources = facts
12470            .icss
12471            .iter()
12472            .filter(|icss| icss.kind == ParsedIcssFactKind::ImportSource)
12473            .map(|icss| icss.name.as_str())
12474            .collect::<Vec<_>>();
12475
12476        assert_eq!(facts.icss_count, 7);
12477        assert_eq!(export_names, vec!["primary", "secondary"]);
12478        assert_eq!(import_local_names, vec!["imported", "tone"]);
12479        assert_eq!(import_remote_names, vec!["primary", "themeTone"]);
12480        assert_eq!(import_sources, vec!["./tokens.css"]);
12481        assert_eq!(facts.icss_import_edge_count, 2);
12482        assert_eq!(facts.icss_import_edges[0].local_name, "imported");
12483        assert_eq!(facts.icss_import_edges[0].remote_name, "primary");
12484        assert_eq!(facts.icss_import_edges[0].import_source, "./tokens.css");
12485        assert_eq!(facts.icss_import_edges[1].local_name, "tone");
12486        assert_eq!(facts.icss_import_edges[1].remote_name, "themeTone");
12487        assert_eq!(facts.icss_import_edges[1].import_source, "./tokens.css");
12488        assert_eq!(facts.icss_export_edge_count, 1);
12489        assert_eq!(facts.icss_export_edges[0].export_name, "secondary");
12490        assert_eq!(facts.icss_export_edges[0].reference_names, vec!["accent"]);
12491    }
12492
12493    #[test]
12494    fn recovers_css_module_value_and_composes_bogus_nodes() {
12495        let result = parse(
12496            "@value from; .bad { composes: from; } .missing { composes base; } .invalid { composes: base from 123; } @value bad as alias from 123; .multi { composes: a from \"./a.css\", b from \"./b.css\"; }",
12497            StyleDialect::Scss,
12498        );
12499        let kinds = node_kinds(&result.syntax());
12500        let invalid_from_source_count = result
12501            .errors()
12502            .iter()
12503            .filter(|error| error.message == "invalid CSS Modules from-clause source")
12504            .count();
12505        let multiple_from_count = result
12506            .errors()
12507            .iter()
12508            .filter(|error| error.message == "multiple composes from clauses are not allowed")
12509            .count();
12510
12511        assert!(kinds.contains(&SyntaxKind::BogusCssModuleBlock));
12512        assert!(kinds.contains(&SyntaxKind::BogusFromClause));
12513        assert!(kinds.contains(&SyntaxKind::BogusComposesTarget));
12514        assert!(kinds.contains(&SyntaxKind::BogusComposesDeclaration));
12515        assert_eq!(invalid_from_source_count, 2);
12516        assert_eq!(multiple_from_count, 1);
12517    }
12518
12519    #[test]
12520    fn validates_composes_outside_css_module_global_scope() {
12521        let invalid = parse(
12522            ":global(.reset) { composes: base; } :global { .utility { composes: base; } } :local(.ok) { composes: base; }",
12523            StyleDialect::Css,
12524        );
12525        let outer_local = parse(
12526            ":local { :global(.ok) { composes: base; } }",
12527            StyleDialect::Css,
12528        );
12529        let mixed_local_global = parse(".foo :global(.bar) { composes: base; }", StyleDialect::Css);
12530        let global_composes_count = invalid
12531            .errors()
12532            .iter()
12533            .filter(|error| error.message == "composes is not allowed inside :global scope")
12534            .count();
12535
12536        assert_eq!(global_composes_count, 2);
12537        assert!(
12538            !outer_local
12539                .errors()
12540                .iter()
12541                .any(|error| error.message == "composes is not allowed inside :global scope")
12542        );
12543        assert!(
12544            !mixed_local_global
12545                .errors()
12546                .iter()
12547                .any(|error| error.message == "composes is not allowed inside :global scope")
12548        );
12549    }
12550
12551    #[test]
12552    fn parses_registered_group_at_rule_blocks() {
12553        let result = parse(
12554            "@media screen and (min-width: 40rem) { .card { color: red; } }",
12555            StyleDialect::Css,
12556        );
12557        let kinds = node_kinds(&result.syntax());
12558
12559        assert!(result.errors().is_empty());
12560        assert!(kinds.contains(&SyntaxKind::AtRule));
12561        assert!(kinds.contains(&SyntaxKind::MediaRule));
12562        assert!(kinds.contains(&SyntaxKind::RuleList));
12563        assert!(kinds.contains(&SyntaxKind::Rule));
12564        assert!(kinds.contains(&SyntaxKind::ClassSelector));
12565    }
12566
12567    #[test]
12568    fn parses_conditional_at_rule_preludes() {
12569        let result = parse(
12570            "@media screen and (min-width: 40rem), print { .card { color: red; } } @supports (display: grid) { .grid { display: grid; } } @container card (inline-size > 40rem) { .item { color: blue; } }",
12571            StyleDialect::Css,
12572        );
12573        let kinds = node_kinds(&result.syntax());
12574
12575        assert!(result.errors().is_empty());
12576        assert!(kinds.contains(&SyntaxKind::MediaQueryList));
12577        assert_eq!(
12578            kinds
12579                .iter()
12580                .filter(|kind| **kind == SyntaxKind::MediaQuery)
12581                .count(),
12582            2
12583        );
12584        assert!(kinds.contains(&SyntaxKind::MediaFeature));
12585        assert!(kinds.contains(&SyntaxKind::SupportsCondition));
12586        assert!(kinds.contains(&SyntaxKind::ContainerCondition));
12587    }
12588
12589    #[test]
12590    fn validates_media_query_list_preludes() {
12591        let result = parse(
12592            "@media { .a { color: red; } } @media , screen { .b { color: blue; } } @media screen, { .c { color: green; } } @media 1 { .d { color: black; } } @media screen and (min-width: 40rem), print { .e { color: white; } }",
12593            StyleDialect::Css,
12594        );
12595        let kinds = node_kinds(&result.syntax());
12596        let invalid_media_errors = result
12597            .errors()
12598            .iter()
12599            .filter(|error| error.message == "invalid @media prelude")
12600            .count();
12601        let bogus_media_queries = kinds
12602            .iter()
12603            .filter(|kind| **kind == SyntaxKind::BogusMediaQuery)
12604            .count();
12605
12606        assert_eq!(invalid_media_errors, 4);
12607        assert_eq!(bogus_media_queries, 4);
12608        assert!(kinds.contains(&SyntaxKind::MediaQuery));
12609    }
12610
12611    #[test]
12612    fn validates_supports_rule_preludes() {
12613        let result = parse(
12614            "@supports { .a { color: red; } } @supports display: grid { .b { color: blue; } } @supports not { .c { color: green; } } @supports (display: grid) { .d { color: black; } } @supports selector(:has(*)) { .e { color: white; } }",
12615            StyleDialect::Css,
12616        );
12617        let kinds = node_kinds(&result.syntax());
12618        let invalid_supports_errors = result
12619            .errors()
12620            .iter()
12621            .filter(|error| error.message == "invalid @supports prelude")
12622            .count();
12623        let bogus_supports_conditions = kinds
12624            .iter()
12625            .filter(|kind| **kind == SyntaxKind::BogusSupportsCondition)
12626            .count();
12627
12628        assert_eq!(invalid_supports_errors, 3);
12629        assert_eq!(bogus_supports_conditions, 3);
12630        assert!(kinds.contains(&SyntaxKind::SupportsCondition));
12631    }
12632
12633    #[test]
12634    fn validates_container_rule_preludes() {
12635        let result = parse(
12636            "@container { .a { color: red; } } @container card { .b { color: blue; } } @container 1 (width > 0) { .c { color: green; } } @container style(--theme: dark) { .d { color: white; } } @container card style(--theme: dark) { .e { color: black; } }",
12637            StyleDialect::Css,
12638        );
12639        let kinds = node_kinds(&result.syntax());
12640        let invalid_container_errors = result
12641            .errors()
12642            .iter()
12643            .filter(|error| error.message == "invalid @container prelude")
12644            .count();
12645        let bogus_container_conditions = kinds
12646            .iter()
12647            .filter(|kind| **kind == SyntaxKind::BogusContainerCondition)
12648            .count();
12649
12650        assert_eq!(invalid_container_errors, 3);
12651        assert_eq!(bogus_container_conditions, 3);
12652        assert!(kinds.contains(&SyntaxKind::ContainerCondition));
12653    }
12654
12655    #[test]
12656    fn classifies_css_at_rules_case_insensitively() {
12657        let source = "@MEDIA (width >= 1px) { .card { color: red; } } @KEYFRAMES fade { from { opacity: 0; } to { opacity: 1; } }";
12658        let result = parse(source, StyleDialect::Css);
12659        let facts = collect_style_facts(source, StyleDialect::Css);
12660        let kinds = node_kinds(&result.syntax());
12661        let at_rule_names: Vec<&str> = facts
12662            .at_rules
12663            .iter()
12664            .map(|at_rule| at_rule.name.as_str())
12665            .collect();
12666
12667        assert!(result.errors().is_empty());
12668        assert!(kinds.contains(&SyntaxKind::MediaRule));
12669        assert!(kinds.contains(&SyntaxKind::KeyframesRule));
12670        assert!(
12671            facts
12672                .selectors
12673                .iter()
12674                .any(|selector| selector.name == "card")
12675        );
12676        assert_eq!(at_rule_names, vec!["@media", "@keyframes"]);
12677    }
12678
12679    #[test]
12680    fn parses_import_layer_supports_media_prelude() {
12681        let result = parse(
12682            "@import url(\"theme.css\") layer(app.theme) supports(display: grid) screen and (min-width: 40rem);",
12683            StyleDialect::Css,
12684        );
12685        let less = parse(
12686            "@import (reference) \"theme.less\" screen and (min-width: 40rem);",
12687            StyleDialect::Less,
12688        );
12689        let kinds = node_kinds(&result.syntax());
12690        let less_kinds = node_kinds(&less.syntax());
12691
12692        assert!(result.errors().is_empty());
12693        assert!(less.errors().is_empty());
12694        assert!(kinds.contains(&SyntaxKind::ImportRule));
12695        assert!(kinds.contains(&SyntaxKind::UrlValue));
12696        assert!(kinds.contains(&SyntaxKind::LayerName));
12697        assert!(kinds.contains(&SyntaxKind::SupportsCondition));
12698        assert!(kinds.contains(&SyntaxKind::MediaQueryList));
12699        assert!(kinds.contains(&SyntaxKind::MediaFeature));
12700        assert!(less_kinds.contains(&SyntaxKind::ImportRule));
12701        assert!(less_kinds.contains(&SyntaxKind::AtRulePrelude));
12702        assert!(less_kinds.contains(&SyntaxKind::MediaQueryList));
12703    }
12704
12705    #[test]
12706    fn validates_import_sources() {
12707        let result = parse(
12708            "@import ; @import layer(app); @import 1; @import url(foo bar);",
12709            StyleDialect::Css,
12710        );
12711        let kinds = node_kinds(&result.syntax());
12712        let invalid_import_errors = result
12713            .errors()
12714            .iter()
12715            .filter(|error| error.message == "invalid @import source")
12716            .count();
12717        let bogus_preludes = kinds
12718            .iter()
12719            .filter(|kind| **kind == SyntaxKind::BogusAtRulePrelude)
12720            .count();
12721
12722        assert_eq!(invalid_import_errors, 4);
12723        assert_eq!(bogus_preludes, 4);
12724    }
12725
12726    #[test]
12727    fn validates_import_optional_tails() {
12728        let result = parse(
12729            "@import \"a.css\" layer(); @import \"b.css\" layer(1); @import \"c.css\" supports(); @import \"d.css\" supports screen; @import \"ok.css\" layer(app.theme) supports(display: grid) screen;",
12730            StyleDialect::Css,
12731        );
12732        let kinds = node_kinds(&result.syntax());
12733        let invalid_layer_tail_errors = result
12734            .errors()
12735            .iter()
12736            .filter(|error| error.message == "invalid @import layer tail")
12737            .count();
12738        let invalid_supports_tail_errors = result
12739            .errors()
12740            .iter()
12741            .filter(|error| error.message == "invalid @import supports tail")
12742            .count();
12743        let bogus_layer_names = kinds
12744            .iter()
12745            .filter(|kind| **kind == SyntaxKind::BogusLayerName)
12746            .count();
12747        let bogus_supports_conditions = kinds
12748            .iter()
12749            .filter(|kind| **kind == SyntaxKind::BogusSupportsCondition)
12750            .count();
12751
12752        assert_eq!(invalid_layer_tail_errors, 2);
12753        assert_eq!(invalid_supports_tail_errors, 2);
12754        assert_eq!(bogus_layer_names, 2);
12755        assert_eq!(bogus_supports_conditions, 2);
12756        assert!(kinds.contains(&SyntaxKind::LayerName));
12757        assert!(kinds.contains(&SyntaxKind::SupportsCondition));
12758        assert!(kinds.contains(&SyntaxKind::MediaQueryList));
12759    }
12760
12761    #[test]
12762    fn parses_layer_and_scope_preludes() {
12763        let result = parse(
12764            "@layer reset, app.ui; @layer components { .card { color: red; } } @layer { .anon { color: blue; } } @scope (.card) to (.card-content) { .title { color: red; } }",
12765            StyleDialect::Css,
12766        );
12767        let kinds = node_kinds(&result.syntax());
12768
12769        assert!(result.errors().is_empty());
12770        assert!(kinds.contains(&SyntaxKind::LayerRule));
12771        assert!(kinds.contains(&SyntaxKind::LayerName));
12772        assert!(kinds.contains(&SyntaxKind::ScopeRule));
12773        assert!(kinds.contains(&SyntaxKind::ScopeRange));
12774        assert!(kinds.contains(&SyntaxKind::RuleList));
12775    }
12776
12777    #[test]
12778    fn validates_layer_rule_preludes() {
12779        let result = parse(
12780            "@layer , reset; @layer app.; @layer 1; @layer ok.name;",
12781            StyleDialect::Css,
12782        );
12783        let kinds = node_kinds(&result.syntax());
12784        let invalid_layer_errors = result
12785            .errors()
12786            .iter()
12787            .filter(|error| error.message == "invalid @layer prelude")
12788            .count();
12789        let bogus_layer_names = kinds
12790            .iter()
12791            .filter(|kind| **kind == SyntaxKind::BogusLayerName)
12792            .count();
12793
12794        assert_eq!(invalid_layer_errors, 3);
12795        assert_eq!(bogus_layer_names, 3);
12796        assert!(kinds.contains(&SyntaxKind::LayerName));
12797    }
12798
12799    #[test]
12800    fn validates_scope_rule_preludes() {
12801        let result = parse(
12802            "@scope { .a { color: red; } } @scope .a { .b { color: blue; } } @scope (.a) to { .c { color: green; } } @scope (.a) to (.b) { .d { color: black; } }",
12803            StyleDialect::Css,
12804        );
12805        let kinds = node_kinds(&result.syntax());
12806        let invalid_scope_errors = result
12807            .errors()
12808            .iter()
12809            .filter(|error| error.message == "invalid @scope prelude")
12810            .count();
12811        let bogus_scope_ranges = kinds
12812            .iter()
12813            .filter(|kind| **kind == SyntaxKind::BogusScopeRange)
12814            .count();
12815
12816        assert_eq!(invalid_scope_errors, 3);
12817        assert_eq!(bogus_scope_ranges, 3);
12818        assert!(kinds.contains(&SyntaxKind::ScopeRange));
12819    }
12820
12821    #[test]
12822    fn validates_page_rule_preludes() {
12823        let result = parse(
12824            "@page { margin: 1cm; } @page :first { margin: 2cm; } @page chapter:left, appendix:right { margin: 3cm; } @page 1 { margin: 4cm; } @page chapter, { margin: 5cm; } @page chapter first { margin: 6cm; }",
12825            StyleDialect::Css,
12826        );
12827        let kinds = node_kinds(&result.syntax());
12828        let invalid_page_errors = result
12829            .errors()
12830            .iter()
12831            .filter(|error| error.message == "invalid @page prelude")
12832            .count();
12833        let bogus_preludes = kinds
12834            .iter()
12835            .filter(|kind| **kind == SyntaxKind::BogusAtRulePrelude)
12836            .count();
12837
12838        assert_eq!(invalid_page_errors, 3);
12839        assert_eq!(bogus_preludes, 3);
12840        assert!(kinds.contains(&SyntaxKind::PageRule));
12841        assert!(kinds.contains(&SyntaxKind::AtRulePrelude));
12842    }
12843
12844    #[test]
12845    fn parses_registered_keyframes_and_declaration_at_rules() {
12846        let keyframes = parse(
12847            "@keyframes fade { from { opacity: 0; } to { opacity: 1; } }",
12848            StyleDialect::Css,
12849        );
12850        let font_face = parse(
12851            "@font-face { font-family: \"Demo\"; src: url(demo.woff2); }",
12852            StyleDialect::Css,
12853        );
12854        let page_margin = parse(
12855            "@page :first { margin: 1cm; @top-left { content: \"A\"; } @bottom-center { content: counter(page); } }",
12856            StyleDialect::Css,
12857        );
12858        let conditional_l5 = parse(
12859            "@when media(width >= 1px) { .a { color: red; } } @else { .b { color: blue; } }",
12860            StyleDialect::Css,
12861        );
12862        let modern_declaration_rules = parse(
12863            "@counter-style thumbs { system: cyclic; symbols: \"yes\"; suffix: \" \"; } @font-palette-values --brand { font-family: Demo; base-palette: 1; } @color-profile --display-p3 { src: url(p3.icc); } @position-try --popover { inset-area: top; }",
12864            StyleDialect::Css,
12865        );
12866        let font_feature_values = parse(
12867            "@font-feature-values Demo { @stylistic { nice: 1; } @styleset { alt: 2; } @character-variant { nice: 3 4; } @swash { fancy: 1; } @ornaments { leaf: 1; } @annotation { circled: 1; } @historical-forms { old: 1; } } @view-transition { navigation: auto; }",
12868            StyleDialect::Css,
12869        );
12870        let less_css_at_rules = parse(
12871            "@font-feature-values Demo { @styleset { alt: 2; } } @view-transition { navigation: auto; }",
12872            StyleDialect::Less,
12873        );
12874        let nesting_and_custom_media = parse(
12875            ".card { @nest &__icon { color: red; &--active { color: blue; } } } @custom-media --narrow (width < 40rem);",
12876            StyleDialect::Css,
12877        );
12878        let keyframe_kinds = node_kinds(&keyframes.syntax());
12879        let font_face_kinds = node_kinds(&font_face.syntax());
12880        let page_margin_kinds = node_kinds(&page_margin.syntax());
12881        let conditional_l5_kinds = node_kinds(&conditional_l5.syntax());
12882        let modern_declaration_kinds = node_kinds(&modern_declaration_rules.syntax());
12883        let font_feature_value_kinds = node_kinds(&font_feature_values.syntax());
12884        let less_css_at_rule_kinds = node_kinds(&less_css_at_rules.syntax());
12885        let nesting_and_custom_media_kinds = node_kinds(&nesting_and_custom_media.syntax());
12886
12887        assert!(keyframes.errors().is_empty());
12888        assert!(font_face.errors().is_empty());
12889        assert!(page_margin.errors().is_empty());
12890        assert!(conditional_l5.errors().is_empty());
12891        assert!(modern_declaration_rules.errors().is_empty());
12892        assert!(font_feature_values.errors().is_empty());
12893        assert!(less_css_at_rules.errors().is_empty());
12894        assert!(nesting_and_custom_media.errors().is_empty());
12895        assert!(keyframe_kinds.contains(&SyntaxKind::KeyframesRule));
12896        assert!(keyframe_kinds.contains(&SyntaxKind::AtRulePrelude));
12897        assert!(keyframe_kinds.contains(&SyntaxKind::KeyframeBlock));
12898        assert!(font_face_kinds.contains(&SyntaxKind::FontFaceRule));
12899        assert!(font_face_kinds.contains(&SyntaxKind::DeclarationList));
12900        assert!(page_margin_kinds.contains(&SyntaxKind::PageRule));
12901        assert!(page_margin_kinds.contains(&SyntaxKind::PageMarginRule));
12902        assert!(conditional_l5_kinds.contains(&SyntaxKind::WhenRule));
12903        assert!(conditional_l5_kinds.contains(&SyntaxKind::ElseRule));
12904        assert!(conditional_l5_kinds.contains(&SyntaxKind::RuleList));
12905        assert!(modern_declaration_kinds.contains(&SyntaxKind::CounterStyleRule));
12906        assert!(modern_declaration_kinds.contains(&SyntaxKind::FontPaletteValuesRule));
12907        assert!(modern_declaration_kinds.contains(&SyntaxKind::ColorProfileRule));
12908        assert!(modern_declaration_kinds.contains(&SyntaxKind::PositionTryRule));
12909        assert!(modern_declaration_kinds.contains(&SyntaxKind::DeclarationList));
12910        assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesRule));
12911        assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesStylisticRule));
12912        assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesStylesetRule));
12913        assert!(
12914            font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesCharacterVariantRule)
12915        );
12916        assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesSwashRule));
12917        assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesOrnamentsRule));
12918        assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesAnnotationRule));
12919        assert!(
12920            font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesHistoricalFormsRule)
12921        );
12922        assert!(font_feature_value_kinds.contains(&SyntaxKind::ViewTransitionRule));
12923        assert!(less_css_at_rule_kinds.contains(&SyntaxKind::FontFeatureValuesRule));
12924        assert!(less_css_at_rule_kinds.contains(&SyntaxKind::FontFeatureValuesStylesetRule));
12925        assert!(less_css_at_rule_kinds.contains(&SyntaxKind::ViewTransitionRule));
12926        assert!(nesting_and_custom_media_kinds.contains(&SyntaxKind::NestRule));
12927        assert!(nesting_and_custom_media_kinds.contains(&SyntaxKind::CustomMediaRule));
12928        assert!(nesting_and_custom_media_kinds.contains(&SyntaxKind::DeclarationList));
12929    }
12930
12931    #[test]
12932    fn validates_property_at_rule_names() {
12933        let valid = parse(
12934            "@property --accent { syntax: \"<color>\"; inherits: false; initial-value: red; }",
12935            StyleDialect::Css,
12936        );
12937        let dynamic = parse(
12938            "@property #{$name} { syntax: \"<color>\"; inherits: false; initial-value: red; }",
12939            StyleDialect::Scss,
12940        );
12941        let invalid = parse(
12942            "@property accent { syntax: \"<color>\"; inherits: false; initial-value: red; }",
12943            StyleDialect::Css,
12944        );
12945        let invalid_property_name_count = invalid
12946            .errors()
12947            .iter()
12948            .filter(|error| error.message == "invalid @property name")
12949            .count();
12950
12951        assert!(valid.errors().is_empty());
12952        assert!(dynamic.errors().is_empty());
12953        assert_eq!(invalid_property_name_count, 1);
12954    }
12955
12956    #[test]
12957    fn validates_named_declaration_at_rule_preludes() {
12958        let valid = parse(
12959            "@counter-style thumbs { system: cyclic; symbols: \"yes\"; } @font-palette-values --brand { font-family: Demo; } @color-profile --display-p3 { src: url(p3.icc); } @position-try --popover { inset-area: top; } @custom-media --narrow (width < 40rem);",
12960            StyleDialect::Css,
12961        );
12962        let dynamic = parse(
12963            "@counter-style #{$style} { system: cyclic; symbols: \"yes\"; } @font-palette-values #{$palette} { font-family: Demo; } @custom-media #{$query} (width < 40rem);",
12964            StyleDialect::Scss,
12965        );
12966        let invalid = parse(
12967            "@counter-style --bad { system: cyclic; } @font-palette-values brand { font-family: Demo; } @color-profile display-p3 { src: url(p3.icc); } @position-try popover { inset-area: top; } @custom-media narrow (width < 40rem); @custom-media --missing;",
12968            StyleDialect::Css,
12969        );
12970        let custom_property_name_errors = invalid
12971            .errors()
12972            .iter()
12973            .filter(|error| error.message == "invalid at-rule custom property name")
12974            .count();
12975        let custom_media_prelude_errors = invalid
12976            .errors()
12977            .iter()
12978            .filter(|error| error.message == "invalid @custom-media prelude")
12979            .count();
12980        let counter_style_name_errors = invalid
12981            .errors()
12982            .iter()
12983            .filter(|error| error.message == "invalid @counter-style name")
12984            .count();
12985
12986        assert!(valid.errors().is_empty());
12987        assert!(dynamic.errors().is_empty());
12988        assert_eq!(custom_property_name_errors, 3);
12989        assert_eq!(custom_media_prelude_errors, 2);
12990        assert_eq!(counter_style_name_errors, 1);
12991    }
12992
12993    #[test]
12994    fn validates_charset_and_namespace_at_rule_preludes() {
12995        let valid = parse(
12996            "@charset \"UTF-8\"; @namespace \"http://www.w3.org/1999/xhtml\"; @namespace svg url(\"http://www.w3.org/2000/svg\"); @namespace math url(http://www.w3.org/1998/Math/MathML);",
12997            StyleDialect::Css,
12998        );
12999        let dynamic = parse(
13000            "@namespace #{$url}; @namespace svg #{$url};",
13001            StyleDialect::Scss,
13002        );
13003        let invalid = parse("@charset UTF-8; @namespace svg;", StyleDialect::Css);
13004        let charset_errors = invalid
13005            .errors()
13006            .iter()
13007            .filter(|error| error.message == "invalid @charset prelude")
13008            .count();
13009        let namespace_errors = invalid
13010            .errors()
13011            .iter()
13012            .filter(|error| error.message == "invalid @namespace prelude")
13013            .count();
13014
13015        assert!(valid.errors().is_empty());
13016        assert!(dynamic.errors().is_empty());
13017        assert_eq!(charset_errors, 1);
13018        assert_eq!(namespace_errors, 1);
13019    }
13020
13021    #[test]
13022    fn validates_keyframes_at_rule_names() {
13023        let valid = parse(
13024            "@keyframes fade { from { opacity: 0; } } @keyframes \"slide\" { to { opacity: 1; } }",
13025            StyleDialect::Css,
13026        );
13027        let dynamic = parse(
13028            "@keyframes #{$animation-name} { from { opacity: 0; } }",
13029            StyleDialect::Scss,
13030        );
13031        let invalid = parse(
13032            "@keyframes 50% { from { opacity: 0; } } @keyframes fade extra { to { opacity: 1; } }",
13033            StyleDialect::Css,
13034        );
13035        let invalid_name_errors = invalid
13036            .errors()
13037            .iter()
13038            .filter(|error| error.message == "invalid @keyframes name")
13039            .count();
13040
13041        assert!(valid.errors().is_empty());
13042        assert!(dynamic.errors().is_empty());
13043        assert_eq!(invalid_name_errors, 2);
13044    }
13045
13046    #[test]
13047    fn validates_keyframe_selector_lists() {
13048        let valid = parse(
13049            "@keyframes fade { from { opacity: 0; } 50%, 75% { opacity: .5; } to { opacity: 1; } }",
13050            StyleDialect::Css,
13051        );
13052        let dynamic = parse(
13053            "@keyframes fade { #{$step} { opacity: .5; } }",
13054            StyleDialect::Scss,
13055        );
13056        let invalid = parse(
13057            "@keyframes fade { middle { opacity: .5; } 120px { opacity: 1; } 50%, { opacity: .8; } }",
13058            StyleDialect::Css,
13059        );
13060        let invalid_selector_errors = invalid
13061            .errors()
13062            .iter()
13063            .filter(|error| error.message == "invalid keyframe selector")
13064            .count();
13065
13066        assert!(valid.errors().is_empty());
13067        assert!(dynamic.errors().is_empty());
13068        assert_eq!(invalid_selector_errors, 3);
13069    }
13070
13071    #[test]
13072    fn validates_empty_block_at_rule_preludes() {
13073        let valid = parse(
13074            "@font-face { font-family: Demo; } @starting-style { .card { opacity: 0; } } @view-transition { navigation: auto; } @page { @top-left { content: \"A\"; } } @font-feature-values Demo { @styleset { alt: 2; } }",
13075            StyleDialect::Css,
13076        );
13077        let invalid = parse(
13078            "@font-face Demo { font-family: Demo; } @starting-style demo { .card { opacity: 0; } } @view-transition demo { navigation: auto; } @page { @top-left header { content: \"A\"; } } @font-feature-values Demo { @styleset alt { alt: 2; } }",
13079            StyleDialect::Css,
13080        );
13081        let unexpected_prelude_errors = invalid
13082            .errors()
13083            .iter()
13084            .filter(|error| error.message == "unexpected at-rule prelude")
13085            .count();
13086
13087        assert!(valid.errors().is_empty());
13088        assert_eq!(unexpected_prelude_errors, 5);
13089    }
13090
13091    #[test]
13092    fn validates_font_feature_values_preludes() {
13093        let valid = parse(
13094            "@font-feature-values Demo, \"Brand Font\" { @styleset { alt: 2; } }",
13095            StyleDialect::Css,
13096        );
13097        let dynamic = parse(
13098            "@font-feature-values #{$family} { @styleset { alt: 2; } }",
13099            StyleDialect::Scss,
13100        );
13101        let invalid = parse(
13102            "@font-feature-values { @styleset { alt: 2; } } @font-feature-values 123 { @styleset { alt: 2; } }",
13103            StyleDialect::Css,
13104        );
13105        let invalid_family_name_errors = invalid
13106            .errors()
13107            .iter()
13108            .filter(|error| error.message == "invalid @font-feature-values family name")
13109            .count();
13110
13111        assert!(valid.errors().is_empty());
13112        assert!(dynamic.errors().is_empty());
13113        assert_eq!(invalid_family_name_errors, 2);
13114    }
13115
13116    #[test]
13117    fn classifies_initial_scss_at_rule_nodes() {
13118        let module_rules = parse(
13119            "@use \"sass:map\"; @forward \"tokens\";",
13120            StyleDialect::Scss,
13121        );
13122        let mixin_rule = parse("@mixin card($gap) { padding: $gap; }", StyleDialect::Scss);
13123        let module_kinds = node_kinds(&module_rules.syntax());
13124        let mixin_kinds = node_kinds(&mixin_rule.syntax());
13125
13126        assert!(module_rules.errors().is_empty());
13127        assert!(mixin_rule.errors().is_empty());
13128        assert!(module_kinds.contains(&SyntaxKind::ScssUseRule));
13129        assert!(module_kinds.contains(&SyntaxKind::ScssForwardRule));
13130        assert!(mixin_kinds.contains(&SyntaxKind::ScssMixinDeclaration));
13131    }
13132
13133    #[test]
13134    fn parses_scss_module_config_preludes() {
13135        let result = parse(
13136            "@use \"theme\" as * with ($gap: 1rem, $enabled: true); @forward \"tokens\" as token-* show $color, mixin with ($color: red);",
13137            StyleDialect::Scss,
13138        );
13139        let kinds = node_kinds(&result.syntax());
13140        let config_count = kinds
13141            .iter()
13142            .filter(|kind| **kind == SyntaxKind::ScssModuleConfig)
13143            .count();
13144
13145        assert!(result.errors().is_empty());
13146        assert!(kinds.contains(&SyntaxKind::ScssUseRule));
13147        assert!(kinds.contains(&SyntaxKind::ScssForwardRule));
13148        assert_eq!(config_count, 2);
13149    }
13150
13151    #[test]
13152    fn validates_scss_module_prelude_clauses() {
13153        let invalid = parse(
13154            "@use as *; @use \"theme\" as ; @use \"theme\" show foo; @forward \"tokens\" hide ; @forward \"tokens\" with $gap;",
13155            StyleDialect::Scss,
13156        );
13157
13158        assert_eq!(
13159            invalid
13160                .errors()
13161                .iter()
13162                .filter(|error| error.message == "expected SCSS module source")
13163                .count(),
13164            1
13165        );
13166        assert_eq!(
13167            invalid
13168                .errors()
13169                .iter()
13170                .filter(|error| error.message == "expected SCSS module namespace")
13171                .count(),
13172            1
13173        );
13174        assert_eq!(
13175            invalid
13176                .errors()
13177                .iter()
13178                .filter(|error| error.message == "unexpected SCSS module visibility clause")
13179                .count(),
13180            1
13181        );
13182        assert_eq!(
13183            invalid
13184                .errors()
13185                .iter()
13186                .filter(|error| error.message == "expected SCSS module visibility name")
13187                .count(),
13188            1
13189        );
13190        assert_eq!(
13191            invalid
13192                .errors()
13193                .iter()
13194                .filter(|error| error.message == "expected SCSS module configuration")
13195                .count(),
13196            1
13197        );
13198    }
13199
13200    #[test]
13201    fn recovers_unclosed_scss_module_config_as_bogus() {
13202        let result = parse(
13203            "@use \"theme\" with ($gap: 1rem; .card { color: red; }",
13204            StyleDialect::Scss,
13205        );
13206        let kinds = node_kinds(&result.syntax());
13207
13208        assert!(
13209            result
13210                .errors()
13211                .iter()
13212                .any(|error| error.message == "unterminated parenthesized prelude")
13213        );
13214        assert!(kinds.contains(&SyntaxKind::BogusScssModuleConfig));
13215        assert!(!kinds.contains(&SyntaxKind::ScssModuleConfig));
13216    }
13217
13218    #[test]
13219    fn parses_scss_placeholder_selectors_and_extend_refs() {
13220        let result = parse(
13221            "%button { color: red; } .primary { @extend %button; }",
13222            StyleDialect::Scss,
13223        );
13224        let kinds = node_kinds(&result.syntax());
13225
13226        assert!(result.errors().is_empty());
13227        assert!(kinds.contains(&SyntaxKind::ScssPlaceholderSelector));
13228        assert!(kinds.contains(&SyntaxKind::ScssExtendRule));
13229        assert!(token_kinds(&result.syntax()).contains(&SyntaxKind::ScssPlaceholder));
13230    }
13231
13232    #[test]
13233    fn parses_structured_scss_at_rule_bodies() {
13234        let result = parse(
13235            "@mixin card($gap) { .item { gap: $gap; } } @function double($x) { @return $x * 2; } @if $enabled { .on { color: green; } } @for $i from 1 through 3 { .n { order: $i; } } @each $k, $v in $map { .e { color: $v; } } @while $enabled { .w { color: red; } }",
13236            StyleDialect::Scss,
13237        );
13238        let kinds = node_kinds(&result.syntax());
13239
13240        assert!(result.errors().is_empty());
13241        assert!(kinds.contains(&SyntaxKind::ScssMixinDeclaration));
13242        assert!(kinds.contains(&SyntaxKind::ScssFunctionDeclaration));
13243        assert!(kinds.contains(&SyntaxKind::ScssReturnRule));
13244        assert!(kinds.contains(&SyntaxKind::ScssControlIf));
13245        assert!(kinds.contains(&SyntaxKind::ScssControlFor));
13246        assert!(kinds.contains(&SyntaxKind::ScssControlEach));
13247        assert!(kinds.contains(&SyntaxKind::ScssControlWhile));
13248        assert!(kinds.contains(&SyntaxKind::DeclarationList));
13249        assert!(kinds.contains(&SyntaxKind::Rule));
13250        assert!(kinds.contains(&SyntaxKind::ClassSelector));
13251        assert!(kinds.contains(&SyntaxKind::ScssVariableReference));
13252    }
13253
13254    #[test]
13255    fn validates_scss_control_preludes() {
13256        let invalid = parse(
13257            "@if { .a { color: red; } } @while { .b { color: red; } } @for i from 1 through 3 { .c { color: red; } } @for $i from 1 { .d { color: red; } } @each item of $items { .e { color: red; } }",
13258            StyleDialect::Scss,
13259        );
13260        let invalid_control_prelude_count = invalid
13261            .errors()
13262            .iter()
13263            .filter(|error| error.message == "invalid SCSS control prelude")
13264            .count();
13265
13266        assert_eq!(invalid_control_prelude_count, 5);
13267    }
13268
13269    #[test]
13270    fn extracts_scss_control_block_style_facts() {
13271        let facts = collect_style_facts(
13272            "@if $enabled { .on { color: green; } } @for $i from 1 through 3 { .n { order: $i; } } @each $k, $v in $map { .e { color: $v; } } @while $enabled { .w { color: red; } }",
13273            StyleDialect::Scss,
13274        );
13275        let class_names = facts
13276            .selectors
13277            .iter()
13278            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
13279            .map(|selector| selector.name.as_str())
13280            .collect::<Vec<_>>();
13281
13282        assert_eq!(class_names, vec!["on", "n", "e", "w"]);
13283    }
13284
13285    #[test]
13286    fn extracts_scss_include_content_block_style_facts() {
13287        let source =
13288            ".card { @include interactive($tone) using ($state) { &--active { color: red; } } }";
13289        let parsed = parse(source, StyleDialect::Scss);
13290        let facts = collect_style_facts(source, StyleDialect::Scss);
13291        let class_names = facts
13292            .selectors
13293            .iter()
13294            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
13295            .map(|selector| selector.name.as_str())
13296            .collect::<Vec<_>>();
13297
13298        assert!(parsed.errors().is_empty());
13299        assert!(node_kinds(&parsed.syntax()).contains(&SyntaxKind::ScssIncludeRule));
13300        assert_eq!(class_names, vec!["card", "card--active"]);
13301    }
13302
13303    #[test]
13304    fn parses_scss_nested_property_blocks() {
13305        let result = parse(
13306            ".card { font: { size: 1rem; weight: 600; } border: 1px solid { color: red; } }",
13307            StyleDialect::Scss,
13308        );
13309        let kinds = node_kinds(&result.syntax());
13310        let nested_property_count = kinds
13311            .iter()
13312            .filter(|kind| **kind == SyntaxKind::ScssNestedProperty)
13313            .count();
13314
13315        assert!(result.errors().is_empty());
13316        assert_eq!(nested_property_count, 2);
13317        assert!(kinds.contains(&SyntaxKind::DeclarationList));
13318        assert!(kinds.contains(&SyntaxKind::Value));
13319        assert!(kinds.contains(&SyntaxKind::DimensionValue));
13320    }
13321
13322    #[test]
13323    fn parses_sass_indented_nested_property_blocks() {
13324        let result = parse(
13325            ".card\n  font:\n    size: 1rem\n    weight: 600\n",
13326            StyleDialect::Sass,
13327        );
13328        let kinds = node_kinds(&result.syntax());
13329
13330        assert!(result.errors().is_empty());
13331        assert!(kinds.contains(&SyntaxKind::ScssNestedProperty));
13332        assert!(kinds.contains(&SyntaxKind::SassIndentedBlock));
13333        assert!(kinds.contains(&SyntaxKind::DeclarationList));
13334        assert!(kinds.contains(&SyntaxKind::DimensionValue));
13335    }
13336
13337    #[test]
13338    fn parses_scss_utility_at_rules() {
13339        let result = parse(
13340            "@mixin slot { @content; } @at-root { .rooted { color: red; } } @warn $message; @debug $message; @error $message;",
13341            StyleDialect::Scss,
13342        );
13343        let kinds = node_kinds(&result.syntax());
13344
13345        assert!(result.errors().is_empty());
13346        assert!(kinds.contains(&SyntaxKind::ScssContentRule));
13347        assert!(kinds.contains(&SyntaxKind::ScssAtRootRule));
13348        assert!(kinds.contains(&SyntaxKind::ScssWarnRule));
13349        assert!(kinds.contains(&SyntaxKind::ScssDebugRule));
13350        assert!(kinds.contains(&SyntaxKind::ScssErrorRule));
13351        assert!(kinds.contains(&SyntaxKind::Rule));
13352    }
13353
13354    #[test]
13355    fn structures_css_value_function_calls() {
13356        let result = parse(".a { width: calc(var(--gap) + 1rem); }", StyleDialect::Css);
13357        let kinds = node_kinds(&result.syntax());
13358
13359        assert!(result.errors().is_empty());
13360        assert!(kinds.contains(&SyntaxKind::Value));
13361        assert!(kinds.contains(&SyntaxKind::FunctionCall));
13362        assert!(kinds.contains(&SyntaxKind::FunctionArguments));
13363        assert!(kinds.contains(&SyntaxKind::CalcFunction));
13364        assert!(kinds.contains(&SyntaxKind::VarFunction));
13365        assert!(kinds.contains(&SyntaxKind::BinaryExpression));
13366    }
13367
13368    #[test]
13369    fn structures_modern_css_value_functions() {
13370        let result = parse(
13371            ".a { color: color-mix(in oklch, var(--brand), white 20%); accent-color: device-cmyk(0 1 1 0); width: clamp(1rem, 2vw, 3rem); content: attr(data-label string, \"x\"); padding: env(safe-area-inset-top); background-image: linear-gradient(red, blue); transform: translateX(1rem) rotate(10deg); filter: blur(2px) brightness(1.1); image-set: image-set(url(a.png) 1x); offset-path: path(\"M0,0 L1,1\"); }",
13372            StyleDialect::Css,
13373        );
13374        let kinds = node_kinds(&result.syntax());
13375
13376        assert!(result.errors().is_empty());
13377        assert!(kinds.contains(&SyntaxKind::ColorValue));
13378        assert!(kinds.contains(&SyntaxKind::MathFunction));
13379        assert!(kinds.contains(&SyntaxKind::AttrFunction));
13380        assert!(kinds.contains(&SyntaxKind::EnvFunction));
13381        assert!(kinds.contains(&SyntaxKind::VarFunction));
13382        assert!(kinds.contains(&SyntaxKind::GradientFunction));
13383        assert!(kinds.contains(&SyntaxKind::TransformFunction));
13384        assert!(kinds.contains(&SyntaxKind::FilterFunction));
13385        assert!(kinds.contains(&SyntaxKind::ImageFunction));
13386        assert!(kinds.contains(&SyntaxKind::ShapeFunction));
13387    }
13388
13389    #[test]
13390    fn validates_color_function_micro_grammars() {
13391        let valid = parse(
13392            ".a { color: color-mix(in srgb, red, blue 30%); background: light-dark(white, black); border-color: contrast-color(red); }",
13393            StyleDialect::Css,
13394        );
13395        let dynamic = parse(
13396            ".a { color: color-mix(#{$space}, red, blue); }",
13397            StyleDialect::Scss,
13398        );
13399        let invalid = parse(
13400            ".a { color: color-mix(srgb, red, blue); background: light-dark(white); border-color: contrast-color(red, blue); outline-color: color-mix(in srgb, red); }",
13401            StyleDialect::Css,
13402        );
13403        let invalid_argument_head_count = invalid
13404            .errors()
13405            .iter()
13406            .filter(|error| error.message == "invalid function argument head")
13407            .count();
13408        let invalid_argument_count = invalid
13409            .errors()
13410            .iter()
13411            .filter(|error| error.message == "invalid function argument count")
13412            .count();
13413
13414        assert!(valid.errors().is_empty());
13415        assert!(dynamic.errors().is_empty());
13416        assert_eq!(invalid_argument_head_count, 1);
13417        assert_eq!(invalid_argument_count, 3);
13418    }
13419
13420    #[test]
13421    fn classifies_css_value_functions_case_insensitively() {
13422        let result = parse(
13423            ".a { width: CALC(1px + 2px); color: COLOR-MIX(in srgb, red, blue); transform: TRANSLATEX(1px); filter: BLUR(2px); clip-path: POLYGON(0 0, 100% 0, 100% 100%); }",
13424            StyleDialect::Css,
13425        );
13426        let kinds = node_kinds(&result.syntax());
13427
13428        assert!(result.errors().is_empty());
13429        assert!(kinds.contains(&SyntaxKind::CalcFunction));
13430        assert!(kinds.contains(&SyntaxKind::ColorValue));
13431        assert!(kinds.contains(&SyntaxKind::TransformFunction));
13432        assert!(kinds.contains(&SyntaxKind::FilterFunction));
13433        assert!(kinds.contains(&SyntaxKind::ShapeFunction));
13434    }
13435
13436    #[test]
13437    fn validates_values_l4_math_function_argument_counts() {
13438        let valid = parse(
13439            ".a { width: calc(1px + 2px); min-width: min(1px, 2px); max-width: max(1px); margin: round(nearest, 10px, 3px); padding: hypot(3px, 4px); opacity: log(8, 2); }",
13440            StyleDialect::Css,
13441        );
13442        let invalid = parse(
13443            ".a { width: calc(1px, 2px); min-width: min(); max-width: clamp(1px, 2px); margin: mod(10px); padding: sin(); opacity: atan2(1); }",
13444            StyleDialect::Css,
13445        );
13446        let invalid_argument_count = invalid
13447            .errors()
13448            .iter()
13449            .filter(|error| error.message == "invalid function argument count")
13450            .count();
13451
13452        assert!(valid.errors().is_empty());
13453        assert_eq!(invalid_argument_count, 6);
13454    }
13455
13456    #[test]
13457    fn validates_values_l4_math_function_empty_arguments() {
13458        let valid_fallback = parse(
13459            ".a { color: var(--brand,); padding: env(safe-area-inset-top,); }",
13460            StyleDialect::Css,
13461        );
13462        let invalid = parse(
13463            ".a { width: min(, 1px); height: max(1px,); inset: clamp(1px, , 3px); }",
13464            StyleDialect::Css,
13465        );
13466        let empty_argument_count = invalid
13467            .errors()
13468            .iter()
13469            .filter(|error| error.message == "empty function argument")
13470            .count();
13471
13472        assert!(valid_fallback.errors().is_empty());
13473        assert_eq!(empty_argument_count, 3);
13474    }
13475
13476    #[test]
13477    fn validates_var_env_attr_function_argument_heads() {
13478        let valid = parse(
13479            ".a { color: var(--brand, red, blue); padding: env(safe-area-inset-top, 0px); content: attr(data-label string, \"x\"); }",
13480            StyleDialect::Css,
13481        );
13482        let dynamic = parse(
13483            ".a { color: var(#{$name}); padding: env($area); content: attr(#{$attribute}); }",
13484            StyleDialect::Scss,
13485        );
13486        let invalid = parse(
13487            ".a { color: var(color); padding: env(, 0px); content: attr(123); }",
13488            StyleDialect::Css,
13489        );
13490        let invalid_head_count = invalid
13491            .errors()
13492            .iter()
13493            .filter(|error| error.message == "invalid function argument head")
13494            .count();
13495
13496        assert!(valid.errors().is_empty());
13497        assert!(dynamic.errors().is_empty());
13498        assert_eq!(invalid_head_count, 3);
13499    }
13500
13501    #[test]
13502    fn structures_css_value_atoms_and_function_argument_lists() {
13503        let result = parse(
13504            ".a { color: #fff; width: clamp(1rem, calc(2px + 3px), 4rem); opacity: 50%; z-index: 1; font-family: system, \"Demo\"; unicode-range: U+00A0-00FF; }",
13505            StyleDialect::Css,
13506        );
13507        let kinds = node_kinds(&result.syntax());
13508        let dimension_value_count = kinds
13509            .iter()
13510            .filter(|kind| **kind == SyntaxKind::DimensionValue)
13511            .count();
13512        let number_value_count = kinds
13513            .iter()
13514            .filter(|kind| **kind == SyntaxKind::NumberValue)
13515            .count();
13516        let percentage_value_count = kinds
13517            .iter()
13518            .filter(|kind| **kind == SyntaxKind::PercentageValue)
13519            .count();
13520
13521        assert!(result.errors().is_empty());
13522        assert!(kinds.contains(&SyntaxKind::ColorValue));
13523        assert!(kinds.contains(&SyntaxKind::ValueList));
13524        assert!(kinds.contains(&SyntaxKind::CalcFunction));
13525        assert!(kinds.contains(&SyntaxKind::BinaryExpression));
13526        assert!(kinds.contains(&SyntaxKind::IdentifierValue));
13527        assert!(kinds.contains(&SyntaxKind::StringValue));
13528        assert!(kinds.contains(&SyntaxKind::UnicodeRangeValue));
13529        assert!(dimension_value_count >= 4);
13530        assert!(number_value_count >= 1);
13531        assert!(percentage_value_count >= 1);
13532    }
13533
13534    #[test]
13535    fn parses_custom_property_values_as_component_value_lists() {
13536        let result = parse(
13537            ".a { --api: { display: none }; --empty: ; color: red; }",
13538            StyleDialect::Css,
13539        );
13540        let kinds = node_kinds(&result.syntax());
13541        let tokens = token_kinds(&result.syntax());
13542        let component_value_list_count = kinds
13543            .iter()
13544            .filter(|kind| **kind == SyntaxKind::ComponentValueList)
13545            .count();
13546
13547        assert!(result.errors().is_empty());
13548        assert!(tokens.contains(&SyntaxKind::CustomPropertyName));
13549        assert!(kinds.contains(&SyntaxKind::CustomPropertyValue));
13550        assert!(kinds.contains(&SyntaxKind::SimpleBlock));
13551        assert_eq!(component_value_list_count, 2);
13552        assert!(!kinds.contains(&SyntaxKind::BogusValue));
13553    }
13554
13555    #[test]
13556    fn structures_top_level_value_lists_without_function_comma_confusion() {
13557        let result = parse(
13558            ".a { font-family: system, sans-serif; color: color-mix(in oklch, red, blue); }",
13559            StyleDialect::Css,
13560        );
13561        let kinds = node_kinds(&result.syntax());
13562
13563        assert!(result.errors().is_empty());
13564        assert!(kinds.contains(&SyntaxKind::ValueList));
13565        assert!(!kinds.contains(&SyntaxKind::BogusValueList));
13566        assert!(kinds.contains(&SyntaxKind::ColorValue));
13567    }
13568
13569    #[test]
13570    fn structures_bracketed_value_atoms_and_recovery() {
13571        let closed = parse(
13572            ".grid { grid-template-columns: [full-start] minmax(0, 1fr) [full-end]; }",
13573            StyleDialect::Css,
13574        );
13575        let missing_close = parse(
13576            ".grid { grid-template-columns: [full-start 1fr; }",
13577            StyleDialect::Css,
13578        );
13579
13580        assert!(closed.errors().is_empty());
13581        assert!(node_kinds(&closed.syntax()).contains(&SyntaxKind::BracketedValue));
13582        assert!(node_kinds(&missing_close.syntax()).contains(&SyntaxKind::BogusBracketedValue));
13583    }
13584
13585    #[test]
13586    fn recovers_bogus_top_level_value_lists() {
13587        let result = parse(".a { font-family: system, ; }", StyleDialect::Css);
13588        let kinds = node_kinds(&result.syntax());
13589
13590        assert!(kinds.contains(&SyntaxKind::BogusValueList));
13591    }
13592
13593    #[test]
13594    fn keeps_important_annotation_in_declaration_values() {
13595        let result = parse(".a { color: red !important; }", StyleDialect::Css);
13596        let split = parse(
13597            ".a { color: red ! /* keep */ important; }",
13598            StyleDialect::Css,
13599        );
13600        let kinds = node_kinds(&result.syntax());
13601        let split_kinds = node_kinds(&split.syntax());
13602
13603        assert!(result.errors().is_empty());
13604        assert!(split.errors().is_empty());
13605        assert!(kinds.contains(&SyntaxKind::Declaration));
13606        assert!(kinds.contains(&SyntaxKind::Value));
13607        assert!(kinds.contains(&SyntaxKind::ImportantAnnotation));
13608        assert!(split_kinds.contains(&SyntaxKind::ImportantAnnotation));
13609        assert!(token_kinds(&result.syntax()).contains(&SyntaxKind::Important));
13610        assert!(token_kinds(&split.syntax()).contains(&SyntaxKind::Ident));
13611    }
13612
13613    #[test]
13614    fn structures_url_values() {
13615        let result = parse(
13616            ".a { background: url(images/bg.png); mask: url(\"icons/mask.svg\"); }",
13617            StyleDialect::Css,
13618        );
13619        let kinds = node_kinds(&result.syntax());
13620        let url_value_count = kinds
13621            .iter()
13622            .filter(|kind| **kind == SyntaxKind::UrlValue)
13623            .count();
13624
13625        assert!(result.errors().is_empty());
13626        assert!(kinds.contains(&SyntaxKind::Value));
13627        assert!(kinds.contains(&SyntaxKind::FunctionCall));
13628        assert_eq!(url_value_count, 2);
13629        assert!(token_kinds(&result.syntax()).contains(&SyntaxKind::Url));
13630    }
13631
13632    #[test]
13633    fn structures_bad_strings_as_bogus_values() {
13634        let result = parse(".a { content: \"bad\ncolor: red; }", StyleDialect::Css);
13635        let kinds = node_kinds(&result.syntax());
13636
13637        assert!(
13638            result
13639                .errors()
13640                .iter()
13641                .any(|error| error.code == ParseErrorCode::UnterminatedString)
13642        );
13643        assert!(kinds.contains(&SyntaxKind::BogusValue));
13644        assert!(token_kinds(&result.syntax()).contains(&SyntaxKind::BadString));
13645    }
13646
13647    #[test]
13648    fn structures_scss_interpolation_in_selector_property_and_value() {
13649        let result = parse(
13650            ".button-#{$variant} { #{$prop}: #{$value}; }",
13651            StyleDialect::Scss,
13652        );
13653        let kinds = node_kinds(&result.syntax());
13654        let interpolation_count = kinds
13655            .iter()
13656            .filter(|kind| **kind == SyntaxKind::Interpolation)
13657            .count();
13658
13659        assert!(result.errors().is_empty());
13660        assert_eq!(interpolation_count, 3);
13661        assert!(kinds.contains(&SyntaxKind::ClassSelector));
13662        assert!(kinds.contains(&SyntaxKind::PropertyName));
13663        assert!(kinds.contains(&SyntaxKind::Value));
13664    }
13665
13666    #[test]
13667    fn structures_less_interpolation_in_selector_property_and_value() {
13668        let result = parse(
13669            ".button-@{variant} { @{prop}: @{value}; }",
13670            StyleDialect::Less,
13671        );
13672        let kinds = node_kinds(&result.syntax());
13673        let interpolation_count = kinds
13674            .iter()
13675            .filter(|kind| **kind == SyntaxKind::Interpolation)
13676            .count();
13677
13678        assert!(result.errors().is_empty());
13679        assert_eq!(interpolation_count, 3);
13680        assert!(kinds.contains(&SyntaxKind::ClassSelector));
13681        assert!(kinds.contains(&SyntaxKind::PropertyName));
13682        assert!(kinds.contains(&SyntaxKind::Value));
13683    }
13684
13685    #[test]
13686    fn structures_less_escaped_strings_as_values() {
13687        let result = parse(".a { filter: ~\"alpha(opacity=50)\"; }", StyleDialect::Less);
13688        let kinds = node_kinds(&result.syntax());
13689        let token_kinds = token_kinds(&result.syntax());
13690
13691        assert!(result.errors().is_empty());
13692        assert!(kinds.contains(&SyntaxKind::Value));
13693        assert!(token_kinds.contains(&SyntaxKind::LessEscapedString));
13694    }
13695
13696    #[test]
13697    fn structures_less_property_variables_as_values() {
13698        let result = parse(".a { color: red; background: $color; }", StyleDialect::Less);
13699        let kinds = node_kinds(&result.syntax());
13700        let token_kinds = token_kinds(&result.syntax());
13701
13702        assert!(result.errors().is_empty());
13703        assert!(kinds.contains(&SyntaxKind::LessPropertyVariable));
13704        assert!(token_kinds.contains(&SyntaxKind::LessPropertyVariableToken));
13705    }
13706
13707    #[test]
13708    fn structures_unclosed_interpolation_as_bogus() {
13709        let scss = parse(".button-#{$variant", StyleDialect::Scss);
13710        let less = parse(".button-@{variant", StyleDialect::Less);
13711
13712        assert!(node_kinds(&scss.syntax()).contains(&SyntaxKind::BogusInterpolation));
13713        assert!(node_kinds(&less.syntax()).contains(&SyntaxKind::BogusInterpolation));
13714        assert!(
13715            scss.errors()
13716                .iter()
13717                .any(|error| error.code == ParseErrorCode::UnexpectedCharacter)
13718        );
13719        assert!(
13720            less.errors()
13721                .iter()
13722                .any(|error| error.code == ParseErrorCode::UnexpectedCharacter)
13723        );
13724    }
13725
13726    #[test]
13727    fn structures_css_value_unary_and_precedence_expressions() {
13728        let result = parse(".a { margin: -(1rem + 2px) * 3; }", StyleDialect::Css);
13729        let kinds = node_kinds(&result.syntax());
13730
13731        assert!(result.errors().is_empty());
13732        assert!(kinds.contains(&SyntaxKind::UnaryExpression));
13733        assert!(kinds.contains(&SyntaxKind::ParenthesizedExpression));
13734        assert!(kinds.contains(&SyntaxKind::BinaryExpression));
13735    }
13736
13737    #[test]
13738    fn structures_dialect_variable_references_in_values() {
13739        let scss = parse(".a { margin: $gap; }", StyleDialect::Scss);
13740        let less = parse(".a { margin: @gap; }", StyleDialect::Less);
13741
13742        assert!(scss.errors().is_empty());
13743        assert!(less.errors().is_empty());
13744        assert!(node_kinds(&scss.syntax()).contains(&SyntaxKind::ScssVariableReference));
13745        assert!(node_kinds(&less.syntax()).contains(&SyntaxKind::LessVariableReference));
13746    }
13747
13748    #[test]
13749    fn structures_scss_variable_flags() {
13750        let result = parse(
13751            "$gap: 1rem ! /* keep */ default !global;",
13752            StyleDialect::Scss,
13753        );
13754        let kinds = node_kinds(&result.syntax());
13755        let flag_count = kinds
13756            .iter()
13757            .filter(|kind| **kind == SyntaxKind::ScssVariableFlag)
13758            .count();
13759
13760        assert!(result.errors().is_empty());
13761        assert!(kinds.contains(&SyntaxKind::ScssVariableDeclaration));
13762        assert_eq!(flag_count, 2);
13763    }
13764
13765    #[test]
13766    fn parses_less_mixin_declarations_calls_and_guards() {
13767        let result = parse(
13768            ".theme(@color) when (iscolor(@color)) { color: @color; .rounded(); } .card { .theme(#fff); }",
13769            StyleDialect::Less,
13770        );
13771        let kinds = node_kinds(&result.syntax());
13772
13773        assert!(result.errors().is_empty());
13774        assert!(kinds.contains(&SyntaxKind::LessMixinDeclaration));
13775        assert!(kinds.contains(&SyntaxKind::LessMixinGuard));
13776        assert!(kinds.contains(&SyntaxKind::LessMixinCall));
13777        assert!(kinds.contains(&SyntaxKind::LessVariableReference));
13778        assert!(kinds.contains(&SyntaxKind::Rule));
13779    }
13780
13781    #[test]
13782    fn parses_less_extend_pseudo_class_without_mixin_confusion() {
13783        let less = parse(
13784            ".nav:extend(.inline all) { color: red; }",
13785            StyleDialect::Less,
13786        );
13787        let css = parse(
13788            ".nav:extend(.inline all) { color: red; }",
13789            StyleDialect::Css,
13790        );
13791        let less_kinds = node_kinds(&less.syntax());
13792        let css_kinds = node_kinds(&css.syntax());
13793
13794        assert!(less.errors().is_empty());
13795        assert!(css.errors().is_empty());
13796        assert!(less_kinds.contains(&SyntaxKind::Rule));
13797        assert!(less_kinds.contains(&SyntaxKind::LessExtendRule));
13798        assert!(less_kinds.contains(&SyntaxKind::PseudoSelectorArgument));
13799        assert!(!less_kinds.contains(&SyntaxKind::LessMixinDeclaration));
13800        assert!(!css_kinds.contains(&SyntaxKind::LessExtendRule));
13801        assert!(css_kinds.contains(&SyntaxKind::PseudoClassSelector));
13802    }
13803
13804    #[test]
13805    fn parses_less_detached_ruleset_variable_values() {
13806        let result = parse(
13807            "@rules: { color: red; .rounded(); }; .card { color: blue; }",
13808            StyleDialect::Less,
13809        );
13810        let kinds = node_kinds(&result.syntax());
13811
13812        assert!(result.errors().is_empty());
13813        assert!(kinds.contains(&SyntaxKind::LessVariableDeclaration));
13814        assert!(kinds.contains(&SyntaxKind::LessDetachedRulesetNode));
13815        assert!(kinds.contains(&SyntaxKind::DeclarationList));
13816        assert!(kinds.contains(&SyntaxKind::Declaration));
13817        assert!(kinds.contains(&SyntaxKind::LessMixinCall));
13818        assert!(kinds.contains(&SyntaxKind::Rule));
13819    }
13820
13821    #[test]
13822    fn recovers_unclosed_less_detached_rulesets_as_bogus() {
13823        let result = parse("@rules: { color: red;", StyleDialect::Less);
13824        let kinds = node_kinds(&result.syntax());
13825
13826        assert!(kinds.contains(&SyntaxKind::BogusLessDetachedRuleset));
13827        assert!(
13828            result
13829                .errors()
13830                .iter()
13831                .any(|error| error.code == ParseErrorCode::UnexpectedCharacter)
13832        );
13833    }
13834
13835    #[test]
13836    fn parses_less_namespace_access_calls() {
13837        let result = parse(
13838            ".card { #bundle > .rounded(); color: blue; }",
13839            StyleDialect::Less,
13840        );
13841        let kinds = node_kinds(&result.syntax());
13842
13843        assert!(result.errors().is_empty());
13844        assert!(kinds.contains(&SyntaxKind::LessNamespaceAccess));
13845        assert!(kinds.contains(&SyntaxKind::LessMixinCall));
13846        assert!(kinds.contains(&SyntaxKind::Declaration));
13847    }
13848
13849    #[test]
13850    fn keeps_nested_selectors_separate_from_less_namespace_access() {
13851        let result = parse(
13852            ".card { #child > .leaf { color: red; } }",
13853            StyleDialect::Less,
13854        );
13855        let kinds = node_kinds(&result.syntax());
13856
13857        assert!(result.errors().is_empty());
13858        assert!(kinds.contains(&SyntaxKind::Rule));
13859        assert!(!kinds.contains(&SyntaxKind::LessNamespaceAccess));
13860    }
13861
13862    #[test]
13863    fn extracts_initial_style_facts_from_parser_surface() {
13864        let facts = collect_style_facts(
13865            "@use \"tokens\"; $gap: 1rem; %surface { color: red; } .card#main { --space: $gap; }",
13866            StyleDialect::Scss,
13867        );
13868
13869        assert_eq!(facts.product, "omena-parser.style-facts");
13870        assert_eq!(facts.dialect, StyleDialect::Scss);
13871        assert_eq!(facts.selector_count, 3);
13872        assert_eq!(facts.variable_count, 3);
13873        assert_eq!(facts.at_rule_count, 1);
13874        assert!(facts.selectors.iter().any(|selector| {
13875            selector.kind == ParsedSelectorFactKind::Class && selector.name == "card"
13876        }));
13877        assert!(facts.selectors.iter().any(|selector| {
13878            selector.kind == ParsedSelectorFactKind::Id && selector.name == "main"
13879        }));
13880        assert!(facts.selectors.iter().any(|selector| {
13881            selector.kind == ParsedSelectorFactKind::Placeholder && selector.name == "surface"
13882        }));
13883        assert!(facts.variables.iter().any(|variable| {
13884            variable.kind == ParsedVariableFactKind::ScssDeclaration && variable.name == "$gap"
13885        }));
13886        assert!(facts.variables.iter().any(|variable| {
13887            variable.kind == ParsedVariableFactKind::ScssReference && variable.name == "$gap"
13888        }));
13889        assert!(facts.variables.iter().any(|variable| {
13890            variable.kind == ParsedVariableFactKind::CustomPropertyDeclaration
13891                && variable.name == "--space"
13892        }));
13893        assert_eq!(facts.at_rules[0].node_kind, Some(SyntaxKind::ScssUseRule));
13894    }
13895
13896    #[test]
13897    fn summarizes_style_facts_as_parser_owned_product() {
13898        let summary = summarize_omena_parser_style_facts(
13899            "@use \"tokens\"; $gap: 1rem; .card { --space: $gap; }",
13900            StyleDialect::Scss,
13901        );
13902
13903        assert_eq!(summary.schema_version, "0");
13904        assert_eq!(summary.product, "omena-parser.style-facts");
13905        assert_eq!(summary.dialect, "scss");
13906        assert_eq!(summary.parser_error_count, 0);
13907        assert_eq!(summary.class_selector_names, vec!["card".to_string()]);
13908        assert_eq!(summary.variable_names, vec!["$gap".to_string()]);
13909        assert_eq!(summary.custom_property_names, vec!["--space".to_string()]);
13910        assert_eq!(summary.sass_module_use_sources, vec!["tokens".to_string()]);
13911    }
13912
13913    #[test]
13914    fn extracts_sass_symbol_style_facts() {
13915        let facts = collect_style_facts(
13916            "@mixin tone($color) { color: $color; } @function double($x) { @return $x * 2; } .card { @include tone(red); width: double(2px); }",
13917            StyleDialect::Scss,
13918        );
13919        let symbol_kinds = facts
13920            .sass_symbols
13921            .iter()
13922            .map(|symbol| (symbol.kind, symbol.name.as_str(), symbol.role))
13923            .collect::<Vec<_>>();
13924
13925        assert_eq!(facts.sass_symbol_count, 8);
13926        assert!(symbol_kinds.contains(&(
13927            ParsedSassSymbolFactKind::MixinDeclaration,
13928            "tone",
13929            "declaration"
13930        )));
13931        assert!(symbol_kinds.contains(&(
13932            ParsedSassSymbolFactKind::MixinInclude,
13933            "tone",
13934            "include"
13935        )));
13936        assert!(symbol_kinds.contains(&(
13937            ParsedSassSymbolFactKind::FunctionDeclaration,
13938            "double",
13939            "declaration"
13940        )));
13941        assert!(symbol_kinds.contains(&(ParsedSassSymbolFactKind::FunctionCall, "double", "call")));
13942        assert!(symbol_kinds.contains(&(
13943            ParsedSassSymbolFactKind::VariableDeclaration,
13944            "color",
13945            "declaration"
13946        )));
13947        assert!(symbol_kinds.contains(&(
13948            ParsedSassSymbolFactKind::VariableReference,
13949            "color",
13950            "reference"
13951        )));
13952    }
13953
13954    #[test]
13955    fn extracts_namespaced_sass_symbol_style_facts() {
13956        let facts = collect_style_facts(
13957            r#"@use "./tokens" as tokens; .card { color: tokens.$brand; @include tokens.tone(red); width: tokens.double(2px); }"#,
13958            StyleDialect::Scss,
13959        );
13960        let symbol_kinds = facts
13961            .sass_symbols
13962            .iter()
13963            .map(|symbol| {
13964                (
13965                    symbol.kind,
13966                    symbol.name.as_str(),
13967                    symbol.role,
13968                    symbol.namespace.as_deref(),
13969                )
13970            })
13971            .collect::<Vec<_>>();
13972
13973        assert_eq!(facts.sass_symbol_count, 3);
13974        assert!(symbol_kinds.contains(&(
13975            ParsedSassSymbolFactKind::VariableReference,
13976            "brand",
13977            "reference",
13978            Some("tokens")
13979        )));
13980        assert!(symbol_kinds.contains(&(
13981            ParsedSassSymbolFactKind::MixinInclude,
13982            "tone",
13983            "include",
13984            Some("tokens")
13985        )));
13986        assert!(symbol_kinds.contains(&(
13987            ParsedSassSymbolFactKind::FunctionCall,
13988            "double",
13989            "call",
13990            Some("tokens")
13991        )));
13992        assert_eq!(facts.sass_include_count, 1);
13993        assert_eq!(facts.sass_includes[0].name, "tone");
13994        assert_eq!(facts.sass_includes[0].namespace.as_deref(), Some("tokens"));
13995        assert_eq!(facts.sass_includes[0].params, "(red)");
13996    }
13997
13998    #[test]
13999    fn extracts_sass_module_edge_style_facts() {
14000        let facts = collect_style_facts(
14001            r#"@use "./tokens" as tokens; @use "./reset" as *; @use "sass:map"; @forward "./theme" show $brand, tone; @import "legacy", url("print.css");"#,
14002            StyleDialect::Scss,
14003        );
14004
14005        assert_eq!(facts.sass_module_edge_count, 6);
14006        assert!(facts.sass_module_edges.iter().any(|edge| {
14007            edge.kind == ParsedSassModuleEdgeFactKind::Use
14008                && edge.source == "./tokens"
14009                && edge.namespace_kind == Some("alias")
14010                && edge.namespace.as_deref() == Some("tokens")
14011        }));
14012        assert!(facts.sass_module_edges.iter().any(|edge| {
14013            edge.kind == ParsedSassModuleEdgeFactKind::Use
14014                && edge.source == "./reset"
14015                && edge.namespace_kind == Some("wildcard")
14016                && edge.namespace.is_none()
14017        }));
14018        assert!(facts.sass_module_edges.iter().any(|edge| {
14019            edge.kind == ParsedSassModuleEdgeFactKind::Use
14020                && edge.source == "sass:map"
14021                && edge.namespace_kind == Some("default")
14022                && edge.namespace.as_deref() == Some("map")
14023        }));
14024        assert!(facts.sass_module_edges.iter().any(|edge| {
14025            edge.kind == ParsedSassModuleEdgeFactKind::Forward
14026                && edge.source == "./theme"
14027                && edge.visibility_filter_kind == Some("show")
14028                && edge.visibility_filter_names == vec!["brand", "tone"]
14029        }));
14030        assert!(facts.sass_module_edges.iter().any(|edge| {
14031            edge.kind == ParsedSassModuleEdgeFactKind::Import && edge.source == "legacy"
14032        }));
14033    }
14034
14035    #[test]
14036    fn extracts_animation_name_style_facts() {
14037        let facts = collect_style_facts(
14038            "@keyframes fade { from { opacity: 0; } to { opacity: 1; } } @keyframes \"slide\" { to { opacity: 1; } } .card { animation-name: fade, \"slide\", none; }",
14039            StyleDialect::Css,
14040        );
14041        let keyframe_names = facts
14042            .animations
14043            .iter()
14044            .filter(|animation| animation.kind == ParsedAnimationFactKind::KeyframesDeclaration)
14045            .map(|animation| animation.name.as_str())
14046            .collect::<Vec<_>>();
14047        let reference_names = facts
14048            .animations
14049            .iter()
14050            .filter(|animation| animation.kind == ParsedAnimationFactKind::AnimationNameReference)
14051            .map(|animation| animation.name.as_str())
14052            .collect::<Vec<_>>();
14053
14054        assert_eq!(facts.animation_count, 4);
14055        assert_eq!(keyframe_names, vec!["fade", "slide"]);
14056        assert_eq!(reference_names, vec!["fade", "slide"]);
14057    }
14058
14059    #[test]
14060    fn extracts_animation_shorthand_style_facts() {
14061        let facts = collect_style_facts(
14062            "@keyframes fade { to { opacity: 1; } } @keyframes \"slide\" { to { opacity: 1; } } .card { animation: 1s ease-in fade, \"slide\" 2s linear both, none 1s, var(--anim) 1s; }",
14063            StyleDialect::Css,
14064        );
14065        let keyframe_names = facts
14066            .animations
14067            .iter()
14068            .filter(|animation| animation.kind == ParsedAnimationFactKind::KeyframesDeclaration)
14069            .map(|animation| animation.name.as_str())
14070            .collect::<Vec<_>>();
14071        let reference_names = facts
14072            .animations
14073            .iter()
14074            .filter(|animation| animation.kind == ParsedAnimationFactKind::AnimationNameReference)
14075            .map(|animation| animation.name.as_str())
14076            .collect::<Vec<_>>();
14077
14078        assert_eq!(facts.animation_count, 4);
14079        assert_eq!(keyframe_names, vec!["fade", "slide"]);
14080        assert_eq!(reference_names, vec!["fade", "slide"]);
14081    }
14082
14083    #[test]
14084    fn keeps_at_rule_header_dashed_idents_out_of_custom_property_facts() {
14085        let facts = collect_style_facts(
14086            "@property --accent { syntax: \"<color>\"; inherits: false; initial-value: red; } @font-palette-values --brand { font-family: Demo; } @color-profile --display-p3 { src: url(p3.icc); } @position-try --popover { inset-area: top; }",
14087            StyleDialect::Css,
14088        );
14089        let custom_properties: Vec<&str> = facts
14090            .variables
14091            .iter()
14092            .filter(|variable| {
14093                matches!(
14094                    variable.kind,
14095                    ParsedVariableFactKind::CustomPropertyDeclaration
14096                        | ParsedVariableFactKind::CustomPropertyReference
14097                )
14098            })
14099            .map(|variable| variable.name.as_str())
14100            .collect();
14101
14102        assert_eq!(custom_properties, vec!["--accent"]);
14103    }
14104
14105    #[test]
14106    fn extracts_all_top_level_classes_from_complex_selector_headers() {
14107        let facts = collect_style_facts(
14108            "#app.theme > .card:has(> .icon) { color: red; }",
14109            StyleDialect::Css,
14110        );
14111        let class_names: Vec<&str> = facts
14112            .selectors
14113            .iter()
14114            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14115            .map(|selector| selector.name.as_str())
14116            .collect();
14117
14118        assert_eq!(class_names, vec!["theme", "card"]);
14119    }
14120
14121    #[test]
14122    fn extracts_css_nesting_at_rule_selector_facts() {
14123        let facts = collect_style_facts(
14124            ".card { @nest &__icon { color: red; &--active { color: blue; } } }",
14125            StyleDialect::Css,
14126        );
14127        let class_names: Vec<&str> = facts
14128            .selectors
14129            .iter()
14130            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14131            .map(|selector| selector.name.as_str())
14132            .collect();
14133
14134        assert_eq!(
14135            class_names,
14136            vec!["card", "card__icon", "card__icon--active"]
14137        );
14138    }
14139
14140    #[test]
14141    fn parses_mid_typing_char_boundary_edits_without_panicking() {
14142        let fixtures = [
14143            (
14144                StyleDialect::Css,
14145                ".card { color: color-mix(in oklch, red, blue); }",
14146            ),
14147            (
14148                StyleDialect::Scss,
14149                "@use \"tokens\" with ($gap: 1rem); .card { &__아이콘 { color: $gap; } }",
14150            ),
14151            (
14152                StyleDialect::Sass,
14153                ".card\n  color: red\n  &__icon\n    color: blue\n",
14154            ),
14155            (
14156                StyleDialect::Less,
14157                "@tone: red; .card() when (iscolor(@tone)) { color: @tone; }",
14158            ),
14159        ];
14160        let insertions = [" ", "{", "}", ":", "@media (", "한"];
14161
14162        for (dialect, source) in fixtures {
14163            for offset in char_boundary_offsets(source) {
14164                for insertion in insertions {
14165                    let mut edited = source.to_string();
14166                    edited.insert_str(offset, insertion);
14167                    let _ = parse(&edited, dialect);
14168                }
14169            }
14170        }
14171    }
14172
14173    #[test]
14174    fn parses_deterministic_malformed_byte_corpus_without_panicking() {
14175        let mut byte_fixtures = vec![
14176            Vec::new(),
14177            b"\0".to_vec(),
14178            b"\xef\xbb\xbf.card { color: red; }".to_vec(),
14179            b".a { content: \"unterminated".to_vec(),
14180            b".a { background: url(foo bar) }".to_vec(),
14181            b"@media screen { .a { color: red".to_vec(),
14182            b".a { --x: { [ ( ; }".to_vec(),
14183            vec![0xff, b'.', b'a', b' ', b'{', b'}'],
14184            vec![0xe1, 0x84, b'.', b'a', b'{', b'c', b':', b'r'],
14185        ];
14186        for seed in 0..32u32 {
14187            byte_fixtures.push(deterministic_byte_fixture(seed));
14188        }
14189
14190        for bytes in byte_fixtures {
14191            let source = String::from_utf8_lossy(&bytes).into_owned();
14192            for dialect in [
14193                StyleDialect::Css,
14194                StyleDialect::Scss,
14195                StyleDialect::Sass,
14196                StyleDialect::Less,
14197            ] {
14198                let parse_result = std::panic::catch_unwind(|| parse(&source, dialect));
14199                assert!(
14200                    parse_result.is_ok(),
14201                    "parse panicked for dialect={dialect:?} source={source:?}"
14202                );
14203                let Ok(parse_result) = parse_result else {
14204                    continue;
14205                };
14206
14207                let lex_result = std::panic::catch_unwind(|| lex(&source, dialect));
14208                assert!(
14209                    lex_result.is_ok(),
14210                    "lex panicked for dialect={dialect:?} source={source:?}"
14211                );
14212                let Ok(lex_result) = lex_result else {
14213                    continue;
14214                };
14215
14216                assert_eq!(parse_result.syntax().kind(), SyntaxKind::Root);
14217                assert_lex_ranges_are_char_boundaries(&source, lex_result.tokens());
14218            }
14219        }
14220    }
14221
14222    #[test]
14223    fn preserves_lossless_cst_text_for_valid_corpus() {
14224        let fixtures = [
14225            (
14226                StyleDialect::Css,
14227                ".card { color: red; --space: calc(1rem + 2px); }",
14228            ),
14229            (
14230                StyleDialect::Scss,
14231                "@use \"tokens\"; .card { &__icon { color: $accent; } }",
14232            ),
14233            (
14234                StyleDialect::Sass,
14235                ".card\n  color: red\n  &__icon\n    color: blue\n",
14236            ),
14237            (
14238                StyleDialect::Less,
14239                "@tone: red; .card() when (iscolor(@tone)) { color: @tone; }",
14240            ),
14241        ];
14242
14243        for (dialect, source) in fixtures {
14244            let result = parse(source, dialect);
14245            let syntax = result.syntax();
14246
14247            assert_eq!(syntax.kind(), SyntaxKind::Root);
14248            assert_eq!(source_text(&syntax).as_deref(), Some(source));
14249            assert_eq!(result.source_text().as_deref(), Some(source));
14250
14251            let reparsed = parse(&result.source_text().unwrap_or_default(), dialect);
14252            assert_eq!(reparsed.source_text().as_deref(), Some(source));
14253            assert_eq!(reparsed.syntax().kind(), SyntaxKind::Root);
14254        }
14255    }
14256
14257    #[test]
14258    fn extracts_nested_bem_style_facts_with_parent_context() {
14259        let facts = collect_style_facts(
14260            ".card { &__icon { &--small { color: red; } } --space: 1rem; color: var(--space); }",
14261            StyleDialect::Scss,
14262        );
14263        let class_names: Vec<&str> = facts
14264            .selectors
14265            .iter()
14266            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14267            .map(|selector| selector.name.as_str())
14268            .collect();
14269        let custom_properties: Vec<&str> = facts
14270            .variables
14271            .iter()
14272            .map(|variable| variable.name.as_str())
14273            .collect();
14274
14275        assert_eq!(class_names, vec!["card", "card__icon", "card__icon--small"]);
14276        assert!(custom_properties.contains(&"--space"));
14277        assert!(!custom_properties.contains(&"--small"));
14278        assert_eq!(facts.error_count, 0);
14279    }
14280
14281    #[test]
14282    fn extracts_non_bem_ampersand_suffix_style_facts() {
14283        let facts = collect_style_facts(
14284            ".btn { &-legacy {} &_legacy {} &suffix {} }",
14285            StyleDialect::Scss,
14286        );
14287        let class_names: Vec<&str> = facts
14288            .selectors
14289            .iter()
14290            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14291            .map(|selector| selector.name.as_str())
14292            .collect();
14293
14294        assert_eq!(
14295            class_names,
14296            vec!["btn", "btn-legacy", "btn_legacy", "btnsuffix"]
14297        );
14298        assert_eq!(facts.error_count, 0);
14299    }
14300
14301    #[test]
14302    fn ignores_non_defining_selector_function_arguments() {
14303        let facts = collect_style_facts(
14304            ".btn:is(.active, .primary):has(#target, %surface) { color: red; }",
14305            StyleDialect::Scss,
14306        );
14307        let class_names: Vec<&str> = facts
14308            .selectors
14309            .iter()
14310            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14311            .map(|selector| selector.name.as_str())
14312            .collect();
14313        let id_names: Vec<&str> = facts
14314            .selectors
14315            .iter()
14316            .filter(|selector| selector.kind == ParsedSelectorFactKind::Id)
14317            .map(|selector| selector.name.as_str())
14318            .collect();
14319        let placeholder_names: Vec<&str> = facts
14320            .selectors
14321            .iter()
14322            .filter(|selector| selector.kind == ParsedSelectorFactKind::Placeholder)
14323            .map(|selector| selector.name.as_str())
14324            .collect();
14325
14326        assert_eq!(class_names, vec!["btn"]);
14327        assert!(id_names.is_empty());
14328        assert!(placeholder_names.is_empty());
14329    }
14330
14331    #[test]
14332    fn filters_css_module_global_scope_selector_facts() {
14333        let facts = collect_style_facts(
14334            ":global { .reset { color: red; } } :global(.standalone) { color: red; } .card :global(.child) { color: red; } :local(.button) { color: blue; }",
14335            StyleDialect::Css,
14336        );
14337        let outer_local = collect_style_facts(
14338            ":local { :global { .kept { color: green; } } }",
14339            StyleDialect::Css,
14340        );
14341        let class_names = facts
14342            .selectors
14343            .iter()
14344            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14345            .map(|selector| selector.name.as_str())
14346            .collect::<Vec<_>>();
14347        let outer_local_class_names = outer_local
14348            .selectors
14349            .iter()
14350            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14351            .map(|selector| selector.name.as_str())
14352            .collect::<Vec<_>>();
14353
14354        assert_eq!(class_names, vec!["card", "button"]);
14355        assert_eq!(outer_local_class_names, vec!["kept"]);
14356    }
14357
14358    #[test]
14359    fn extracts_css_module_local_id_selector_facts() {
14360        let facts = collect_style_facts(
14361            ":local(#panel) { color: red; } :global(#reset) { color: red; } .card :global(#child) { color: blue; }",
14362            StyleDialect::Css,
14363        );
14364        let class_names = facts
14365            .selectors
14366            .iter()
14367            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14368            .map(|selector| selector.name.as_str())
14369            .collect::<Vec<_>>();
14370        let id_names = facts
14371            .selectors
14372            .iter()
14373            .filter(|selector| selector.kind == ParsedSelectorFactKind::Id)
14374            .map(|selector| selector.name.as_str())
14375            .collect::<Vec<_>>();
14376
14377        assert_eq!(class_names, vec!["card"]);
14378        assert_eq!(id_names, vec!["panel"]);
14379    }
14380
14381    #[test]
14382    fn extracts_css_module_local_selector_list_facts() {
14383        let facts = collect_style_facts(
14384            ":local(.button, .link:hover) { color: red; } :global(.reset, .theme) { color: blue; }",
14385            StyleDialect::Css,
14386        );
14387        let class_names = facts
14388            .selectors
14389            .iter()
14390            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14391            .map(|selector| selector.name.as_str())
14392            .collect::<Vec<_>>();
14393
14394        assert_eq!(class_names, vec!["button", "link"]);
14395    }
14396
14397    #[test]
14398    fn keeps_trailing_local_selector_group_classes() {
14399        let facts = collect_style_facts(
14400            ":local(.button) .icon, :local(.card).active { color: red; }",
14401            StyleDialect::Css,
14402        );
14403        let mut class_names = facts
14404            .selectors
14405            .iter()
14406            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14407            .map(|selector| selector.name.as_str())
14408            .collect::<Vec<_>>();
14409        class_names.sort_unstable();
14410
14411        assert_eq!(class_names, vec!["active", "button", "card", "icon"]);
14412    }
14413
14414    #[test]
14415    fn parses_functional_pseudo_selector_lists_with_bogus_item_recovery() {
14416        let result = parse(
14417            ".btn:is(#it/typo, .ok):where(.wide, .compact) { color: red; }",
14418            StyleDialect::Css,
14419        );
14420        let kinds = node_kinds(&result.syntax());
14421        let selector_list_count = kinds
14422            .iter()
14423            .filter(|kind| **kind == SyntaxKind::SelectorList)
14424            .count();
14425        let class_selector_count = kinds
14426            .iter()
14427            .filter(|kind| **kind == SyntaxKind::ClassSelector)
14428            .count();
14429
14430        assert!(kinds.contains(&SyntaxKind::Rule));
14431        assert!(kinds.contains(&SyntaxKind::Declaration));
14432        assert!(kinds.contains(&SyntaxKind::PseudoSelectorArgument));
14433        assert!(kinds.contains(&SyntaxKind::BogusSelector));
14434        assert!(!kinds.contains(&SyntaxKind::BogusRule));
14435        assert!(selector_list_count >= 3);
14436        assert!(class_selector_count >= 4);
14437        assert!(
14438            result
14439                .errors()
14440                .iter()
14441                .any(|error| error.message == "invalid selector in selector list")
14442        );
14443    }
14444
14445    #[test]
14446    fn parses_not_arguments_as_strict_selector_lists() {
14447        let forgiving = parse(".btn:is(#it/typo, .ok) { color: red; }", StyleDialect::Css);
14448        let strict = parse(".btn:not(#it/typo, .ok) { color: red; }", StyleDialect::Css);
14449        let forgiving_kinds = node_kinds(&forgiving.syntax());
14450        let strict_kinds = node_kinds(&strict.syntax());
14451
14452        assert!(forgiving_kinds.contains(&SyntaxKind::BogusSelector));
14453        assert!(!forgiving_kinds.contains(&SyntaxKind::BogusSelectorList));
14454        assert!(strict_kinds.contains(&SyntaxKind::BogusSelector));
14455        assert!(strict_kinds.contains(&SyntaxKind::BogusSelectorList));
14456    }
14457
14458    #[test]
14459    fn parses_nth_child_of_selector_lists_as_cst_nodes() {
14460        let result = parse(
14461            ".grid > :nth-child(2n + 1 of .item, [data-active]) { color: red; }",
14462            StyleDialect::Css,
14463        );
14464        let kinds = node_kinds(&result.syntax());
14465        let selector_list_count = kinds
14466            .iter()
14467            .filter(|kind| **kind == SyntaxKind::SelectorList)
14468            .count();
14469
14470        assert!(result.errors().is_empty());
14471        assert!(kinds.contains(&SyntaxKind::NthSelectorArgument));
14472        assert!(kinds.contains(&SyntaxKind::NthSelectorFormula));
14473        assert!(kinds.contains(&SyntaxKind::NthSelectorOfSelectorList));
14474        assert!(kinds.contains(&SyntaxKind::ClassSelector));
14475        assert!(kinds.contains(&SyntaxKind::AttributeSelector));
14476        assert!(selector_list_count >= 2);
14477    }
14478
14479    #[test]
14480    fn parses_nth_of_type_arguments_as_formula_cst_nodes() {
14481        let result = parse("li:nth-of-type(2n + 1) { color: red; }", StyleDialect::Css);
14482        let kinds = node_kinds(&result.syntax());
14483
14484        assert!(result.errors().is_empty());
14485        assert!(kinds.contains(&SyntaxKind::NthSelectorArgument));
14486        assert!(kinds.contains(&SyntaxKind::NthSelectorFormula));
14487        assert!(!kinds.contains(&SyntaxKind::NthSelectorOfSelectorList));
14488    }
14489
14490    #[test]
14491    fn parses_has_arguments_as_relative_selector_lists() {
14492        let result = parse(
14493            ".card:has(> .icon, + [data-active], :has(~ .nested)) { color: red; }",
14494            StyleDialect::Css,
14495        );
14496        let kinds = node_kinds(&result.syntax());
14497        let relative_selector_count = kinds
14498            .iter()
14499            .filter(|kind| **kind == SyntaxKind::RelativeSelector)
14500            .count();
14501        let relative_list_count = kinds
14502            .iter()
14503            .filter(|kind| **kind == SyntaxKind::RelativeSelectorList)
14504            .count();
14505
14506        assert!(result.errors().is_empty());
14507        assert_eq!(relative_list_count, 2);
14508        assert_eq!(relative_selector_count, 4);
14509        assert!(kinds.contains(&SyntaxKind::Combinator));
14510        assert!(kinds.contains(&SyntaxKind::AttributeSelector));
14511        assert!(kinds.contains(&SyntaxKind::PseudoClassSelector));
14512    }
14513
14514    #[test]
14515    fn parses_lang_and_dir_arguments_as_cst_nodes() {
14516        let result = parse(
14517            ":lang(en-US, \"ko\") .card:dir(rtl) { color: red; }",
14518            StyleDialect::Css,
14519        );
14520        let kinds = node_kinds(&result.syntax());
14521        let language_tag_count = kinds
14522            .iter()
14523            .filter(|kind| **kind == SyntaxKind::LanguageTag)
14524            .count();
14525
14526        assert!(
14527            result.errors().is_empty(),
14528            "unexpected parse errors: {:?}",
14529            result.errors()
14530        );
14531        assert!(kinds.contains(&SyntaxKind::LanguageSelectorArgument));
14532        assert!(kinds.contains(&SyntaxKind::DirectionalitySelectorArgument));
14533        assert_eq!(language_tag_count, 2);
14534    }
14535
14536    #[test]
14537    fn decomposes_selector_lists_into_selector_nodes() {
14538        let result = parse(
14539            ".card:hover > #title, article.card || .icon[data-active] { color: red; }",
14540            StyleDialect::Css,
14541        );
14542        let kinds = node_kinds(&result.syntax());
14543
14544        assert!(result.errors().is_empty());
14545        assert!(kinds.contains(&SyntaxKind::Selector));
14546        assert!(kinds.contains(&SyntaxKind::ComplexSelector));
14547        assert!(kinds.contains(&SyntaxKind::CompoundSelector));
14548        assert!(kinds.contains(&SyntaxKind::ClassSelector));
14549        assert!(kinds.contains(&SyntaxKind::IdSelector));
14550        assert!(kinds.contains(&SyntaxKind::TypeSelector));
14551        assert!(kinds.contains(&SyntaxKind::PseudoClassSelector));
14552        assert!(kinds.contains(&SyntaxKind::AttributeSelector));
14553        assert!(kinds.contains(&SyntaxKind::Combinator));
14554    }
14555
14556    #[test]
14557    fn parses_namespace_qualified_selectors() {
14558        let result = parse(
14559            "@namespace svg url(\"http://www.w3.org/2000/svg\"); svg|a, *|button, |main, svg|*, *|* { color: red; }",
14560            StyleDialect::Css,
14561        );
14562        let kinds = node_kinds(&result.syntax());
14563        let namespace_prefix_count = kinds
14564            .iter()
14565            .filter(|kind| **kind == SyntaxKind::NamespacePrefix)
14566            .count();
14567        let type_selector_count = kinds
14568            .iter()
14569            .filter(|kind| **kind == SyntaxKind::TypeSelector)
14570            .count();
14571        let universal_selector_count = kinds
14572            .iter()
14573            .filter(|kind| **kind == SyntaxKind::UniversalSelector)
14574            .count();
14575
14576        assert!(result.errors().is_empty());
14577        assert_eq!(namespace_prefix_count, 5);
14578        assert_eq!(type_selector_count, 3);
14579        assert_eq!(universal_selector_count, 2);
14580    }
14581
14582    #[test]
14583    fn decomposes_attribute_matchers_into_cst_nodes() {
14584        let result = parse(
14585            ".a[data-state~=\"active\"][lang|=\"en\"][href^=\"/docs\"][href$=\".pdf\"][class*=\"btn\"][data-mode=\"x\" i] { color: red; }",
14586            StyleDialect::Css,
14587        );
14588        let kinds = node_kinds(&result.syntax());
14589        let matcher_count = kinds
14590            .iter()
14591            .filter(|kind| **kind == SyntaxKind::AttributeMatcher)
14592            .count();
14593        let name_count = kinds
14594            .iter()
14595            .filter(|kind| **kind == SyntaxKind::AttributeName)
14596            .count();
14597        let value_count = kinds
14598            .iter()
14599            .filter(|kind| **kind == SyntaxKind::AttributeValue)
14600            .count();
14601
14602        assert!(result.errors().is_empty());
14603        assert!(kinds.contains(&SyntaxKind::AttributeSelector));
14604        assert_eq!(matcher_count, 6);
14605        assert_eq!(name_count, 6);
14606        assert_eq!(value_count, 6);
14607        assert!(kinds.contains(&SyntaxKind::AttributeModifier));
14608    }
14609
14610    #[test]
14611    fn decomposes_css_module_scope_functions_into_cst_nodes() {
14612        let result = parse(
14613            ":local(.button) { color: red; } :global(.reset) { box-sizing: border-box; }",
14614            StyleDialect::Css,
14615        );
14616        let kinds = node_kinds(&result.syntax());
14617
14618        assert!(result.errors().is_empty());
14619        assert!(kinds.contains(&SyntaxKind::PseudoClassSelector));
14620        assert!(kinds.contains(&SyntaxKind::PseudoSelectorArgument));
14621        assert!(kinds.contains(&SyntaxKind::CssModuleLocalBlock));
14622        assert!(kinds.contains(&SyntaxKind::CssModuleGlobalBlock));
14623    }
14624
14625    #[test]
14626    fn decomposes_nested_and_pseudo_element_selectors() {
14627        let result = parse("&::before { content: \"\"; }", StyleDialect::Scss);
14628        let kinds = node_kinds(&result.syntax());
14629
14630        assert!(result.errors().is_empty());
14631        assert!(kinds.contains(&SyntaxKind::NestingSelectorNode));
14632        assert!(kinds.contains(&SyntaxKind::PseudoElementSelector));
14633    }
14634
14635    #[test]
14636    fn parses_sass_indented_blocks_as_rule_declaration_lists() {
14637        let result = parse(
14638            ".card\n  color: red\n  .title\n    color: blue\n",
14639            StyleDialect::Sass,
14640        );
14641        let kinds = node_kinds(&result.syntax());
14642
14643        assert!(result.errors().is_empty());
14644        assert!(kinds.contains(&SyntaxKind::SassIndentedBlock));
14645        assert!(kinds.contains(&SyntaxKind::Rule));
14646        assert!(kinds.contains(&SyntaxKind::DeclarationList));
14647        assert!(kinds.contains(&SyntaxKind::Declaration));
14648        assert!(kinds.contains(&SyntaxKind::ClassSelector));
14649    }
14650
14651    #[test]
14652    fn extracts_sass_indented_nested_bem_style_facts() {
14653        let facts = collect_style_facts(".card\n  &__icon\n    color: red\n", StyleDialect::Sass);
14654        let class_names: Vec<&str> = facts
14655            .selectors
14656            .iter()
14657            .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14658            .map(|selector| selector.name.as_str())
14659            .collect();
14660
14661        assert_eq!(class_names, vec!["card", "card__icon"]);
14662        assert_eq!(facts.error_count, 0);
14663    }
14664
14665    #[test]
14666    fn exposes_typed_cst_wrapper_slice() {
14667        let result = parse(
14668            ".card { color: red; --accent: blue; } @media (width >= 1px) { .button { color: var(--accent); } }",
14669            StyleDialect::Css,
14670        );
14671        let cst = result.cst();
14672        let stylesheet = cst.stylesheet();
14673        let rules = cst.rules();
14674        let selectors = cst.selectors();
14675        let declarations = cst.declarations();
14676        let values = cst.values();
14677        let component_values = parse_entry_point(
14678            "calc(1px + 2px)",
14679            StyleDialect::Css,
14680            ParseEntryPoint::ComponentValue,
14681        )
14682        .cst()
14683        .component_values();
14684        let simple_blocks = parse_entry_point(
14685            "{ color: red; (width >= 1px) }",
14686            StyleDialect::Css,
14687            ParseEntryPoint::SimpleBlock,
14688        )
14689        .cst()
14690        .simple_blocks();
14691        let component_value_lists = parse_entry_point(
14692            "red calc(1px + 2px)",
14693            StyleDialect::Css,
14694            ParseEntryPoint::ComponentValueList,
14695        )
14696        .cst()
14697        .component_value_lists();
14698        let comma_separated_component_value_lists = parse_entry_point(
14699            "red, calc(1px + 2px)",
14700            StyleDialect::Css,
14701            ParseEntryPoint::CommaSeparatedComponentValueList,
14702        )
14703        .cst()
14704        .comma_separated_component_value_lists();
14705        let custom_property_values = result.cst().custom_property_values();
14706        let at_rules = cst.at_rules();
14707
14708        assert_eq!(
14709            stylesheet.as_ref().map(TypedCstNode::kind),
14710            Some(SyntaxKind::Stylesheet)
14711        );
14712        assert_eq!(rules.len(), 2);
14713        assert_eq!(selectors.len(), 2);
14714        assert_eq!(declarations.len(), 3);
14715        assert_eq!(values.len(), 3);
14716        assert!(!component_values.is_empty());
14717        assert!(!simple_blocks.is_empty());
14718        assert!(!component_value_lists.is_empty());
14719        assert!(!comma_separated_component_value_lists.is_empty());
14720        assert_eq!(custom_property_values.len(), 1);
14721        assert!(!at_rules.is_empty());
14722        assert!(
14723            at_rules
14724                .iter()
14725                .any(|at_rule| at_rule.kind() == SyntaxKind::MediaRule)
14726        );
14727        assert!(
14728            stylesheet
14729                .and_then(|node| RuleCstNode::cast(node.into_syntax()))
14730                .is_none()
14731        );
14732    }
14733
14734    #[test]
14735    fn exposes_typed_bogus_cst_wrapper_slice() {
14736        let result = parse(".card { color: @; width: ?; }", StyleDialect::Css);
14737        let cst = result.cst();
14738        let bogus_kinds: Vec<SyntaxKind> =
14739            cst.bogus_nodes().iter().map(TypedCstNode::kind).collect();
14740
14741        assert!(cst.has_bogus_nodes());
14742        assert!(bogus_kinds.contains(&SyntaxKind::BogusValue));
14743        assert!(bogus_kinds.contains(&SyntaxKind::BogusToken));
14744        assert!(bogus_kinds.iter().all(|kind| kind.is_bogus()));
14745    }
14746
14747    #[test]
14748    fn consumes_parser_style_fact_names_through_typed_interner() {
14749        let db = salsa::DatabaseImpl::default();
14750        let summary = summarize_parser_semantic_name_consumption(
14751            r#"@use "./tokens" as t;
14752@mixin tone { color: $brand; }
14753.button { --brand: red; animation: fade 1s; composes: base from "./base.module.css"; }
14754@keyframes fade { from { opacity: 0; } to { opacity: 1; } }"#,
14755            StyleDialect::Scss,
14756            &db,
14757        );
14758
14759        assert_eq!(summary.product, "omena-parser.semantic-name-consumption");
14760        assert_eq!(summary.dialect, StyleDialect::Scss);
14761        assert_eq!(summary.invalid_name_count, 0);
14762        assert_eq!(summary.semantic_name_count, summary.interned_name_count);
14763        assert!(summary.class_name_count >= 2);
14764        assert!(summary.custom_property_name_count >= 1);
14765        assert!(summary.css_ident_count >= 1);
14766        assert!(summary.keyframes_name_count >= 1);
14767        assert!(summary.mixin_name_count >= 1);
14768        assert!(summary.file_path_count >= 1);
14769        assert!(
14770            summary
14771                .ready_surfaces
14772                .contains(&"parserSemanticNameConsumption")
14773        );
14774    }
14775
14776    #[test]
14777    fn summarizes_parser_cst_equivalence_contract() {
14778        let summary = summarize_parser_cst_equivalence(
14779            r#"@media (min-width: 1px) { .card { --tone: red; color: var(--tone); } }"#,
14780            StyleDialect::Css,
14781        );
14782
14783        assert_eq!(summary.product, "omena-parser.cst-equivalence");
14784        assert_eq!(summary.dialect, StyleDialect::Css);
14785        assert_eq!(summary.root_kind, SyntaxKind::Root);
14786        assert!(summary.parser_node_count > 1);
14787        assert!(summary.parser_token_count > 1);
14788        assert!(summary.typed_wrapper_count > 4);
14789        assert!(summary.source_text_round_trip_ready);
14790        assert!(summary.syntax_kind_round_trip_ready);
14791        assert!(summary.zero_unknown_kind_ready);
14792        assert!(summary.typed_cst_wrapper_ready);
14793        assert!(summary.ready_surfaces.contains(&"parserCstEquivalence"));
14794    }
14795
14796    #[test]
14797    fn summarizes_green_field_parser_boundary() {
14798        let summary = summarize_parser_boundary();
14799
14800        assert_eq!(summary.product, "omena-parser.boundary");
14801        assert_eq!(summary.dialect_count, 4);
14802        assert_eq!(summary.shared_name_kind_count, 8);
14803        assert!(summary.ready_surfaces.contains(&"selectorCstSkeleton"));
14804        assert!(summary.ready_surfaces.contains(&"lexedTokenTextSurface"));
14805        assert!(
14806            summary
14807                .ready_surfaces
14808                .contains(&"recursiveDescentParserCore")
14809        );
14810        assert!(
14811            summary
14812                .ready_surfaces
14813                .contains(&"recursiveDescentCoverageSummary")
14814        );
14815        assert!(summary.ready_surfaces.contains(&"atRuleRegistrySkeleton"));
14816        assert!(
14817            summary
14818                .ready_surfaces
14819                .contains(&"prattValueExpressionSkeleton")
14820        );
14821        assert!(summary.ready_surfaces.contains(&"prattValueParserCore"));
14822        assert!(
14823            summary
14824                .ready_surfaces
14825                .contains(&"prattValueCoverageSummary")
14826        );
14827        assert!(
14828            summary
14829                .ready_surfaces
14830                .contains(&"attributeMatcherTokenization")
14831        );
14832        assert!(summary.ready_surfaces.contains(&"attributeMatcherCstNodes"));
14833        assert!(
14834            summary
14835                .ready_surfaces
14836                .contains(&"attributeNameValueModifierCstNodes")
14837        );
14838        assert!(
14839            summary
14840                .ready_surfaces
14841                .contains(&"specializedValueFunctionCstNodes")
14842        );
14843        assert!(
14844            summary
14845                .ready_surfaces
14846                .contains(&"caseInsensitiveFunctionRegistry")
14847        );
14848        assert!(
14849            summary
14850                .ready_surfaces
14851                .contains(&"caseInsensitiveAtRuleRegistry")
14852        );
14853        assert!(summary.ready_surfaces.contains(&"identifierValueCstNodes"));
14854        assert!(summary.ready_surfaces.contains(&"stringValueCstNodes"));
14855        assert!(
14856            summary
14857                .ready_surfaces
14858                .contains(&"unicodeRangeValueCstNodes")
14859        );
14860        assert!(
14861            summary
14862                .ready_surfaces
14863                .contains(&"cssModuleScopeFunctionCstNodes")
14864        );
14865        assert!(
14866            summary
14867                .ready_surfaces
14868                .contains(&"cssModuleGlobalSelectorFactFiltering")
14869        );
14870        assert!(
14871            summary
14872                .ready_surfaces
14873                .contains(&"cssModuleLocalIdSelectorFacts")
14874        );
14875        assert!(summary.ready_surfaces.contains(&"cssModuleValueStyleFacts"));
14876        assert!(
14877            summary
14878                .ready_surfaces
14879                .contains(&"cssModuleValueDeclarationReferenceFacts")
14880        );
14881        assert!(
14882            summary
14883                .ready_surfaces
14884                .contains(&"cssModuleComposesStyleFacts")
14885        );
14886        assert!(summary.ready_surfaces.contains(&"icssStyleFacts"));
14887        assert!(summary.ready_surfaces.contains(&"animationNameStyleFacts"));
14888        assert!(
14889            summary
14890                .ready_surfaces
14891                .contains(&"animationShorthandStyleFacts")
14892        );
14893        assert!(
14894            summary
14895                .ready_surfaces
14896                .contains(&"scssStructuredBlockAtRules")
14897        );
14898        assert!(
14899            summary
14900                .ready_surfaces
14901                .contains(&"scssControlPreludeValidation")
14902        );
14903        assert!(
14904            summary
14905                .ready_surfaces
14906                .contains(&"scssControlStyleFactExtraction")
14907        );
14908        assert!(
14909            summary
14910                .ready_surfaces
14911                .contains(&"scssIncludeContentBlockStyleFacts")
14912        );
14913        assert!(summary.ready_surfaces.contains(&"scssUtilityAtRules"));
14914        assert!(summary.ready_surfaces.contains(&"scssVariableFlagCstNodes"));
14915        assert!(
14916            summary
14917                .ready_surfaces
14918                .contains(&"scssModulePreludeSourceValidation")
14919        );
14920        assert!(
14921            summary
14922                .ready_surfaces
14923                .contains(&"scssModulePreludeClauseValidation")
14924        );
14925        assert!(
14926            summary
14927                .ready_surfaces
14928                .contains(&"lessMixinDeclarationCstNodes")
14929        );
14930        assert!(summary.ready_surfaces.contains(&"lessMixinCallCstNodes"));
14931        assert!(summary.ready_surfaces.contains(&"lessMixinGuardCstNodes"));
14932        assert!(summary.ready_surfaces.contains(&"lessExtendPseudoCstNodes"));
14933        assert!(
14934            summary
14935                .ready_surfaces
14936                .contains(&"lessDetachedRulesetCstNodes")
14937        );
14938        assert!(
14939            summary
14940                .ready_surfaces
14941                .contains(&"lessNamespaceAccessCstNodes")
14942        );
14943        assert!(
14944            summary
14945                .ready_surfaces
14946                .contains(&"lessPropertyVariableTokenization")
14947        );
14948        assert!(
14949            summary
14950                .ready_surfaces
14951                .contains(&"lessPropertyVariableCstNodes")
14952        );
14953        assert!(
14954            summary
14955                .ready_surfaces
14956                .contains(&"lessEscapedStringTokenization")
14957        );
14958        assert!(
14959            summary
14960                .ready_surfaces
14961                .contains(&"lessEscapedStringValueCstNodes")
14962        );
14963        assert!(
14964            summary
14965                .ready_surfaces
14966                .contains(&"importantAnnotationTokenization")
14967        );
14968        assert!(summary.ready_surfaces.contains(&"urlTokenization"));
14969        assert!(summary.ready_surfaces.contains(&"urlValueCstNodes"));
14970        assert!(
14971            summary
14972                .ready_surfaces
14973                .contains(&"quotedUrlFunctionValueCstNodes")
14974        );
14975        assert!(
14976            summary
14977                .ready_surfaces
14978                .contains(&"conditionalAtRulePreludeCstNodes")
14979        );
14980        assert!(
14981            summary
14982                .ready_surfaces
14983                .contains(&"supportsAtRulePreludeValidation")
14984        );
14985        assert!(
14986            summary
14987                .ready_surfaces
14988                .contains(&"conditionalLevel5AtRuleCstNodes")
14989        );
14990        assert!(summary.ready_surfaces.contains(&"mediaQueryCstNodes"));
14991        assert!(summary.ready_surfaces.contains(&"mediaQueryListValidation"));
14992        assert!(summary.ready_surfaces.contains(&"importPreludeCstNodes"));
14993        assert!(
14994            summary
14995                .ready_surfaces
14996                .contains(&"importSourcePreludeValidation")
14997        );
14998        assert!(
14999            summary
15000                .ready_surfaces
15001                .contains(&"importTailPreludeValidation")
15002        );
15003        assert!(
15004            summary
15005                .ready_surfaces
15006                .contains(&"customMediaPreludeValidation")
15007        );
15008        assert!(
15009            summary
15010                .ready_surfaces
15011                .contains(&"propertyAtRuleNameValidation")
15012        );
15013        assert!(
15014            summary
15015                .ready_surfaces
15016                .contains(&"namedAtRulePreludeValidation")
15017        );
15018        assert!(
15019            summary
15020                .ready_surfaces
15021                .contains(&"containerAtRulePreludeValidation")
15022        );
15023        assert!(
15024            summary
15025                .ready_surfaces
15026                .contains(&"charsetNamespaceAtRulePreludeValidation")
15027        );
15028        assert!(
15029            summary
15030                .ready_surfaces
15031                .contains(&"keyframesAtRuleNameValidation")
15032        );
15033        assert!(
15034            summary
15035                .ready_surfaces
15036                .contains(&"emptyBlockAtRulePreludeValidation")
15037        );
15038        assert!(
15039            summary
15040                .ready_surfaces
15041                .contains(&"layerScopePreludeCstNodes")
15042        );
15043        assert!(
15044            summary
15045                .ready_surfaces
15046                .contains(&"layerAtRulePreludeValidation")
15047        );
15048        assert!(
15049            summary
15050                .ready_surfaces
15051                .contains(&"scopeAtRulePreludeValidation")
15052        );
15053        assert!(
15054            summary
15055                .ready_surfaces
15056                .contains(&"pageAtRulePreludeValidation")
15057        );
15058        assert!(summary.ready_surfaces.contains(&"pageMarginAtRuleCstNodes"));
15059        assert!(
15060            summary
15061                .ready_surfaces
15062                .contains(&"modernDeclarationAtRuleCstNodes")
15063        );
15064        assert!(
15065            summary
15066                .ready_surfaces
15067                .contains(&"fontFeatureValuesAtRuleCstNodes")
15068        );
15069        assert!(
15070            summary
15071                .ready_surfaces
15072                .contains(&"fontFeatureValuesPreludeValidation")
15073        );
15074        assert!(
15075            summary
15076                .ready_surfaces
15077                .contains(&"keyframeSelectorListValidation")
15078        );
15079        assert!(
15080            summary
15081                .ready_surfaces
15082                .contains(&"viewTransitionAtRuleCstNodes")
15083        );
15084        assert!(
15085            summary
15086                .ready_surfaces
15087                .contains(&"genericAtRulePreludeCstNodes")
15088        );
15089        assert!(
15090            summary
15091                .ready_surfaces
15092                .contains(&"bogusAtRulePreludeCstNodes")
15093        );
15094        assert!(summary.ready_surfaces.contains(&"nestingAtRuleCstNodes"));
15095        assert!(
15096            summary
15097                .ready_surfaces
15098                .contains(&"customMediaAtRuleCstNodes")
15099        );
15100        assert!(summary.ready_surfaces.contains(&"cssColorFunctionCstNodes"));
15101        assert!(
15102            summary
15103                .ready_surfaces
15104                .contains(&"colorFunctionArgumentChecks")
15105        );
15106        assert!(summary.ready_surfaces.contains(&"gradientFunctionCstNodes"));
15107        assert!(
15108            summary
15109                .ready_surfaces
15110                .contains(&"transformFunctionCstNodes")
15111        );
15112        assert!(summary.ready_surfaces.contains(&"filterFunctionCstNodes"));
15113        assert!(summary.ready_surfaces.contains(&"imageFunctionCstNodes"));
15114        assert!(summary.ready_surfaces.contains(&"shapeFunctionCstNodes"));
15115        assert!(summary.ready_surfaces.contains(&"envAttrFunctionCstNodes"));
15116        assert!(summary.ready_surfaces.contains(&"mathFunctionCstNodes"));
15117        assert!(summary.ready_surfaces.contains(&"mathFunctionArityChecks"));
15118        assert!(
15119            summary
15120                .ready_surfaces
15121                .contains(&"mathFunctionEmptyArgumentChecks")
15122        );
15123        assert!(
15124            summary
15125                .ready_surfaces
15126                .contains(&"varEnvAttrFunctionHeadChecks")
15127        );
15128        assert!(
15129            summary
15130                .ready_surfaces
15131                .contains(&"scssInterpolationTokenization")
15132        );
15133        assert!(
15134            summary
15135                .ready_surfaces
15136                .contains(&"scssInterpolationCstNodes")
15137        );
15138        assert!(
15139            summary
15140                .ready_surfaces
15141                .contains(&"lessInterpolationTokenization")
15142        );
15143        assert!(
15144            summary
15145                .ready_surfaces
15146                .contains(&"lessInterpolationCstNodes")
15147        );
15148        assert!(
15149            summary
15150                .ready_surfaces
15151                .contains(&"interpolationBogusRecovery")
15152        );
15153        assert!(summary.ready_surfaces.contains(&"unicodeRangeTokenization"));
15154        assert!(summary.ready_surfaces.contains(&"badStringTokenRecovery"));
15155        assert!(summary.ready_surfaces.contains(&"badStringValueBogusNodes"));
15156        assert!(
15157            summary
15158                .ready_surfaces
15159                .contains(&"emptyDeclarationValueRecovery")
15160        );
15161        assert!(
15162            summary
15163                .ready_surfaces
15164                .contains(&"emptyVariableValueRecovery")
15165        );
15166        assert!(
15167            summary
15168                .ready_surfaces
15169                .contains(&"missingSemicolonDeclarationRecovery")
15170        );
15171        assert!(summary.ready_surfaces.contains(&"coreBogusPopulationSlice"));
15172        assert!(
15173            summary
15174                .ready_surfaces
15175                .contains(&"dialectBogusPopulationSlice")
15176        );
15177        assert!(summary.ready_surfaces.contains(&"cssModuleValueCstNodes"));
15178        assert!(
15179            summary
15180                .ready_surfaces
15181                .contains(&"cssModuleComposesCstNodes")
15182        );
15183        assert!(summary.ready_surfaces.contains(&"icssModuleBlockCstNodes"));
15184        assert!(
15185            summary
15186                .ready_surfaces
15187                .contains(&"icssImportSourceValidation")
15188        );
15189        assert!(
15190            summary
15191                .ready_surfaces
15192                .contains(&"cssModuleFromClauseSourceValidation")
15193        );
15194        assert!(
15195            summary
15196                .ready_surfaces
15197                .contains(&"cssModuleComposesMultipleFromValidation")
15198        );
15199        assert!(
15200            summary
15201                .ready_surfaces
15202                .contains(&"cssModuleGlobalComposesValidation")
15203        );
15204        assert!(summary.ready_surfaces.contains(&"cssModuleBogusRecovery"));
15205        assert!(summary.ready_surfaces.contains(&"valueListCstNodes"));
15206        assert!(summary.ready_surfaces.contains(&"valueListBogusRecovery"));
15207        assert!(
15208            summary
15209                .ready_surfaces
15210                .contains(&"genericRecoveryBogusNodes")
15211        );
15212        assert!(
15213            summary
15214                .ready_surfaces
15215                .contains(&"lightningCssDifferentialCorpusSlice")
15216        );
15217        assert!(
15218            summary
15219                .ready_surfaces
15220                .contains(&"midTypingNoPanicPropertySlice")
15221        );
15222        assert!(
15223            summary
15224                .ready_surfaces
15225                .contains(&"deterministicPanicFreeCorpus")
15226        );
15227        assert!(
15228            summary
15229                .ready_surfaces
15230                .contains(&"losslessCstTextRoundTripSmoke")
15231        );
15232        assert!(
15233            summary
15234                .ready_surfaces
15235                .contains(&"parseResultSourceTextSurface")
15236        );
15237        assert!(
15238            summary
15239                .ready_surfaces
15240                .contains(&"parseSourceParseRoundTripSmoke")
15241        );
15242        assert!(
15243            summary
15244                .ready_surfaces
15245                .contains(&"typedNumericValueAtomCstNodes")
15246        );
15247        assert!(summary.ready_surfaces.contains(&"bracketedValueCstNodes"));
15248        assert!(
15249            summary
15250                .ready_surfaces
15251                .contains(&"importantAnnotationCstNodes")
15252        );
15253        assert!(
15254            summary
15255                .ready_surfaces
15256                .contains(&"splitImportantAnnotationCstNodes")
15257        );
15258        assert!(
15259            summary
15260                .ready_surfaces
15261                .contains(&"unexpectedValueTokenBogusNodes")
15262        );
15263        assert!(summary.ready_surfaces.contains(&"cdoCdcTokenization"));
15264        assert!(
15265            summary
15266                .ready_surfaces
15267                .contains(&"cssIdentifierEscapeTokenization")
15268        );
15269        assert!(
15270            summary
15271                .ready_surfaces
15272                .contains(&"nullAndBomInputPreprocessingSlice")
15273        );
15274        assert!(
15275            summary
15276                .ready_surfaces
15277                .contains(&"hashDelimiterTokenization")
15278        );
15279        assert!(summary.ready_surfaces.contains(&"cssDashIdentTokenization"));
15280        assert!(
15281            summary
15282                .ready_surfaces
15283                .contains(&"signedNumericTokenization")
15284        );
15285        assert!(
15286            summary
15287                .ready_surfaces
15288                .contains(&"exponentNumericTokenization")
15289        );
15290        assert!(summary.ready_surfaces.contains(&"badUrlWhitespaceRecovery"));
15291        assert!(summary.ready_surfaces.contains(&"parserEntryPointApiSlice"));
15292        assert!(
15293            summary
15294                .ready_surfaces
15295                .contains(&"ruleListEntryPointApiSlice")
15296        );
15297        assert!(
15298            summary
15299                .ready_surfaces
15300                .contains(&"componentValueEntryPointApiSlice")
15301        );
15302        assert!(
15303            summary
15304                .ready_surfaces
15305                .contains(&"componentValueListEntryPointApiSlice")
15306        );
15307        assert!(
15308            summary
15309                .ready_surfaces
15310                .contains(&"commaSeparatedComponentValueListEntryPointApiSlice")
15311        );
15312        assert!(
15313            summary
15314                .ready_surfaces
15315                .contains(&"simpleBlockEntryPointApiSlice")
15316        );
15317        assert!(summary.ready_surfaces.contains(&"typedCstWrapperSlice"));
15318        assert!(summary.ready_surfaces.contains(&"parserCstEquivalence"));
15319        assert!(
15320            summary
15321                .ready_surfaces
15322                .contains(&"typedBogusCstWrapperSlice")
15323        );
15324        assert!(summary.ready_surfaces.contains(&"componentValueCstNodes"));
15325        assert!(summary.ready_surfaces.contains(&"simpleBlockCstNodes"));
15326        assert!(summary.ready_surfaces.contains(&"fullBogusPopulation"));
15327        assert!(
15328            summary
15329                .ready_surfaces
15330                .contains(&"componentValueListCstNodes")
15331        );
15332        assert!(
15333            summary
15334                .ready_surfaces
15335                .contains(&"commaSeparatedComponentValueListCstNodes")
15336        );
15337        assert!(
15338            summary
15339                .ready_surfaces
15340                .contains(&"customPropertyAnyValueComponentList")
15341        );
15342        assert!(
15343            summary
15344                .ready_surfaces
15345                .contains(&"customPropertyValueCstNodes")
15346        );
15347        assert!(
15348            summary
15349                .ready_surfaces
15350                .contains(&"functionalPseudoSelectorListCstNodes")
15351        );
15352        assert!(
15353            summary
15354                .ready_surfaces
15355                .contains(&"strictNotPseudoSelectorListCstNodes")
15356        );
15357        assert!(
15358            summary
15359                .ready_surfaces
15360                .contains(&"nthSelectorOfSelectorListCstNodes")
15361        );
15362        assert!(
15363            summary
15364                .ready_surfaces
15365                .contains(&"nthSelectorFormulaCstNodes")
15366        );
15367        assert!(
15368            summary
15369                .ready_surfaces
15370                .contains(&"hasRelativeSelectorListCstNodes")
15371        );
15372        assert!(
15373            summary
15374                .ready_surfaces
15375                .contains(&"langDirSelectorArgumentCstNodes")
15376        );
15377        assert!(
15378            summary
15379                .ready_surfaces
15380                .contains(&"namespaceQualifiedSelectorCstNodes")
15381        );
15382        assert!(
15383            summary
15384                .ready_surfaces
15385                .contains(&"selectorFunctionArgumentFactExclusion")
15386        );
15387        assert!(
15388            summary
15389                .ready_surfaces
15390                .contains(&"missingBlockCloseBogusTrivia")
15391        );
15392        assert!(
15393            summary
15394                .ready_surfaces
15395                .contains(&"initialDialectStatementNodes")
15396        );
15397        assert!(
15398            summary
15399                .ready_surfaces
15400                .contains(&"scssNestedPropertyCstNodes")
15401        );
15402        assert!(summary.ready_surfaces.contains(&"scssModuleConfigCstNodes"));
15403        assert!(
15404            summary
15405                .ready_surfaces
15406                .contains(&"scssModuleConfigBogusRecovery")
15407        );
15408        assert!(
15409            summary
15410                .ready_surfaces
15411                .contains(&"scssPlaceholderSelectorCstNodes")
15412        );
15413        assert!(summary.ready_surfaces.contains(&"recoveryBogusSkeleton"));
15414        assert!(
15415            summary
15416                .ready_surfaces
15417                .contains(&"styleFactExtractionSurface")
15418        );
15419        assert!(
15420            summary
15421                .ready_surfaces
15422                .contains(&"parserSemanticNameConsumption")
15423        );
15424        assert!(summary.ready_surfaces.contains(&"differentialCorpus"));
15425        assert!(!summary.not_ready_surfaces.contains(&"differentialCorpus"));
15426        assert!(
15427            summary
15428                .ready_surfaces
15429                .contains(&"lightningCssSelectorIdAndAtRuleDifferentialSlice")
15430        );
15431        assert!(!summary.not_ready_surfaces.contains(&"fullPrattValueParser"));
15432        assert!(
15433            summary
15434                .not_ready_surfaces
15435                .contains(&"fullPropertyValueGrammarRegistry")
15436        );
15437        assert!(
15438            !summary
15439                .not_ready_surfaces
15440                .contains(&"fullRecursiveDescentGrammar")
15441        );
15442        assert!(
15443            summary
15444                .not_ready_surfaces
15445                .contains(&"completeExternalSpecMirror")
15446        );
15447        assert!(summary.not_ready_surfaces.contains(&"productCutover"));
15448    }
15449
15450    #[test]
15451    fn summarizes_recursive_descent_parser_coverage_without_claiming_full_spec_mirror() {
15452        let summary = summarize_recursive_descent_parser_coverage();
15453
15454        assert_eq!(summary.product, "omena-parser.recursive-descent-coverage");
15455        assert_eq!(summary.dialect_count, 4);
15456        assert_eq!(summary.entry_point_count, 10);
15457        assert!(summary.selector_surface_count >= 12);
15458        assert!(summary.at_rule_surface_count >= 19);
15459        assert!(summary.dialect_extension_surface_count >= 17);
15460        assert!(summary.recovery_surface_count >= 8);
15461        assert!(
15462            summary
15463                .ready_surfaces
15464                .contains(&"recursiveDescentParserCore")
15465        );
15466        assert!(summary.ready_surfaces.contains(&"sassIndentedBlocks"));
15467        assert!(
15468            summary
15469                .next_surfaces
15470                .contains(&"completeExternalSpecMirror")
15471        );
15472    }
15473
15474    #[test]
15475    fn summarizes_pratt_value_parser_coverage_without_overclaiming_property_grammar() {
15476        let summary = summarize_pratt_value_parser_coverage();
15477
15478        assert_eq!(summary.product, "omena-parser.pratt-value-coverage");
15479        assert!(summary.infix_operator_kinds.contains(&SyntaxKind::Plus));
15480        assert!(summary.infix_operator_kinds.contains(&SyntaxKind::Star));
15481        assert!(summary.prefix_operator_kinds.contains(&SyntaxKind::Minus));
15482        assert!(
15483            summary
15484                .value_expression_node_kinds
15485                .contains(&SyntaxKind::BinaryExpression)
15486        );
15487        assert!(
15488            summary
15489                .value_expression_node_kinds
15490                .contains(&SyntaxKind::FunctionArguments)
15491        );
15492        assert!(summary.specialized_function_family_count >= 10);
15493        assert!(summary.css_values_l4_math_function_count >= 20);
15494        assert!(summary.css_color_function_count >= 14);
15495        assert!(summary.ready_surfaces.contains(&"prattValueParserCore"));
15496        assert!(
15497            summary
15498                .next_surfaces
15499                .contains(&"fullPropertyValueGrammarRegistry")
15500        );
15501    }
15502
15503    fn char_boundary_offsets(source: &str) -> Vec<usize> {
15504        source
15505            .char_indices()
15506            .map(|(offset, _)| offset)
15507            .chain(std::iter::once(source.len()))
15508            .collect()
15509    }
15510
15511    fn deterministic_byte_fixture(seed: u32) -> Vec<u8> {
15512        let mut state = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
15513        let len = (state as usize % 96) + 1;
15514        let mut bytes = Vec::with_capacity(len);
15515        for _ in 0..len {
15516            state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
15517            bytes.push((state >> 24) as u8);
15518        }
15519        bytes
15520    }
15521
15522    fn assert_lex_ranges_are_char_boundaries(source: &str, tokens: &[LexedToken]) {
15523        for token in tokens {
15524            let start = u32::from(token.range.start()) as usize;
15525            let end = u32::from(token.range.end()) as usize;
15526            assert!(
15527                source.is_char_boundary(start),
15528                "token start is not a char boundary: token={token:?} source={source:?}"
15529            );
15530            assert!(
15531                source.is_char_boundary(end),
15532                "token end is not a char boundary: token={token:?} source={source:?}"
15533            );
15534        }
15535    }
15536
15537    fn source_text(node: &SyntaxNode<SyntaxKind>) -> Option<String> {
15538        let mut text = String::new();
15539        for token in node
15540            .descendants_with_tokens()
15541            .filter_map(|element| element.into_token())
15542        {
15543            if let Some(resolver) = token.resolver() {
15544                text.push_str(token.resolve_text(&**resolver));
15545            } else if let Some(static_text) = token.static_text() {
15546                text.push_str(static_text);
15547            } else {
15548                return None;
15549            }
15550        }
15551        Some(text)
15552    }
15553
15554    fn node_kinds(node: &SyntaxNode<SyntaxKind>) -> Vec<SyntaxKind> {
15555        let mut kinds = vec![node.kind()];
15556        for child in node.children() {
15557            kinds.extend(node_kinds(child));
15558        }
15559        kinds
15560    }
15561
15562    fn token_kinds(node: &SyntaxNode<SyntaxKind>) -> Vec<SyntaxKind> {
15563        node.descendants_with_tokens()
15564            .filter_map(|element| element.into_token().map(|token| token.kind()))
15565            .collect()
15566    }
15567}