Skip to main content

omena_parser/
lib.rs

1//! Green-field parser substrate for omena-css.
2//!
3//! This crate owns the cstree parser track and publishes parser facts for the
4//! product query, bridge, LSP, and transform consumers.
5
6use cstree::{
7    Syntax,
8    build::GreenNodeBuilder,
9    green::GreenNode,
10    interning::TokenInterner,
11    syntax::SyntaxNode,
12    text::{TextRange, TextSize},
13};
14use omena_interner::{
15    NameKind, intern_class_name, intern_css_ident, intern_custom_property_name, intern_file_path,
16    intern_keyframes_name, intern_mixin_name, intern_property_name, intern_selector_key,
17};
18pub use omena_syntax::StyleDialect;
19use omena_syntax::SyntaxKind;
20use serde::{Deserialize, Serialize};
21use std::{
22    collections::{BTreeMap, BTreeSet},
23    sync::Arc,
24};
25
26mod public_product;
27pub use public_product::{
28    ParserCanonicalCandidateBundleV0, ParserCanonicalProducerSignalV0, ParserEvaluatorCandidatesV0,
29    ParserIndexSummaryV0, dialect_for_path, summarize_css_modules_intermediate,
30    summarize_parser_canonical_candidate, summarize_parser_canonical_producer_signal,
31    summarize_parser_evaluator_candidates,
32};
33
34const VALUES_L4_MATH_FUNCTION_NAMES: &[&str] = &[
35    "min", "max", "clamp", "round", "mod", "rem", "sin", "cos", "tan", "asin", "acos", "atan",
36    "atan2", "pow", "sqrt", "hypot", "log", "exp", "abs", "sign",
37];
38
39const CSS_COLOR_FUNCTION_NAMES: &[&str] = &[
40    "rgb",
41    "rgba",
42    "hsl",
43    "hsla",
44    "hwb",
45    "lab",
46    "lch",
47    "oklab",
48    "oklch",
49    "color",
50    "color-mix",
51    "device-cmyk",
52    "light-dark",
53    "contrast-color",
54];
55
56const CSS_GRADIENT_FUNCTION_NAMES: &[&str] = &[
57    "linear-gradient",
58    "radial-gradient",
59    "conic-gradient",
60    "repeating-linear-gradient",
61    "repeating-radial-gradient",
62    "repeating-conic-gradient",
63];
64
65const CSS_TRANSFORM_FUNCTION_NAMES: &[&str] = &[
66    "matrix",
67    "matrix3d",
68    "translate",
69    "translate3d",
70    "translateX",
71    "translateY",
72    "translateZ",
73    "scale",
74    "scale3d",
75    "scaleX",
76    "scaleY",
77    "scaleZ",
78    "rotate",
79    "rotate3d",
80    "rotateX",
81    "rotateY",
82    "rotateZ",
83    "skew",
84    "skewX",
85    "skewY",
86    "perspective",
87];
88
89const CSS_FILTER_FUNCTION_NAMES: &[&str] = &[
90    "blur",
91    "brightness",
92    "contrast",
93    "drop-shadow",
94    "grayscale",
95    "hue-rotate",
96    "invert",
97    "opacity",
98    "saturate",
99    "sepia",
100];
101
102const CSS_IMAGE_FUNCTION_NAMES: &[&str] = &["image", "image-set", "cross-fade", "element", "paint"];
103
104const CSS_SHAPE_FUNCTION_NAMES: &[&str] = &[
105    "path", "shape", "ray", "inset", "circle", "ellipse", "polygon",
106];
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
109#[serde(rename_all = "camelCase")]
110pub struct ParserByteSpanV0 {
111    pub start: usize,
112    pub end: usize,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Default)]
116#[serde(rename_all = "camelCase")]
117pub struct ParserPositionV0 {
118    pub line: usize,
119    pub character: usize,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Default)]
123#[serde(rename_all = "camelCase")]
124pub struct ParserRangeV0 {
125    pub start: ParserPositionV0,
126    pub end: ParserPositionV0,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum StyleLanguage {
131    Css,
132    Scss,
133    Less,
134}
135
136impl StyleLanguage {
137    pub fn from_module_path(path: &str) -> Option<Self> {
138        if path.ends_with(".module.css") || path.ends_with(".css") {
139            Some(Self::Css)
140        } else if path.ends_with(".module.scss") || path.ends_with(".scss") {
141            Some(Self::Scss)
142        } else if path.ends_with(".module.less") || path.ends_with(".less") {
143            Some(Self::Less)
144        } else {
145            None
146        }
147    }
148}
149
150#[derive(Debug, Clone)]
151pub struct ParseResult {
152    green: GreenNode,
153    interner: Option<Arc<TokenInterner>>,
154    errors: Vec<ParseError>,
155    token_count: usize,
156    dialect: StyleDialect,
157}
158
159impl PartialEq for ParseResult {
160    fn eq(&self, other: &Self) -> bool {
161        self.green == other.green
162            && self.errors == other.errors
163            && self.token_count == other.token_count
164            && self.dialect == other.dialect
165    }
166}
167
168impl Eq for ParseResult {}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct LexResult {
172    tokens: Vec<LexedToken>,
173    errors: Vec<ParseError>,
174    dialect: StyleDialect,
175}
176
177impl LexResult {
178    pub fn tokens(&self) -> &[LexedToken] {
179        &self.tokens
180    }
181
182    pub fn errors(&self) -> &[ParseError] {
183        &self.errors
184    }
185
186    pub fn dialect(&self) -> StyleDialect {
187        self.dialect
188    }
189}
190
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct LexedToken {
193    pub kind: SyntaxKind,
194    pub range: TextRange,
195    pub text: String,
196}
197
198impl ParseResult {
199    pub fn green(&self) -> &GreenNode {
200        &self.green
201    }
202
203    pub fn syntax(&self) -> SyntaxNode<SyntaxKind> {
204        if let Some(interner) = &self.interner {
205            return SyntaxNode::new_root_with_resolver(self.green.clone(), Arc::clone(interner))
206                .syntax()
207                .clone();
208        }
209        SyntaxNode::new_root(self.green.clone())
210    }
211
212    pub fn source_text(&self) -> Option<String> {
213        let syntax = self.syntax();
214        syntax
215            .try_resolved()
216            .map(|resolved| resolved.text().to_string())
217    }
218
219    pub fn errors(&self) -> &[ParseError] {
220        &self.errors
221    }
222
223    pub fn token_count(&self) -> usize {
224        self.token_count
225    }
226
227    pub fn dialect(&self) -> StyleDialect {
228        self.dialect
229    }
230
231    pub fn cst(&self) -> ParsedCst {
232        ParsedCst::new(self.syntax())
233    }
234}
235
236#[derive(Debug, Clone, PartialEq, Eq)]
237pub struct ParseError {
238    pub code: ParseErrorCode,
239    pub range: TextRange,
240    pub message: &'static str,
241}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub enum ParseErrorCode {
245    UnterminatedBlockComment,
246    UnterminatedString,
247    UnexpectedCharacter,
248    ExpectedSelectorName,
249    UnterminatedAttributeSelector,
250    ExpectedValue,
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254pub enum ParseEntryPoint {
255    Stylesheet,
256    RuleList,
257    Rule,
258    DeclarationList,
259    Declaration,
260    Value,
261    ComponentValue,
262    ComponentValueList,
263    CommaSeparatedComponentValueList,
264    SimpleBlock,
265}
266
267#[derive(Debug, Clone, PartialEq, Eq)]
268pub struct ParserBoundarySummary {
269    pub product: &'static str,
270    pub tree_model: &'static str,
271    pub parser_track: &'static str,
272    pub dialect_count: usize,
273    pub shared_name_kind_count: usize,
274    pub ready_surfaces: Vec<&'static str>,
275    pub not_ready_surfaces: Vec<&'static str>,
276}
277
278#[derive(Debug, Clone, PartialEq, Eq)]
279pub struct ParserSemanticNameConsumptionSummaryV0 {
280    pub product: &'static str,
281    pub dialect: StyleDialect,
282    pub semantic_name_count: usize,
283    pub interned_name_count: usize,
284    pub invalid_name_count: usize,
285    pub class_name_count: usize,
286    pub css_ident_count: usize,
287    pub property_name_count: usize,
288    pub selector_key_count: usize,
289    pub custom_property_name_count: usize,
290    pub keyframes_name_count: usize,
291    pub mixin_name_count: usize,
292    pub file_path_count: usize,
293    pub ready_surfaces: Vec<&'static str>,
294}
295
296#[derive(Debug, Clone, PartialEq, Eq)]
297pub struct ParserCstEquivalenceSummaryV0 {
298    pub product: &'static str,
299    pub dialect: StyleDialect,
300    pub root_kind: SyntaxKind,
301    pub parser_node_count: usize,
302    pub parser_token_count: usize,
303    pub typed_wrapper_count: usize,
304    pub source_text_round_trip_ready: bool,
305    pub syntax_kind_round_trip_ready: bool,
306    pub zero_unknown_kind_ready: bool,
307    pub typed_cst_wrapper_ready: bool,
308    pub ready_surfaces: Vec<&'static str>,
309}
310
311#[derive(Debug, Clone, PartialEq, Eq)]
312pub struct ParserPrattValueCoverageSummaryV0 {
313    pub product: &'static str,
314    pub infix_operator_kinds: Vec<SyntaxKind>,
315    pub prefix_operator_kinds: Vec<SyntaxKind>,
316    pub value_expression_node_kinds: Vec<SyntaxKind>,
317    pub specialized_function_family_count: usize,
318    pub css_values_l4_math_function_count: usize,
319    pub css_color_function_count: usize,
320    pub ready_surfaces: Vec<&'static str>,
321    pub next_surfaces: Vec<&'static str>,
322}
323
324#[derive(Debug, Clone, PartialEq, Eq)]
325pub struct ParserRecursiveDescentCoverageSummaryV0 {
326    pub product: &'static str,
327    pub dialect_count: usize,
328    pub entry_point_count: usize,
329    pub selector_surface_count: usize,
330    pub at_rule_surface_count: usize,
331    pub dialect_extension_surface_count: usize,
332    pub recovery_surface_count: usize,
333    pub ready_surfaces: Vec<&'static str>,
334    pub next_surfaces: Vec<&'static str>,
335}
336
337#[derive(Debug, Clone, PartialEq, Eq)]
338struct ParserSemanticNameCandidateV0 {
339    kind: NameKind,
340    text: String,
341}
342
343#[derive(Debug, Clone, PartialEq, Eq)]
344pub struct ParsedStyleFacts {
345    pub product: &'static str,
346    pub dialect: StyleDialect,
347    pub selector_count: usize,
348    pub selectors: Vec<ParsedSelectorFact>,
349    pub variable_count: usize,
350    pub variables: Vec<ParsedVariableFact>,
351    pub sass_symbol_count: usize,
352    pub sass_symbols: Vec<ParsedSassSymbolFact>,
353    pub sass_include_count: usize,
354    pub sass_includes: Vec<ParsedSassIncludeFact>,
355    pub sass_module_edge_count: usize,
356    pub sass_module_edges: Vec<ParsedSassModuleEdgeFact>,
357    pub animation_count: usize,
358    pub animations: Vec<ParsedAnimationFact>,
359    pub css_module_value_count: usize,
360    pub css_module_values: Vec<ParsedCssModuleValueFact>,
361    pub css_module_value_import_edge_count: usize,
362    pub css_module_value_import_edges: Vec<ParsedCssModuleValueImportEdgeFact>,
363    pub css_module_value_definition_edge_count: usize,
364    pub css_module_value_definition_edges: Vec<ParsedCssModuleValueDefinitionEdgeFact>,
365    pub css_module_composes_count: usize,
366    pub css_module_composes: Vec<ParsedCssModuleComposesFact>,
367    pub css_module_composes_edge_count: usize,
368    pub css_module_composes_edges: Vec<ParsedCssModuleComposesEdgeFact>,
369    pub icss_count: usize,
370    pub icss: Vec<ParsedIcssFact>,
371    pub icss_import_edge_count: usize,
372    pub icss_import_edges: Vec<ParsedIcssImportEdgeFact>,
373    pub icss_export_edge_count: usize,
374    pub icss_export_edges: Vec<ParsedIcssExportEdgeFact>,
375    pub at_rule_count: usize,
376    pub at_rules: Vec<ParsedAtRuleFact>,
377    pub error_count: usize,
378}
379
380#[derive(Debug, Clone, PartialEq, Eq)]
381pub struct ParsedSelectorFact {
382    pub kind: ParsedSelectorFactKind,
383    pub name: String,
384    pub range: TextRange,
385}
386
387#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
388pub enum ParsedSelectorFactKind {
389    Class,
390    Id,
391    Placeholder,
392}
393
394#[derive(Debug, Clone, PartialEq, Eq)]
395pub struct ParsedVariableFact {
396    pub kind: ParsedVariableFactKind,
397    pub name: String,
398    pub range: TextRange,
399}
400
401#[derive(Debug, Clone, Copy, PartialEq, Eq)]
402pub enum ParsedVariableFactKind {
403    ScssDeclaration,
404    ScssReference,
405    LessDeclaration,
406    LessReference,
407    CustomPropertyDeclaration,
408    CustomPropertyReference,
409}
410
411#[derive(Debug, Clone, PartialEq, Eq)]
412pub struct ParsedSassSymbolFact {
413    pub kind: ParsedSassSymbolFactKind,
414    pub symbol_kind: &'static str,
415    pub name: String,
416    pub role: &'static str,
417    pub namespace: Option<String>,
418    pub range: TextRange,
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
422pub enum ParsedSassSymbolFactKind {
423    VariableDeclaration,
424    VariableReference,
425    MixinDeclaration,
426    MixinInclude,
427    FunctionDeclaration,
428    FunctionCall,
429}
430
431#[derive(Debug, Clone, PartialEq, Eq)]
432pub struct ParsedSassIncludeFact {
433    pub name: String,
434    pub namespace: Option<String>,
435    pub params: String,
436    pub range: TextRange,
437}
438
439#[derive(Debug, Clone, PartialEq, Eq)]
440pub struct ParsedSassModuleEdgeFact {
441    pub kind: ParsedSassModuleEdgeFactKind,
442    pub source: String,
443    pub namespace_kind: Option<&'static str>,
444    pub namespace: Option<String>,
445    pub forward_prefix: 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            "productCutoverGate",
2544        ],
2545        not_ready_surfaces: vec![
2546            "completeExternalSpecMirror",
2547            "fullPropertyValueGrammarRegistry",
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        let forward_prefix = if kind == ParsedSassModuleEdgeFactKind::Forward {
8725            sass_module_forward_prefix(tokens, source_index + 1, end)
8726        } else {
8727            None
8728        };
8729        push_sass_module_edge_fact(
8730            &mut edges,
8731            &mut seen,
8732            ParsedSassModuleEdgeFact {
8733                kind,
8734                source: source_name,
8735                namespace_kind,
8736                namespace,
8737                forward_prefix,
8738                visibility_filter_kind,
8739                visibility_filter_names,
8740                range: source.range,
8741            },
8742        );
8743    }
8744
8745    edges
8746}
8747
8748fn sass_module_edge_kind(text: &str) -> Option<ParsedSassModuleEdgeFactKind> {
8749    match text {
8750        text if text.eq_ignore_ascii_case("@use") => Some(ParsedSassModuleEdgeFactKind::Use),
8751        text if text.eq_ignore_ascii_case("@forward") => {
8752            Some(ParsedSassModuleEdgeFactKind::Forward)
8753        }
8754        text if text.eq_ignore_ascii_case("@import") => Some(ParsedSassModuleEdgeFactKind::Import),
8755        _ => None,
8756    }
8757}
8758
8759fn collect_sass_import_module_edges(
8760    tokens: &[Token<'_>],
8761    start: usize,
8762    end: usize,
8763    edges: &mut Vec<ParsedSassModuleEdgeFact>,
8764    seen: &mut BTreeSet<(ParsedSassModuleEdgeFactKind, String, u32, u32)>,
8765) {
8766    for token in &tokens[start..end] {
8767        if !matches!(token.kind, SyntaxKind::String | SyntaxKind::Url) {
8768            continue;
8769        }
8770        push_sass_module_edge_fact(
8771            edges,
8772            seen,
8773            ParsedSassModuleEdgeFact {
8774                kind: ParsedSassModuleEdgeFactKind::Import,
8775                source: css_module_value_source_name(*token),
8776                namespace_kind: None,
8777                namespace: None,
8778                forward_prefix: None,
8779                visibility_filter_kind: None,
8780                visibility_filter_names: Vec::new(),
8781                range: token.range,
8782            },
8783        );
8784    }
8785}
8786
8787fn sass_module_use_namespace(
8788    tokens: &[Token<'_>],
8789    source: &str,
8790    start: usize,
8791    end: usize,
8792) -> (Option<&'static str>, Option<String>) {
8793    let Some(as_index) = top_level_token_text_index(tokens, start, end, "as") else {
8794        return (
8795            Some("default"),
8796            sass_module_default_namespace(source).map(str::to_string),
8797        );
8798    };
8799    let Some(namespace_index) = next_non_trivia_token_index_until(tokens, as_index + 1, end) else {
8800        return (Some("invalid"), None);
8801    };
8802    let namespace = tokens[namespace_index];
8803    match namespace.kind {
8804        SyntaxKind::Star => (Some("wildcard"), None),
8805        SyntaxKind::Ident => (Some("alias"), Some(namespace.text.to_string())),
8806        _ => (Some("invalid"), None),
8807    }
8808}
8809
8810fn sass_module_forward_prefix(tokens: &[Token<'_>], start: usize, end: usize) -> Option<String> {
8811    let as_index = top_level_token_text_index(tokens, start, end, "as")?;
8812    let prefix_index = next_non_trivia_token_index_until(tokens, as_index + 1, end)?;
8813    let prefix = tokens[prefix_index].text.trim();
8814    if prefix.is_empty() {
8815        return None;
8816    }
8817    Some(prefix.to_string())
8818}
8819
8820fn sass_module_forward_visibility_filter(
8821    tokens: &[Token<'_>],
8822    start: usize,
8823    end: usize,
8824) -> (Option<&'static str>, Vec<String>) {
8825    let show_index = top_level_token_text_index(tokens, start, end, "show");
8826    let hide_index = top_level_token_text_index(tokens, start, end, "hide");
8827    let (filter_kind, filter_index) = match (show_index, hide_index) {
8828        (Some(show_index), Some(hide_index)) if show_index <= hide_index => ("show", show_index),
8829        (Some(_), Some(hide_index)) => ("hide", hide_index),
8830        (Some(show_index), None) => ("show", show_index),
8831        (None, Some(hide_index)) => ("hide", hide_index),
8832        (None, None) => return (None, Vec::new()),
8833    };
8834    let clause_end =
8835        top_level_token_text_index(tokens, filter_index + 1, end, "with").unwrap_or(end);
8836    (
8837        Some(filter_kind),
8838        sass_module_visibility_filter_names(tokens, filter_index + 1, clause_end),
8839    )
8840}
8841
8842fn sass_module_visibility_filter_names(
8843    tokens: &[Token<'_>],
8844    start: usize,
8845    end: usize,
8846) -> Vec<String> {
8847    let mut names = BTreeSet::new();
8848    for token in &tokens[start..end] {
8849        match token.kind {
8850            SyntaxKind::Ident | SyntaxKind::ScssVariable => {
8851                if matches_ignore_ascii_case(token.text, &["show", "hide", "with", "as"]) {
8852                    continue;
8853                }
8854                let name = token.text.trim_start_matches('$');
8855                if !name.is_empty() {
8856                    names.insert(name.to_string());
8857                }
8858            }
8859            _ => {}
8860        }
8861    }
8862    names.into_iter().collect()
8863}
8864
8865fn sass_module_default_namespace(source: &str) -> Option<&str> {
8866    let basename = source
8867        .rsplit(['/', '\\', ':'])
8868        .next()
8869        .unwrap_or(source)
8870        .trim_start_matches('_');
8871    let namespace = basename.split('.').next().unwrap_or(basename);
8872    (!namespace.is_empty()).then_some(namespace)
8873}
8874
8875fn push_sass_module_edge_fact(
8876    edges: &mut Vec<ParsedSassModuleEdgeFact>,
8877    seen: &mut BTreeSet<(ParsedSassModuleEdgeFactKind, String, u32, u32)>,
8878    edge: ParsedSassModuleEdgeFact,
8879) {
8880    let start: u32 = edge.range.start().into();
8881    let end: u32 = edge.range.end().into();
8882    if seen.insert((edge.kind, edge.source.clone(), start, end)) {
8883        edges.push(edge);
8884    }
8885}
8886
8887fn collect_css_module_value_facts_from_tokens(
8888    tokens: &[Token<'_>],
8889) -> Vec<ParsedCssModuleValueFact> {
8890    let mut values = Vec::new();
8891    let mut seen = BTreeSet::new();
8892    let value_path_aliases = collect_css_module_value_path_aliases_from_tokens(tokens);
8893    for (index, token) in tokens.iter().enumerate() {
8894        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
8895            continue;
8896        }
8897
8898        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
8899        let end = css_module_value_statement_end(tokens, start);
8900        let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
8901        let from_index = top_level_token_text_index(tokens, start, end, "from");
8902
8903        if let Some(from_index) = from_index
8904            && match colon_index {
8905                Some(colon_index) => from_index < colon_index,
8906                None => true,
8907            }
8908        {
8909            collect_css_module_value_import_facts(
8910                tokens,
8911                start,
8912                from_index,
8913                end,
8914                &value_path_aliases,
8915                &mut values,
8916                &mut seen,
8917            );
8918            continue;
8919        }
8920
8921        if let Some(colon_index) = colon_index {
8922            if css_module_value_path_alias_from_tokens(tokens, start, colon_index, end).is_some() {
8923                continue;
8924            }
8925            collect_css_module_value_definition_facts(
8926                tokens,
8927                start,
8928                colon_index,
8929                &mut values,
8930                &mut seen,
8931            );
8932            collect_css_module_value_reference_facts(
8933                tokens,
8934                colon_index + 1,
8935                end,
8936                &mut values,
8937                &mut seen,
8938            );
8939        } else {
8940            collect_css_module_value_definition_facts(tokens, start, end, &mut values, &mut seen);
8941        }
8942    }
8943    let local_value_names = values
8944        .iter()
8945        .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
8946        .map(|value| value.name.clone())
8947        .collect::<BTreeSet<_>>();
8948    collect_css_module_value_declaration_reference_facts(
8949        tokens,
8950        0,
8951        tokens.len(),
8952        &local_value_names,
8953        &mut values,
8954        &mut seen,
8955    );
8956    values
8957}
8958
8959fn collect_css_module_value_path_aliases_from_tokens(
8960    tokens: &[Token<'_>],
8961) -> BTreeMap<String, String> {
8962    let mut aliases = BTreeMap::new();
8963    for (index, token) in tokens.iter().enumerate() {
8964        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
8965            continue;
8966        }
8967
8968        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
8969        let end = css_module_value_statement_end(tokens, start);
8970        let Some(colon_index) = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon)
8971        else {
8972            continue;
8973        };
8974        if top_level_token_text_index(tokens, start, end, "from").is_some() {
8975            continue;
8976        }
8977        if let Some((name, target)) =
8978            css_module_value_path_alias_from_tokens(tokens, start, colon_index, end)
8979        {
8980            aliases.insert(name, target);
8981        }
8982    }
8983    aliases
8984}
8985
8986fn css_module_value_path_alias_from_tokens(
8987    tokens: &[Token<'_>],
8988    start: usize,
8989    colon_index: usize,
8990    end: usize,
8991) -> Option<(String, String)> {
8992    let name_index = next_non_trivia_token_index_until(tokens, start, colon_index)?;
8993    let name_token = tokens[name_index];
8994    if !css_module_value_name_token_can_define(name_token) {
8995        return None;
8996    }
8997    let source_index = next_non_trivia_token_index_until(tokens, colon_index + 1, end)?;
8998    let source_token = tokens[source_index];
8999    if !matches!(source_token.kind, SyntaxKind::String | SyntaxKind::Url) {
9000        return None;
9001    }
9002    let source = css_module_value_source_name(source_token);
9003    css_module_value_source_looks_like_style_request(&source)
9004        .then(|| (name_token.text.to_string(), source))
9005}
9006
9007fn css_module_value_statement_end(tokens: &[Token<'_>], start: usize) -> usize {
9008    let mut index = start;
9009    let mut paren_depth = 0usize;
9010    let mut bracket_depth = 0usize;
9011    while index < tokens.len() {
9012        match tokens[index].kind {
9013            SyntaxKind::LeftParen => paren_depth += 1,
9014            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9015            SyntaxKind::LeftBracket => bracket_depth += 1,
9016            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9017            SyntaxKind::Semicolon
9018            | SyntaxKind::SassOptionalSemicolon
9019            | SyntaxKind::LeftBrace
9020            | SyntaxKind::RightBrace
9021            | SyntaxKind::SassIndent
9022            | SyntaxKind::SassDedent
9023                if paren_depth == 0 && bracket_depth == 0 =>
9024            {
9025                return index;
9026            }
9027            _ => {}
9028        }
9029        index += 1;
9030    }
9031    index
9032}
9033
9034fn collect_css_module_value_import_facts(
9035    tokens: &[Token<'_>],
9036    start: usize,
9037    from_index: usize,
9038    end: usize,
9039    value_path_aliases: &BTreeMap<String, String>,
9040    values: &mut Vec<ParsedCssModuleValueFact>,
9041    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9042) {
9043    collect_css_module_value_import_names(tokens, start, from_index, values, seen);
9044    if let Some((source_name, source_range)) =
9045        css_module_value_import_edge_source(tokens, from_index + 1, end, value_path_aliases)
9046    {
9047        push_css_module_value_fact(
9048            values,
9049            seen,
9050            ParsedCssModuleValueFactKind::ImportSource,
9051            source_name,
9052            source_range,
9053        );
9054    }
9055}
9056
9057fn collect_css_module_value_import_edge_facts_from_tokens(
9058    tokens: &[Token<'_>],
9059) -> Vec<ParsedCssModuleValueImportEdgeFact> {
9060    let mut edges = Vec::new();
9061    let value_path_aliases = collect_css_module_value_path_aliases_from_tokens(tokens);
9062    for (index, token) in tokens.iter().enumerate() {
9063        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9064            continue;
9065        }
9066
9067        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9068        let end = css_module_value_statement_end(tokens, start);
9069        let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9070        let from_index = top_level_token_text_index(tokens, start, end, "from");
9071        let Some(from_index) = from_index else {
9072            continue;
9073        };
9074        if colon_index.is_some_and(|colon_index| from_index > colon_index) {
9075            continue;
9076        }
9077        let Some((import_source, _source_range)) =
9078            css_module_value_import_edge_source(tokens, from_index + 1, end, &value_path_aliases)
9079        else {
9080            continue;
9081        };
9082
9083        collect_css_module_value_import_edges(tokens, start, from_index, import_source, &mut edges);
9084    }
9085    edges
9086}
9087
9088fn collect_css_module_value_definition_edge_facts_from_tokens(
9089    tokens: &[Token<'_>],
9090) -> Vec<ParsedCssModuleValueDefinitionEdgeFact> {
9091    let mut edges = Vec::new();
9092    for (index, token) in tokens.iter().enumerate() {
9093        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9094            continue;
9095        }
9096
9097        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9098        let end = css_module_value_statement_end(tokens, start);
9099        let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9100        let from_index = top_level_token_text_index(tokens, start, end, "from");
9101        let Some(colon_index) = colon_index else {
9102            continue;
9103        };
9104        if from_index.is_some_and(|from_index| from_index < colon_index) {
9105            continue;
9106        }
9107
9108        let definition_names = collect_css_module_value_definition_edge_names(
9109            tokens,
9110            start,
9111            colon_index,
9112            |tokens, index| css_module_value_name_token_can_define(tokens[index]),
9113        );
9114        let reference_names = collect_css_module_value_definition_edge_names(
9115            tokens,
9116            colon_index + 1,
9117            end,
9118            css_module_value_reference_token_can_be_name,
9119        );
9120        if reference_names.is_empty() {
9121            continue;
9122        }
9123        let range_end = end
9124            .checked_sub(1)
9125            .and_then(|end| tokens.get(end))
9126            .map(|token| token.range.end())
9127            .unwrap_or_else(|| tokens[index].range.end());
9128
9129        for definition_name in definition_names {
9130            edges.push(ParsedCssModuleValueDefinitionEdgeFact {
9131                definition_name,
9132                reference_names: reference_names.clone(),
9133                range: TextRange::new(tokens[index].range.start(), range_end),
9134            });
9135        }
9136    }
9137    edges
9138}
9139
9140fn collect_css_module_value_definition_edge_names(
9141    tokens: &[Token<'_>],
9142    start: usize,
9143    end: usize,
9144    predicate: impl Fn(&[Token<'_>], usize) -> bool,
9145) -> Vec<String> {
9146    let mut names = Vec::new();
9147    let mut index = start;
9148    while index < end {
9149        if predicate(tokens, index) && !names.iter().any(|name| name == tokens[index].text) {
9150            names.push(tokens[index].text.to_string());
9151        }
9152        index += 1;
9153    }
9154    names
9155}
9156
9157fn css_module_value_import_edge_source(
9158    tokens: &[Token<'_>],
9159    start: usize,
9160    end: usize,
9161    value_path_aliases: &BTreeMap<String, String>,
9162) -> Option<(String, TextRange)> {
9163    let source_index = next_non_trivia_token_index_until(tokens, start, end)?;
9164    let token = tokens[source_index];
9165    if matches!(token.kind, SyntaxKind::String | SyntaxKind::Url) {
9166        return Some((css_module_value_source_name(token), token.range));
9167    }
9168    if css_module_value_name_token_can_define(token) {
9169        return css_module_value_source_alias_target(token.text, token.range, value_path_aliases);
9170    }
9171    None
9172}
9173
9174fn css_module_value_source_alias_target(
9175    name: &str,
9176    range: TextRange,
9177    value_path_aliases: &BTreeMap<String, String>,
9178) -> Option<(String, TextRange)> {
9179    value_path_aliases
9180        .get(name)
9181        .map(|source| (source.clone(), range))
9182}
9183
9184fn collect_css_module_value_import_edges(
9185    tokens: &[Token<'_>],
9186    start: usize,
9187    end: usize,
9188    import_source: String,
9189    edges: &mut Vec<ParsedCssModuleValueImportEdgeFact>,
9190) {
9191    let mut index = start;
9192    while index < end {
9193        let token = tokens[index];
9194        if !css_module_value_name_token_can_define(token) {
9195            index += 1;
9196            continue;
9197        }
9198        if previous_non_trivia_token_index(tokens, index, start)
9199            .is_some_and(|previous| tokens[previous].text == "as")
9200        {
9201            index += 1;
9202            continue;
9203        }
9204        let remote_name = token.text.to_string();
9205        let mut local_name = remote_name.clone();
9206        let mut local_range = token.range;
9207        if let Some(as_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
9208            && tokens[as_index].text == "as"
9209            && let Some(local_index) = next_non_trivia_token_index_until(tokens, as_index + 1, end)
9210            && css_module_value_name_token_can_define(tokens[local_index])
9211        {
9212            local_name = tokens[local_index].text.to_string();
9213            local_range = tokens[local_index].range;
9214            index = local_index + 1;
9215        } else {
9216            index += 1;
9217        }
9218        edges.push(ParsedCssModuleValueImportEdgeFact {
9219            remote_name,
9220            local_name,
9221            import_source: import_source.clone(),
9222            local_range,
9223            remote_range: token.range,
9224            range: token.range,
9225        });
9226    }
9227}
9228
9229fn collect_css_module_value_import_names(
9230    tokens: &[Token<'_>],
9231    start: usize,
9232    end: usize,
9233    values: &mut Vec<ParsedCssModuleValueFact>,
9234    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9235) {
9236    let mut index = start;
9237    while index < end {
9238        let token = tokens[index];
9239        if css_module_value_name_token_can_define(token) {
9240            let previous = previous_non_trivia_token_index(tokens, index, start);
9241            let next = next_non_trivia_token_index_until(tokens, index + 1, end);
9242            let kind = if previous.is_some_and(|previous| tokens[previous].text == "as") {
9243                Some(ParsedCssModuleValueFactKind::Definition)
9244            } else if next.is_some_and(|next| tokens[next].text == "as") {
9245                Some(ParsedCssModuleValueFactKind::Reference)
9246            } else {
9247                Some(ParsedCssModuleValueFactKind::Definition)
9248            };
9249            if let Some(kind) = kind {
9250                push_css_module_value_fact(values, seen, kind, token.text.to_string(), token.range);
9251            }
9252        }
9253        index += 1;
9254    }
9255}
9256
9257fn collect_css_module_value_definition_facts(
9258    tokens: &[Token<'_>],
9259    start: usize,
9260    end: usize,
9261    values: &mut Vec<ParsedCssModuleValueFact>,
9262    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9263) {
9264    let mut index = start;
9265    while index < end {
9266        let token = tokens[index];
9267        if css_module_value_name_token_can_define(token) {
9268            push_css_module_value_fact(
9269                values,
9270                seen,
9271                ParsedCssModuleValueFactKind::Definition,
9272                token.text.to_string(),
9273                token.range,
9274            );
9275        }
9276        index += 1;
9277    }
9278}
9279
9280fn collect_css_module_value_reference_facts(
9281    tokens: &[Token<'_>],
9282    start: usize,
9283    end: usize,
9284    values: &mut Vec<ParsedCssModuleValueFact>,
9285    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9286) {
9287    let mut index = start;
9288    let mut paren_depth = 0usize;
9289    let mut bracket_depth = 0usize;
9290    while index < end {
9291        match tokens[index].kind {
9292            SyntaxKind::LeftParen => paren_depth += 1,
9293            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9294            SyntaxKind::LeftBracket => bracket_depth += 1,
9295            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9296            _ => {}
9297        }
9298        if paren_depth == 0
9299            && bracket_depth == 0
9300            && css_module_value_reference_token_can_be_name(tokens, index)
9301        {
9302            push_css_module_value_fact(
9303                values,
9304                seen,
9305                ParsedCssModuleValueFactKind::Reference,
9306                tokens[index].text.to_string(),
9307                tokens[index].range,
9308            );
9309        }
9310        index += 1;
9311    }
9312}
9313
9314fn collect_css_module_value_declaration_reference_facts(
9315    tokens: &[Token<'_>],
9316    start: usize,
9317    end: usize,
9318    local_value_names: &BTreeSet<String>,
9319    values: &mut Vec<ParsedCssModuleValueFact>,
9320    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9321) {
9322    if local_value_names.is_empty() {
9323        return;
9324    }
9325
9326    let mut index = start;
9327    while index < end {
9328        index = skip_trivia_tokens(tokens, index, end);
9329        if index >= end {
9330            break;
9331        }
9332
9333        if tokens[index].kind == SyntaxKind::AtKeyword {
9334            let block = find_block_after_header(tokens, index, end);
9335            if let Some((open, close)) = block {
9336                if style_wrapper_at_rule(tokens[index].text) {
9337                    collect_css_module_value_declaration_reference_facts(
9338                        tokens,
9339                        open + 1,
9340                        close,
9341                        local_value_names,
9342                        values,
9343                        seen,
9344                    );
9345                }
9346                index = close + 1;
9347            } else {
9348                index = skip_statement(tokens, index, end);
9349            }
9350            continue;
9351        }
9352
9353        let statement_end = css_module_value_statement_end(tokens, index);
9354        if statement_end < end && tokens[statement_end].kind == SyntaxKind::LeftBrace {
9355            if let Some(close) = matching_right_brace(tokens, statement_end, end) {
9356                collect_css_module_value_declaration_reference_facts(
9357                    tokens,
9358                    statement_end + 1,
9359                    close,
9360                    local_value_names,
9361                    values,
9362                    seen,
9363                );
9364                index = close + 1;
9365            } else {
9366                index = statement_end + 1;
9367            }
9368            continue;
9369        }
9370
9371        if let Some(colon_index) = declaration_colon_index(tokens, index, statement_end.min(end)) {
9372            collect_known_css_module_value_reference_facts(
9373                tokens,
9374                colon_index + 1,
9375                statement_end.min(end),
9376                local_value_names,
9377                values,
9378                seen,
9379            );
9380        }
9381
9382        if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
9383            break;
9384        }
9385        index = statement_end + 1;
9386    }
9387}
9388
9389fn declaration_colon_index(tokens: &[Token<'_>], start: usize, end: usize) -> Option<usize> {
9390    let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon)?;
9391    let property_index = previous_non_trivia_token_index(tokens, colon_index, start)?;
9392    if !matches!(
9393        tokens[property_index].kind,
9394        SyntaxKind::Ident
9395            | SyntaxKind::CustomPropertyName
9396            | SyntaxKind::ScssVariable
9397            | SyntaxKind::LessVariable
9398            | SyntaxKind::LessPropertyVariableToken
9399    ) {
9400        return None;
9401    }
9402    let value_index = next_non_trivia_token_index_until(tokens, colon_index + 1, end)?;
9403    if matches!(
9404        tokens[value_index].kind,
9405        SyntaxKind::LeftBrace | SyntaxKind::LeftParen | SyntaxKind::LeftBracket
9406    ) {
9407        return None;
9408    }
9409    Some(colon_index)
9410}
9411
9412fn collect_known_css_module_value_reference_facts(
9413    tokens: &[Token<'_>],
9414    start: usize,
9415    end: usize,
9416    local_value_names: &BTreeSet<String>,
9417    values: &mut Vec<ParsedCssModuleValueFact>,
9418    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9419) {
9420    let mut index = start;
9421    let mut paren_depth = 0usize;
9422    let mut bracket_depth = 0usize;
9423    while index < end {
9424        match tokens[index].kind {
9425            SyntaxKind::LeftParen => paren_depth += 1,
9426            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9427            SyntaxKind::LeftBracket => bracket_depth += 1,
9428            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9429            _ => {}
9430        }
9431        if paren_depth == 0
9432            && bracket_depth == 0
9433            && css_module_value_reference_token_can_be_name(tokens, index)
9434            && local_value_names.contains(tokens[index].text)
9435        {
9436            push_css_module_value_fact(
9437                values,
9438                seen,
9439                ParsedCssModuleValueFactKind::Reference,
9440                tokens[index].text.to_string(),
9441                tokens[index].range,
9442            );
9443        }
9444        index += 1;
9445    }
9446}
9447
9448fn push_css_module_value_fact(
9449    values: &mut Vec<ParsedCssModuleValueFact>,
9450    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9451    kind: ParsedCssModuleValueFactKind,
9452    name: String,
9453    range: TextRange,
9454) {
9455    if seen.insert((
9456        kind,
9457        name.clone(),
9458        u32::from(range.start()),
9459        u32::from(range.end()),
9460    )) {
9461        values.push(ParsedCssModuleValueFact { kind, name, range });
9462    }
9463}
9464
9465fn top_level_token_kind_index(
9466    tokens: &[Token<'_>],
9467    start: usize,
9468    end: usize,
9469    expected: SyntaxKind,
9470) -> Option<usize> {
9471    let mut index = start;
9472    let mut paren_depth = 0usize;
9473    let mut bracket_depth = 0usize;
9474    while index < end {
9475        match tokens[index].kind {
9476            SyntaxKind::LeftParen => paren_depth += 1,
9477            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9478            SyntaxKind::LeftBracket => bracket_depth += 1,
9479            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9480            kind if kind == expected && paren_depth == 0 && bracket_depth == 0 => {
9481                return Some(index);
9482            }
9483            _ => {}
9484        }
9485        index += 1;
9486    }
9487    None
9488}
9489
9490fn top_level_token_text_index(
9491    tokens: &[Token<'_>],
9492    start: usize,
9493    end: usize,
9494    expected: &str,
9495) -> Option<usize> {
9496    let mut index = start;
9497    let mut paren_depth = 0usize;
9498    let mut bracket_depth = 0usize;
9499    while index < end {
9500        match tokens[index].kind {
9501            SyntaxKind::LeftParen => paren_depth += 1,
9502            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9503            SyntaxKind::LeftBracket => bracket_depth += 1,
9504            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9505            SyntaxKind::Ident
9506                if paren_depth == 0
9507                    && bracket_depth == 0
9508                    && tokens[index].text.eq_ignore_ascii_case(expected) =>
9509            {
9510                return Some(index);
9511            }
9512            _ => {}
9513        }
9514        index += 1;
9515    }
9516    None
9517}
9518
9519fn previous_non_trivia_token_index(
9520    tokens: &[Token<'_>],
9521    mut index: usize,
9522    start: usize,
9523) -> Option<usize> {
9524    while index > start {
9525        index -= 1;
9526        if !tokens[index].kind.is_trivia() {
9527            return Some(index);
9528        }
9529    }
9530    None
9531}
9532
9533fn css_module_value_name_token_can_define(token: Token<'_>) -> bool {
9534    matches!(
9535        token.kind,
9536        SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9537    ) && !matches!(token.text, "as" | "from")
9538}
9539
9540fn css_module_value_reference_token_can_be_name(tokens: &[Token<'_>], index: usize) -> bool {
9541    let token = tokens[index];
9542    if !matches!(
9543        token.kind,
9544        SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9545    ) {
9546        return false;
9547    }
9548    if let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9549        && tokens[next_index].kind == SyntaxKind::LeftParen
9550    {
9551        return false;
9552    }
9553    !css_module_value_literal_ident_is_not_reference(token.text)
9554}
9555
9556fn css_module_value_literal_ident_is_not_reference(name: &str) -> bool {
9557    matches!(
9558        name.to_ascii_lowercase().as_str(),
9559        "initial"
9560            | "inherit"
9561            | "unset"
9562            | "revert"
9563            | "revert-layer"
9564            | "none"
9565            | "auto"
9566            | "normal"
9567            | "transparent"
9568            | "currentcolor"
9569            | "black"
9570            | "white"
9571            | "red"
9572            | "green"
9573            | "blue"
9574            | "yellow"
9575            | "magenta"
9576            | "cyan"
9577            | "solid"
9578            | "dashed"
9579            | "block"
9580            | "inline"
9581            | "flex"
9582            | "grid"
9583    )
9584}
9585
9586fn css_module_value_source_name(token: Token<'_>) -> String {
9587    token
9588        .text
9589        .trim_matches(|character| character == '"' || character == '\'')
9590        .to_string()
9591}
9592
9593fn css_module_value_source_looks_like_style_request(source: &str) -> bool {
9594    let lower = source.to_ascii_lowercase();
9595    (lower.starts_with('/') || lower.starts_with("./") || lower.starts_with("../"))
9596        && (lower.ends_with(".css")
9597            || lower.ends_with(".scss")
9598            || lower.ends_with(".sass")
9599            || lower.ends_with(".less"))
9600}
9601
9602fn collect_css_module_composes_facts_from_tokens(
9603    tokens: &[Token<'_>],
9604) -> Vec<ParsedCssModuleComposesFact> {
9605    let mut composes = Vec::new();
9606    let mut seen = BTreeSet::new();
9607    for (index, token) in tokens.iter().enumerate() {
9608        if token.kind != SyntaxKind::Ident || !token.text.eq_ignore_ascii_case("composes") {
9609            continue;
9610        }
9611        let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9612        else {
9613            continue;
9614        };
9615        if tokens[colon_index].kind != SyntaxKind::Colon {
9616            continue;
9617        }
9618
9619        let start = colon_index + 1;
9620        let end = css_module_value_statement_end(tokens, start);
9621        let from_index = top_level_token_text_index(tokens, start, end, "from");
9622        let target_end = from_index.unwrap_or(end);
9623        collect_css_module_composes_targets(tokens, start, target_end, &mut composes, &mut seen);
9624        if let Some(from_index) = from_index {
9625            collect_css_module_composes_import_source(
9626                tokens,
9627                from_index + 1,
9628                end,
9629                &mut composes,
9630                &mut seen,
9631            );
9632        }
9633    }
9634    composes
9635}
9636
9637fn collect_css_module_composes_edge_facts_from_tokens(
9638    tokens: &[Token<'_>],
9639) -> Vec<ParsedCssModuleComposesEdgeFact> {
9640    let mut edges = Vec::new();
9641    collect_css_module_composes_edge_facts_in_range(tokens, 0, tokens.len(), &[], None, &mut edges);
9642    edges
9643}
9644
9645fn collect_css_module_composes_edge_facts_in_range(
9646    tokens: &[Token<'_>],
9647    start: usize,
9648    end: usize,
9649    parent_branches: &[SelectorBranch],
9650    css_module_scope: Option<&'static str>,
9651    edges: &mut Vec<ParsedCssModuleComposesEdgeFact>,
9652) {
9653    let mut index = start;
9654    while index < end {
9655        index = skip_trivia_tokens(tokens, index, end);
9656        if index >= end {
9657            break;
9658        }
9659
9660        if tokens[index].kind == SyntaxKind::AtKeyword {
9661            let block = find_block_after_header(tokens, index, end);
9662            if let Some((open, close)) = block {
9663                if tokens[index].text == "@nest" {
9664                    if css_module_scope == Some("global") {
9665                        collect_css_module_composes_edge_facts_in_range(
9666                            tokens,
9667                            open + 1,
9668                            close,
9669                            &[],
9670                            css_module_scope,
9671                            edges,
9672                        );
9673                    } else {
9674                        let branches =
9675                            resolve_selector_header(tokens, index + 1, open, parent_branches);
9676                        collect_immediate_css_module_composes_edge_facts(
9677                            tokens,
9678                            open + 1,
9679                            close,
9680                            &branches,
9681                            edges,
9682                        );
9683                        collect_css_module_composes_edge_facts_in_range(
9684                            tokens,
9685                            open + 1,
9686                            close,
9687                            &branches,
9688                            css_module_scope,
9689                            edges,
9690                        );
9691                    }
9692                } else if style_wrapper_at_rule(tokens[index].text) {
9693                    collect_css_module_composes_edge_facts_in_range(
9694                        tokens,
9695                        open + 1,
9696                        close,
9697                        parent_branches,
9698                        css_module_scope,
9699                        edges,
9700                    );
9701                }
9702                index = close + 1;
9703            } else {
9704                index = skip_statement(tokens, index, end);
9705            }
9706            continue;
9707        }
9708
9709        let Some((open, close)) = find_block_after_header(tokens, index, end) else {
9710            index = skip_statement(tokens, index, end);
9711            continue;
9712        };
9713
9714        let effective_scope = css_module_scope
9715            .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
9716        if effective_scope == Some("global") {
9717            collect_css_module_composes_edge_facts_in_range(
9718                tokens,
9719                open + 1,
9720                close,
9721                &[],
9722                effective_scope,
9723                edges,
9724            );
9725        } else {
9726            let branches = resolve_selector_header(tokens, index, open, parent_branches);
9727            collect_immediate_css_module_composes_edge_facts(
9728                tokens,
9729                open + 1,
9730                close,
9731                &branches,
9732                edges,
9733            );
9734            collect_css_module_composes_edge_facts_in_range(
9735                tokens,
9736                open + 1,
9737                close,
9738                &branches,
9739                effective_scope,
9740                edges,
9741            );
9742        }
9743        index = close + 1;
9744    }
9745}
9746
9747fn collect_immediate_css_module_composes_edge_facts(
9748    tokens: &[Token<'_>],
9749    start: usize,
9750    end: usize,
9751    owner_branches: &[SelectorBranch],
9752    edges: &mut Vec<ParsedCssModuleComposesEdgeFact>,
9753) {
9754    let owner_selector_names = sorted_selector_branch_names(owner_branches);
9755    let mut index = start;
9756    let mut block_depth = 0usize;
9757    while index < end {
9758        match tokens[index].kind {
9759            SyntaxKind::LeftBrace | SyntaxKind::SassIndent => {
9760                block_depth += 1;
9761                index += 1;
9762                continue;
9763            }
9764            SyntaxKind::RightBrace | SyntaxKind::SassDedent => {
9765                block_depth = block_depth.saturating_sub(1);
9766                index += 1;
9767                continue;
9768            }
9769            _ => {}
9770        }
9771        if block_depth > 0
9772            || tokens[index].kind != SyntaxKind::Ident
9773            || !tokens[index].text.eq_ignore_ascii_case("composes")
9774        {
9775            index += 1;
9776            continue;
9777        }
9778        let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end) else {
9779            index += 1;
9780            continue;
9781        };
9782        if tokens[colon_index].kind != SyntaxKind::Colon {
9783            index += 1;
9784            continue;
9785        }
9786
9787        let value_start = colon_index + 1;
9788        let value_end = css_module_value_statement_end(tokens, value_start).min(end);
9789        let from_index = top_level_token_text_index(tokens, value_start, value_end, "from");
9790        let target_end = from_index.unwrap_or(value_end);
9791        let target_names =
9792            collect_css_module_composes_target_names(tokens, value_start, target_end);
9793        if target_names.is_empty() {
9794            index = value_end;
9795            continue;
9796        }
9797
9798        let (kind, import_source) = from_index
9799            .and_then(|from_index| {
9800                css_module_composes_import_edge_source(tokens, from_index + 1, value_end)
9801            })
9802            .map(|source| {
9803                if source == "global" {
9804                    (ParsedCssModuleComposesEdgeKind::Global, Some(source))
9805                } else {
9806                    (ParsedCssModuleComposesEdgeKind::External, Some(source))
9807                }
9808            })
9809            .unwrap_or((ParsedCssModuleComposesEdgeKind::Local, None));
9810        let range_end = value_end
9811            .checked_sub(1)
9812            .and_then(|end| tokens.get(end))
9813            .map(|token| token.range.end())
9814            .unwrap_or_else(|| tokens[index].range.end());
9815
9816        edges.push(ParsedCssModuleComposesEdgeFact {
9817            kind,
9818            owner_selector_names: owner_selector_names.clone(),
9819            target_names,
9820            import_source,
9821            range: TextRange::new(tokens[index].range.start(), range_end),
9822        });
9823        index = value_end;
9824    }
9825}
9826
9827fn sorted_selector_branch_names(branches: &[SelectorBranch]) -> Vec<String> {
9828    branches
9829        .iter()
9830        .map(|branch| branch.name.clone())
9831        .collect::<BTreeSet<_>>()
9832        .into_iter()
9833        .collect()
9834}
9835
9836fn collect_css_module_composes_target_names(
9837    tokens: &[Token<'_>],
9838    start: usize,
9839    end: usize,
9840) -> Vec<String> {
9841    let mut names = Vec::new();
9842    let mut index = start;
9843    while index < end {
9844        if matches!(
9845            tokens[index].kind,
9846            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9847        ) && !tokens[index].text.eq_ignore_ascii_case("from")
9848            && !names.iter().any(|name| name == tokens[index].text)
9849        {
9850            names.push(tokens[index].text.to_string());
9851        }
9852        index += 1;
9853    }
9854    names
9855}
9856
9857fn css_module_composes_import_edge_source(
9858    tokens: &[Token<'_>],
9859    start: usize,
9860    end: usize,
9861) -> Option<String> {
9862    let source_index = next_non_trivia_token_index_until(tokens, start, end)?;
9863    let token = tokens[source_index];
9864    matches!(
9865        token.kind,
9866        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
9867    )
9868    .then(|| css_module_value_source_name(token))
9869}
9870
9871fn collect_css_module_composes_targets(
9872    tokens: &[Token<'_>],
9873    start: usize,
9874    end: usize,
9875    composes: &mut Vec<ParsedCssModuleComposesFact>,
9876    seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
9877) {
9878    let mut index = start;
9879    while index < end {
9880        if matches!(
9881            tokens[index].kind,
9882            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9883        ) && !tokens[index].text.eq_ignore_ascii_case("from")
9884        {
9885            push_css_module_composes_fact(
9886                composes,
9887                seen,
9888                ParsedCssModuleComposesFactKind::Target,
9889                tokens[index].text.to_string(),
9890                tokens[index].range,
9891            );
9892        }
9893        index += 1;
9894    }
9895}
9896
9897fn collect_css_module_composes_import_source(
9898    tokens: &[Token<'_>],
9899    start: usize,
9900    end: usize,
9901    composes: &mut Vec<ParsedCssModuleComposesFact>,
9902    seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
9903) {
9904    if let Some(source_index) = next_non_trivia_token_index_until(tokens, start, end) {
9905        let token = tokens[source_index];
9906        if matches!(
9907            token.kind,
9908            SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
9909        ) {
9910            push_css_module_composes_fact(
9911                composes,
9912                seen,
9913                ParsedCssModuleComposesFactKind::ImportSource,
9914                css_module_value_source_name(token),
9915                token.range,
9916            );
9917        }
9918    }
9919}
9920
9921fn push_css_module_composes_fact(
9922    composes: &mut Vec<ParsedCssModuleComposesFact>,
9923    seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
9924    kind: ParsedCssModuleComposesFactKind,
9925    name: String,
9926    range: TextRange,
9927) {
9928    if seen.insert((
9929        kind,
9930        name.clone(),
9931        u32::from(range.start()),
9932        u32::from(range.end()),
9933    )) {
9934        composes.push(ParsedCssModuleComposesFact { kind, name, range });
9935    }
9936}
9937
9938fn collect_icss_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedIcssFact> {
9939    let mut icss = Vec::new();
9940    let mut seen = BTreeSet::new();
9941    for (index, token) in tokens.iter().enumerate() {
9942        if token.kind != SyntaxKind::Colon {
9943            continue;
9944        }
9945        let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9946        else {
9947            continue;
9948        };
9949        let name = tokens[name_index].text;
9950        if !matches!(tokens[name_index].kind, SyntaxKind::Ident) {
9951            continue;
9952        }
9953        if name.eq_ignore_ascii_case("export") {
9954            if let Some((open, close)) =
9955                find_block_after_header(tokens, name_index + 1, tokens.len())
9956            {
9957                collect_icss_export_names(tokens, open + 1, close, &mut icss, &mut seen);
9958            }
9959            continue;
9960        }
9961        if name.eq_ignore_ascii_case("import") {
9962            collect_icss_import_source(tokens, name_index + 1, &mut icss, &mut seen);
9963            if let Some((open, close)) =
9964                find_block_after_header(tokens, name_index + 1, tokens.len())
9965            {
9966                collect_icss_import_names(tokens, open + 1, close, &mut icss, &mut seen);
9967            }
9968        }
9969    }
9970    icss
9971}
9972
9973fn collect_icss_import_edge_facts_from_tokens(
9974    tokens: &[Token<'_>],
9975) -> Vec<ParsedIcssImportEdgeFact> {
9976    let mut edges = Vec::new();
9977    for (index, token) in tokens.iter().enumerate() {
9978        if token.kind != SyntaxKind::Colon {
9979            continue;
9980        }
9981        let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9982        else {
9983            continue;
9984        };
9985        if tokens[name_index].kind != SyntaxKind::Ident
9986            || !tokens[name_index].text.eq_ignore_ascii_case("import")
9987        {
9988            continue;
9989        }
9990        let Some(import_source) = icss_import_edge_source(tokens, name_index + 1) else {
9991            continue;
9992        };
9993        if let Some((open, close)) = find_block_after_header(tokens, name_index + 1, tokens.len()) {
9994            collect_icss_import_edges(tokens, open + 1, close, import_source, &mut edges);
9995        }
9996    }
9997    edges
9998}
9999
10000fn collect_icss_export_edge_facts_from_tokens(
10001    tokens: &[Token<'_>],
10002) -> Vec<ParsedIcssExportEdgeFact> {
10003    let mut edges = Vec::new();
10004    for (index, token) in tokens.iter().enumerate() {
10005        if token.kind != SyntaxKind::Colon {
10006            continue;
10007        }
10008        let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10009        else {
10010            continue;
10011        };
10012        if tokens[name_index].kind != SyntaxKind::Ident
10013            || !tokens[name_index].text.eq_ignore_ascii_case("export")
10014        {
10015            continue;
10016        }
10017        if let Some((open, close)) = find_block_after_header(tokens, name_index + 1, tokens.len()) {
10018            collect_icss_export_edges(tokens, open + 1, close, &mut edges);
10019        }
10020    }
10021    edges
10022}
10023
10024fn collect_icss_export_edges(
10025    tokens: &[Token<'_>],
10026    start: usize,
10027    end: usize,
10028    edges: &mut Vec<ParsedIcssExportEdgeFact>,
10029) {
10030    let mut index = start;
10031    while index < end {
10032        let token = tokens[index];
10033        if matches!(
10034            token.kind,
10035            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10036        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10037            && tokens[colon_index].kind == SyntaxKind::Colon
10038        {
10039            let value_end = css_module_value_statement_end(tokens, colon_index + 1).min(end);
10040            let reference_names = collect_css_module_value_definition_edge_names(
10041                tokens,
10042                colon_index + 1,
10043                value_end,
10044                css_module_value_reference_token_can_be_name,
10045            );
10046            if !reference_names.is_empty() {
10047                let range_end = value_end
10048                    .checked_sub(1)
10049                    .and_then(|end| tokens.get(end))
10050                    .map(|token| token.range.end())
10051                    .unwrap_or_else(|| token.range.end());
10052                edges.push(ParsedIcssExportEdgeFact {
10053                    export_name: token.text.to_string(),
10054                    reference_names,
10055                    range: TextRange::new(token.range.start(), range_end),
10056                });
10057            }
10058            index = value_end;
10059            continue;
10060        }
10061        index += 1;
10062    }
10063}
10064
10065fn icss_import_edge_source(tokens: &[Token<'_>], start: usize) -> Option<String> {
10066    let open_index = next_non_trivia_token_index_until(tokens, start, tokens.len())?;
10067    if tokens[open_index].kind != SyntaxKind::LeftParen {
10068        return None;
10069    }
10070    let source_index = next_non_trivia_token_index_until(tokens, open_index + 1, tokens.len())?;
10071    let token = tokens[source_index];
10072    matches!(
10073        token.kind,
10074        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10075    )
10076    .then(|| css_module_value_source_name(token))
10077}
10078
10079fn collect_icss_import_edges(
10080    tokens: &[Token<'_>],
10081    start: usize,
10082    end: usize,
10083    import_source: String,
10084    edges: &mut Vec<ParsedIcssImportEdgeFact>,
10085) {
10086    let mut index = start;
10087    while index < end {
10088        let token = tokens[index];
10089        if matches!(
10090            token.kind,
10091            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10092        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10093            && tokens[colon_index].kind == SyntaxKind::Colon
10094            && let Some(remote_index) =
10095                next_non_trivia_token_index_until(tokens, colon_index + 1, end)
10096            && matches!(
10097                tokens[remote_index].kind,
10098                SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10099            )
10100        {
10101            edges.push(ParsedIcssImportEdgeFact {
10102                local_name: token.text.to_string(),
10103                remote_name: tokens[remote_index].text.to_string(),
10104                import_source: import_source.clone(),
10105                range: token.range,
10106            });
10107            index = css_module_value_statement_end(tokens, colon_index + 1);
10108            continue;
10109        }
10110        index += 1;
10111    }
10112}
10113
10114fn collect_icss_export_names(
10115    tokens: &[Token<'_>],
10116    start: usize,
10117    end: usize,
10118    icss: &mut Vec<ParsedIcssFact>,
10119    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10120) {
10121    let mut index = start;
10122    while index < end {
10123        let token = tokens[index];
10124        if matches!(
10125            token.kind,
10126            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10127        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10128            && tokens[colon_index].kind == SyntaxKind::Colon
10129        {
10130            push_icss_fact(
10131                icss,
10132                seen,
10133                ParsedIcssFactKind::ExportName,
10134                token.text.to_string(),
10135                token.range,
10136            );
10137            index = css_module_value_statement_end(tokens, colon_index + 1);
10138            continue;
10139        }
10140        index += 1;
10141    }
10142}
10143
10144fn collect_icss_import_source(
10145    tokens: &[Token<'_>],
10146    start: usize,
10147    icss: &mut Vec<ParsedIcssFact>,
10148    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10149) {
10150    let Some(open_index) = next_non_trivia_token_index_until(tokens, start, tokens.len()) else {
10151        return;
10152    };
10153    if tokens[open_index].kind != SyntaxKind::LeftParen {
10154        return;
10155    }
10156    let Some(source_index) =
10157        next_non_trivia_token_index_until(tokens, open_index + 1, tokens.len())
10158    else {
10159        return;
10160    };
10161    let token = tokens[source_index];
10162    if matches!(
10163        token.kind,
10164        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10165    ) {
10166        push_icss_fact(
10167            icss,
10168            seen,
10169            ParsedIcssFactKind::ImportSource,
10170            css_module_value_source_name(token),
10171            token.range,
10172        );
10173    }
10174}
10175
10176fn collect_icss_import_names(
10177    tokens: &[Token<'_>],
10178    start: usize,
10179    end: usize,
10180    icss: &mut Vec<ParsedIcssFact>,
10181    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10182) {
10183    let mut index = start;
10184    while index < end {
10185        let token = tokens[index];
10186        if matches!(
10187            token.kind,
10188            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10189        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10190            && tokens[colon_index].kind == SyntaxKind::Colon
10191        {
10192            push_icss_fact(
10193                icss,
10194                seen,
10195                ParsedIcssFactKind::ImportLocalName,
10196                token.text.to_string(),
10197                token.range,
10198            );
10199            if let Some(remote_index) =
10200                next_non_trivia_token_index_until(tokens, colon_index + 1, end)
10201                && matches!(
10202                    tokens[remote_index].kind,
10203                    SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10204                )
10205            {
10206                push_icss_fact(
10207                    icss,
10208                    seen,
10209                    ParsedIcssFactKind::ImportRemoteName,
10210                    tokens[remote_index].text.to_string(),
10211                    tokens[remote_index].range,
10212                );
10213            }
10214            index = css_module_value_statement_end(tokens, colon_index + 1);
10215            continue;
10216        }
10217        index += 1;
10218    }
10219}
10220
10221fn push_icss_fact(
10222    icss: &mut Vec<ParsedIcssFact>,
10223    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10224    kind: ParsedIcssFactKind,
10225    name: String,
10226    range: TextRange,
10227) {
10228    if seen.insert((
10229        kind,
10230        name.clone(),
10231        u32::from(range.start()),
10232        u32::from(range.end()),
10233    )) {
10234        icss.push(ParsedIcssFact { kind, name, range });
10235    }
10236}
10237
10238fn collect_animation_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedAnimationFact> {
10239    let mut animations = Vec::new();
10240    let mut seen = BTreeSet::new();
10241    for (index, token) in tokens.iter().enumerate() {
10242        if token.kind == SyntaxKind::AtKeyword && token.text.eq_ignore_ascii_case("@keyframes") {
10243            if let Some(name_index) =
10244                next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10245                && let Some(name) = animation_name_from_token(tokens[name_index])
10246            {
10247                push_animation_fact(
10248                    &mut animations,
10249                    &mut seen,
10250                    ParsedAnimationFactKind::KeyframesDeclaration,
10251                    name,
10252                    tokens[name_index].range,
10253                );
10254            }
10255            continue;
10256        }
10257
10258        if token.kind == SyntaxKind::Ident
10259            && token.text.eq_ignore_ascii_case("animation-name")
10260            && let Some(colon_index) =
10261                next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10262            && tokens[colon_index].kind == SyntaxKind::Colon
10263        {
10264            collect_animation_name_references_until(
10265                tokens,
10266                colon_index + 1,
10267                &mut animations,
10268                &mut seen,
10269            );
10270        }
10271
10272        if token.kind == SyntaxKind::Ident
10273            && token.text.eq_ignore_ascii_case("animation")
10274            && let Some(colon_index) =
10275                next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10276            && tokens[colon_index].kind == SyntaxKind::Colon
10277        {
10278            collect_animation_shorthand_references_until(
10279                tokens,
10280                colon_index + 1,
10281                &mut animations,
10282                &mut seen,
10283            );
10284        }
10285    }
10286    animations
10287}
10288
10289fn collect_animation_name_references_until(
10290    tokens: &[Token<'_>],
10291    start: usize,
10292    animations: &mut Vec<ParsedAnimationFact>,
10293    seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10294) {
10295    let mut index = start;
10296    let mut paren_depth = 0usize;
10297    let mut bracket_depth = 0usize;
10298    while index < tokens.len() {
10299        match tokens[index].kind {
10300            SyntaxKind::LeftParen => paren_depth += 1,
10301            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
10302            SyntaxKind::LeftBracket => bracket_depth += 1,
10303            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
10304            SyntaxKind::Semicolon
10305            | SyntaxKind::SassOptionalSemicolon
10306            | SyntaxKind::RightBrace
10307            | SyntaxKind::SassDedent
10308                if paren_depth == 0 && bracket_depth == 0 =>
10309            {
10310                break;
10311            }
10312            _ => {}
10313        }
10314
10315        if paren_depth == 0
10316            && bracket_depth == 0
10317            && let Some(name) = animation_name_from_token(tokens[index])
10318        {
10319            push_animation_fact(
10320                animations,
10321                seen,
10322                ParsedAnimationFactKind::AnimationNameReference,
10323                name,
10324                tokens[index].range,
10325            );
10326        }
10327        index += 1;
10328    }
10329}
10330
10331fn collect_animation_shorthand_references_until(
10332    tokens: &[Token<'_>],
10333    start: usize,
10334    animations: &mut Vec<ParsedAnimationFact>,
10335    seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10336) {
10337    let mut index = start;
10338    let mut paren_depth = 0usize;
10339    let mut bracket_depth = 0usize;
10340    while index < tokens.len() {
10341        match tokens[index].kind {
10342            SyntaxKind::LeftParen => paren_depth += 1,
10343            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
10344            SyntaxKind::LeftBracket => bracket_depth += 1,
10345            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
10346            SyntaxKind::Semicolon
10347            | SyntaxKind::SassOptionalSemicolon
10348            | SyntaxKind::RightBrace
10349            | SyntaxKind::SassDedent
10350                if paren_depth == 0 && bracket_depth == 0 =>
10351            {
10352                break;
10353            }
10354            _ => {}
10355        }
10356
10357        if paren_depth == 0
10358            && bracket_depth == 0
10359            && animation_shorthand_token_can_be_name(tokens, index)
10360            && let Some(name) = animation_name_from_token(tokens[index])
10361        {
10362            push_animation_fact(
10363                animations,
10364                seen,
10365                ParsedAnimationFactKind::AnimationNameReference,
10366                name,
10367                tokens[index].range,
10368            );
10369        }
10370        index += 1;
10371    }
10372}
10373
10374fn animation_shorthand_token_can_be_name(tokens: &[Token<'_>], index: usize) -> bool {
10375    let token = tokens[index];
10376    if token.kind == SyntaxKind::String {
10377        return true;
10378    }
10379    if token.kind != SyntaxKind::Ident {
10380        return false;
10381    }
10382    if let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10383        && tokens[next_index].kind == SyntaxKind::LeftParen
10384    {
10385        return false;
10386    }
10387    !animation_shorthand_ident_is_non_name(token.text)
10388}
10389
10390fn animation_shorthand_ident_is_non_name(name: &str) -> bool {
10391    matches!(
10392        name.to_ascii_lowercase().as_str(),
10393        "ease"
10394            | "ease-in"
10395            | "ease-out"
10396            | "ease-in-out"
10397            | "linear"
10398            | "step-start"
10399            | "step-end"
10400            | "infinite"
10401            | "normal"
10402            | "reverse"
10403            | "alternate"
10404            | "alternate-reverse"
10405            | "running"
10406            | "paused"
10407            | "forwards"
10408            | "backwards"
10409            | "both"
10410            | "replace"
10411            | "add"
10412            | "accumulate"
10413            | "auto"
10414    )
10415}
10416
10417fn push_animation_fact(
10418    animations: &mut Vec<ParsedAnimationFact>,
10419    seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10420    kind: ParsedAnimationFactKind,
10421    name: String,
10422    range: TextRange,
10423) {
10424    if seen.insert((
10425        kind,
10426        name.clone(),
10427        u32::from(range.start()),
10428        u32::from(range.end()),
10429    )) {
10430        animations.push(ParsedAnimationFact { kind, name, range });
10431    }
10432}
10433
10434fn animation_name_from_token(token: Token<'_>) -> Option<String> {
10435    if !matches!(token.kind, SyntaxKind::Ident | SyntaxKind::String) {
10436        return None;
10437    }
10438    let name = token
10439        .text
10440        .trim_matches(|character| character == '"' || character == '\'')
10441        .to_string();
10442    if name.is_empty() || animation_name_is_reserved(&name) {
10443        return None;
10444    }
10445    Some(name)
10446}
10447
10448fn animation_name_is_reserved(name: &str) -> bool {
10449    matches!(
10450        name.to_ascii_lowercase().as_str(),
10451        "none" | "initial" | "inherit" | "unset" | "revert" | "revert-layer"
10452    )
10453}
10454
10455fn containing_at_rule_header_name<'text>(
10456    tokens: &'text [Token<'text>],
10457    index: usize,
10458) -> Option<&'text str> {
10459    let mut current = index;
10460    while current > 0 {
10461        current -= 1;
10462        let token = tokens.get(current)?;
10463        if token.kind.is_trivia() {
10464            continue;
10465        }
10466        if matches!(
10467            token.kind,
10468            SyntaxKind::Semicolon
10469                | SyntaxKind::SassOptionalSemicolon
10470                | SyntaxKind::LeftBrace
10471                | SyntaxKind::RightBrace
10472                | SyntaxKind::SassIndent
10473                | SyntaxKind::SassDedent
10474        ) {
10475            return None;
10476        }
10477        if token.kind == SyntaxKind::AtKeyword {
10478            return Some(token.text);
10479        }
10480    }
10481    None
10482}
10483
10484fn skip_trivia_tokens(tokens: &[Token<'_>], mut index: usize, end: usize) -> usize {
10485    while index < end && tokens[index].kind.is_trivia() {
10486        index += 1;
10487    }
10488    index
10489}
10490
10491fn skip_statement(tokens: &[Token<'_>], mut index: usize, end: usize) -> usize {
10492    while index < end {
10493        match tokens[index].kind {
10494            SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon => return index + 1,
10495            SyntaxKind::RightBrace | SyntaxKind::SassDedent => return index,
10496            _ => index += 1,
10497        }
10498    }
10499    index
10500}
10501
10502fn find_block_after_header(
10503    tokens: &[Token<'_>],
10504    start: usize,
10505    end: usize,
10506) -> Option<(usize, usize)> {
10507    let mut index = start;
10508    while index < end {
10509        match tokens[index].kind {
10510            SyntaxKind::Semicolon
10511            | SyntaxKind::SassOptionalSemicolon
10512            | SyntaxKind::RightBrace
10513            | SyntaxKind::SassDedent => return None,
10514            SyntaxKind::LeftBrace => {
10515                let close = matching_right_brace(tokens, index, end)?;
10516                return Some((index, close));
10517            }
10518            SyntaxKind::SassIndent => {
10519                let close = matching_sass_dedent(tokens, index, end)?;
10520                return Some((index, close));
10521            }
10522            _ => index += 1,
10523        }
10524    }
10525    None
10526}
10527
10528fn matching_right_brace(tokens: &[Token<'_>], open: usize, end: usize) -> Option<usize> {
10529    let mut depth = 0usize;
10530    let mut index = open;
10531    while index < end {
10532        match tokens[index].kind {
10533            SyntaxKind::LeftBrace => depth += 1,
10534            SyntaxKind::RightBrace => {
10535                depth = depth.saturating_sub(1);
10536                if depth == 0 {
10537                    return Some(index);
10538                }
10539            }
10540            _ => {}
10541        }
10542        index += 1;
10543    }
10544    None
10545}
10546
10547fn matching_sass_dedent(tokens: &[Token<'_>], open: usize, end: usize) -> Option<usize> {
10548    let mut depth = 0usize;
10549    let mut index = open;
10550    while index < end {
10551        match tokens[index].kind {
10552            SyntaxKind::SassIndent => depth += 1,
10553            SyntaxKind::SassDedent => {
10554                depth = depth.saturating_sub(1);
10555                if depth == 0 {
10556                    return Some(index);
10557                }
10558            }
10559            _ => {}
10560        }
10561        index += 1;
10562    }
10563    None
10564}
10565
10566fn style_wrapper_at_rule(name: &str) -> bool {
10567    matches_ignore_ascii_case(
10568        name,
10569        &[
10570            "@media",
10571            "@supports",
10572            "@when",
10573            "@else",
10574            "@layer",
10575            "@scope",
10576            "@container",
10577            "@starting-style",
10578            "@if",
10579            "@else",
10580            "@for",
10581            "@each",
10582            "@while",
10583            "@at-root",
10584            "@include",
10585        ],
10586    )
10587}
10588
10589fn is_selector_combinator_kind(kind: SyntaxKind) -> bool {
10590    matches!(
10591        kind,
10592        SyntaxKind::GreaterThan
10593            | SyntaxKind::Plus
10594            | SyntaxKind::Tilde
10595            | SyntaxKind::ColumnCombinator
10596            | SyntaxKind::DoublePipe
10597    )
10598}
10599
10600fn selector_component_can_start(kind: SyntaxKind) -> bool {
10601    matches!(
10602        kind,
10603        SyntaxKind::Dot
10604            | SyntaxKind::Hash
10605            | SyntaxKind::Ident
10606            | SyntaxKind::Star
10607            | SyntaxKind::Ampersand
10608            | SyntaxKind::ScssPlaceholder
10609            | SyntaxKind::LeftBracket
10610            | SyntaxKind::Colon
10611            | SyntaxKind::DoubleColon
10612    )
10613}
10614
10615fn namespace_selector_target_can_start(kind: SyntaxKind) -> bool {
10616    matches!(
10617        kind,
10618        SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star
10619    )
10620}
10621
10622fn keyframe_selector_token_is_valid(token: Token<'_>) -> bool {
10623    token.kind == SyntaxKind::Percentage
10624        || (token.kind == SyntaxKind::Ident
10625            && (token.text.eq_ignore_ascii_case("from") || token.text.eq_ignore_ascii_case("to")))
10626}
10627
10628fn selector_component_can_end(kind: SyntaxKind) -> bool {
10629    matches!(
10630        kind,
10631        SyntaxKind::Ident
10632            | SyntaxKind::CustomPropertyName
10633            | SyntaxKind::Hash
10634            | SyntaxKind::RightBracket
10635            | SyntaxKind::RightParen
10636            | SyntaxKind::Star
10637    )
10638}
10639
10640fn collect_at_rule_facts_from_tokens(
10641    tokens: &[Token<'_>],
10642    dialect: StyleDialect,
10643) -> Vec<ParsedAtRuleFact> {
10644    tokens
10645        .iter()
10646        .filter(|token| token.kind == SyntaxKind::AtKeyword)
10647        .map(|token| {
10648            let css_spec = at_rule_spec(token.text);
10649            let node_kind = css_spec
10650                .or_else(|| match dialect {
10651                    StyleDialect::Scss | StyleDialect::Sass => scss_at_rule_spec(token.text),
10652                    StyleDialect::Css | StyleDialect::Less => None,
10653                })
10654                .map(|spec| spec.node_kind);
10655            let name = if css_spec.is_some() {
10656                token.text.to_ascii_lowercase()
10657            } else {
10658                token.text.to_string()
10659            };
10660            ParsedAtRuleFact {
10661                name,
10662                node_kind,
10663                range: token.range,
10664            }
10665        })
10666        .collect()
10667}
10668
10669fn next_non_trivia_token<'text>(
10670    tokens: &'text [Token<'text>],
10671    mut index: usize,
10672) -> Option<Token<'text>> {
10673    while let Some(token) = tokens.get(index).copied() {
10674        if !token.kind.is_trivia() {
10675            return Some(token);
10676        }
10677        index += 1;
10678    }
10679    None
10680}
10681
10682fn next_non_trivia_token_until<'text>(
10683    tokens: &'text [Token<'text>],
10684    mut index: usize,
10685    end: usize,
10686) -> Option<Token<'text>> {
10687    while index < end {
10688        let token = tokens.get(index).copied()?;
10689        if !token.kind.is_trivia() {
10690            return Some(token);
10691        }
10692        index += 1;
10693    }
10694    None
10695}
10696
10697fn next_non_trivia_token_index_until(
10698    tokens: &[Token<'_>],
10699    mut index: usize,
10700    end: usize,
10701) -> Option<usize> {
10702    while index < end {
10703        let token = tokens.get(index)?;
10704        if !token.kind.is_trivia() {
10705            return Some(index);
10706        }
10707        index += 1;
10708    }
10709    None
10710}
10711
10712fn next_non_trivia_token_after_range<'text>(
10713    tokens: &'text [Token<'text>],
10714    range: TextRange,
10715    end: usize,
10716) -> Option<Token<'text>> {
10717    let index = token_index_by_range(tokens, range)?;
10718    next_non_trivia_token_until(tokens, index + 1, end)
10719}
10720
10721fn token_index_by_range(tokens: &[Token<'_>], range: TextRange) -> Option<usize> {
10722    tokens.iter().position(|token| token.range == range)
10723}
10724
10725fn matching_right_paren_from_range(
10726    tokens: &[Token<'_>],
10727    open_range: TextRange,
10728    end: usize,
10729) -> Option<usize> {
10730    let mut depth = 0usize;
10731    let mut index = token_index_by_range(tokens, open_range)?;
10732    while index < end {
10733        match tokens[index].kind {
10734            SyntaxKind::LeftParen => depth += 1,
10735            SyntaxKind::RightParen => {
10736                depth = depth.saturating_sub(1);
10737                if depth == 0 {
10738                    return Some(index);
10739                }
10740            }
10741            _ => {}
10742        }
10743        index += 1;
10744    }
10745    None
10746}
10747
10748fn previous_non_trivia_token<'text>(
10749    tokens: &'text [Token<'text>],
10750    start: usize,
10751    index: usize,
10752) -> Option<Token<'text>> {
10753    let mut current = index;
10754    while current > start {
10755        current -= 1;
10756        let token = tokens.get(current).copied()?;
10757        if !token.kind.is_trivia() {
10758            return Some(token);
10759        }
10760    }
10761    None
10762}
10763
10764fn at_rule_spec(text: &str) -> Option<AtRuleSpec> {
10765    let lowered = text.to_ascii_lowercase();
10766    let (node_kind, block_kind) = match lowered.as_str() {
10767        "@media" => (SyntaxKind::MediaRule, AtRuleBlockKind::GroupRuleList),
10768        "@supports" => (SyntaxKind::SupportsRule, AtRuleBlockKind::GroupRuleList),
10769        "@when" => (SyntaxKind::WhenRule, AtRuleBlockKind::GroupRuleList),
10770        "@else" => (SyntaxKind::ElseRule, AtRuleBlockKind::GroupRuleList),
10771        "@container" => (SyntaxKind::ContainerRule, AtRuleBlockKind::GroupRuleList),
10772        "@layer" => (SyntaxKind::LayerRule, AtRuleBlockKind::GroupRuleList),
10773        "@scope" => (SyntaxKind::ScopeRule, AtRuleBlockKind::GroupRuleList),
10774        "@starting-style" => (
10775            SyntaxKind::StartingStyleRule,
10776            AtRuleBlockKind::GroupRuleList,
10777        ),
10778        "@nest" => (SyntaxKind::NestRule, AtRuleBlockKind::DeclarationList),
10779        "@keyframes" => (SyntaxKind::KeyframesRule, AtRuleBlockKind::Keyframes),
10780        "@font-face" => (SyntaxKind::FontFaceRule, AtRuleBlockKind::DeclarationList),
10781        "@page" => (SyntaxKind::PageRule, AtRuleBlockKind::DeclarationList),
10782        "@property" => (SyntaxKind::PropertyRule, AtRuleBlockKind::DeclarationList),
10783        "@counter-style" => (
10784            SyntaxKind::CounterStyleRule,
10785            AtRuleBlockKind::DeclarationList,
10786        ),
10787        "@font-palette-values" => (
10788            SyntaxKind::FontPaletteValuesRule,
10789            AtRuleBlockKind::DeclarationList,
10790        ),
10791        "@color-profile" => (
10792            SyntaxKind::ColorProfileRule,
10793            AtRuleBlockKind::DeclarationList,
10794        ),
10795        "@position-try" => (
10796            SyntaxKind::PositionTryRule,
10797            AtRuleBlockKind::DeclarationList,
10798        ),
10799        "@font-feature-values" => (
10800            SyntaxKind::FontFeatureValuesRule,
10801            AtRuleBlockKind::GroupRuleList,
10802        ),
10803        "@stylistic" => (
10804            SyntaxKind::FontFeatureValuesStylisticRule,
10805            AtRuleBlockKind::DeclarationList,
10806        ),
10807        "@styleset" => (
10808            SyntaxKind::FontFeatureValuesStylesetRule,
10809            AtRuleBlockKind::DeclarationList,
10810        ),
10811        "@character-variant" => (
10812            SyntaxKind::FontFeatureValuesCharacterVariantRule,
10813            AtRuleBlockKind::DeclarationList,
10814        ),
10815        "@swash" => (
10816            SyntaxKind::FontFeatureValuesSwashRule,
10817            AtRuleBlockKind::DeclarationList,
10818        ),
10819        "@ornaments" => (
10820            SyntaxKind::FontFeatureValuesOrnamentsRule,
10821            AtRuleBlockKind::DeclarationList,
10822        ),
10823        "@annotation" => (
10824            SyntaxKind::FontFeatureValuesAnnotationRule,
10825            AtRuleBlockKind::DeclarationList,
10826        ),
10827        "@historical-forms" => (
10828            SyntaxKind::FontFeatureValuesHistoricalFormsRule,
10829            AtRuleBlockKind::DeclarationList,
10830        ),
10831        "@view-transition" => (
10832            SyntaxKind::ViewTransitionRule,
10833            AtRuleBlockKind::DeclarationList,
10834        ),
10835        "@charset" => (SyntaxKind::CharsetRule, AtRuleBlockKind::Raw),
10836        "@import" => (SyntaxKind::ImportRule, AtRuleBlockKind::Raw),
10837        "@namespace" => (SyntaxKind::NamespaceRule, AtRuleBlockKind::Raw),
10838        "@custom-media" => (SyntaxKind::CustomMediaRule, AtRuleBlockKind::Raw),
10839        text if is_page_margin_at_rule(text) => {
10840            (SyntaxKind::PageMarginRule, AtRuleBlockKind::DeclarationList)
10841        }
10842        _ => return None,
10843    };
10844    Some(AtRuleSpec {
10845        node_kind,
10846        block_kind,
10847    })
10848}
10849
10850fn is_page_margin_at_rule(text: &str) -> bool {
10851    matches!(
10852        text,
10853        "@top-left-corner"
10854            | "@top-left"
10855            | "@top-center"
10856            | "@top-right"
10857            | "@top-right-corner"
10858            | "@bottom-left-corner"
10859            | "@bottom-left"
10860            | "@bottom-center"
10861            | "@bottom-right"
10862            | "@bottom-right-corner"
10863            | "@left-top"
10864            | "@left-middle"
10865            | "@left-bottom"
10866            | "@right-top"
10867            | "@right-middle"
10868            | "@right-bottom"
10869    )
10870}
10871
10872fn scss_at_rule_spec(text: &str) -> Option<AtRuleSpec> {
10873    let (node_kind, block_kind) = match text {
10874        "@use" => (SyntaxKind::ScssUseRule, AtRuleBlockKind::Raw),
10875        "@forward" => (SyntaxKind::ScssForwardRule, AtRuleBlockKind::Raw),
10876        "@mixin" => (
10877            SyntaxKind::ScssMixinDeclaration,
10878            AtRuleBlockKind::DeclarationList,
10879        ),
10880        "@include" => (
10881            SyntaxKind::ScssIncludeRule,
10882            AtRuleBlockKind::DeclarationList,
10883        ),
10884        "@function" => (
10885            SyntaxKind::ScssFunctionDeclaration,
10886            AtRuleBlockKind::DeclarationList,
10887        ),
10888        "@return" => (SyntaxKind::ScssReturnRule, AtRuleBlockKind::Raw),
10889        "@extend" => (SyntaxKind::ScssExtendRule, AtRuleBlockKind::Raw),
10890        "@if" => (SyntaxKind::ScssControlIf, AtRuleBlockKind::DeclarationList),
10891        "@else" => (
10892            SyntaxKind::ScssControlElse,
10893            AtRuleBlockKind::DeclarationList,
10894        ),
10895        "@each" => (
10896            SyntaxKind::ScssControlEach,
10897            AtRuleBlockKind::DeclarationList,
10898        ),
10899        "@for" => (SyntaxKind::ScssControlFor, AtRuleBlockKind::DeclarationList),
10900        "@while" => (
10901            SyntaxKind::ScssControlWhile,
10902            AtRuleBlockKind::DeclarationList,
10903        ),
10904        "@at-root" => (SyntaxKind::ScssAtRootRule, AtRuleBlockKind::DeclarationList),
10905        "@error" => (SyntaxKind::ScssErrorRule, AtRuleBlockKind::Raw),
10906        "@warn" => (SyntaxKind::ScssWarnRule, AtRuleBlockKind::Raw),
10907        "@debug" => (SyntaxKind::ScssDebugRule, AtRuleBlockKind::Raw),
10908        "@content" => (SyntaxKind::ScssContentRule, AtRuleBlockKind::Raw),
10909        _ => return None,
10910    };
10911    Some(AtRuleSpec {
10912        node_kind,
10913        block_kind,
10914    })
10915}
10916
10917fn is_selector_boundary(kind: SyntaxKind) -> bool {
10918    matches!(
10919        kind,
10920        SyntaxKind::Comma
10921            | SyntaxKind::LeftBrace
10922            | SyntaxKind::SassIndent
10923            | SyntaxKind::RightBrace
10924            | SyntaxKind::SassDedent
10925            | SyntaxKind::Semicolon
10926            | SyntaxKind::SassOptionalSemicolon
10927    )
10928}
10929
10930fn is_selector_boundary_until(kind: SyntaxKind, recovery: &[SyntaxKind]) -> bool {
10931    is_selector_boundary(kind) || recovery.contains(&kind)
10932}
10933
10934fn is_selector_list_pseudo_class(text: &str) -> bool {
10935    matches!(text, "is" | "where" | "local" | "global")
10936}
10937
10938fn is_nth_pseudo_class(text: &str) -> bool {
10939    matches!(
10940        text,
10941        "nth-child" | "nth-last-child" | "nth-of-type" | "nth-last-of-type"
10942    )
10943}
10944
10945fn language_tag_token_can_start(kind: SyntaxKind) -> bool {
10946    matches!(kind, SyntaxKind::Ident | SyntaxKind::String)
10947}
10948
10949fn selector_item_token_is_recoverable(kind: SyntaxKind) -> bool {
10950    matches!(
10951        kind,
10952        SyntaxKind::Whitespace
10953            | SyntaxKind::SassIndentedNewline
10954            | SyntaxKind::Dot
10955            | SyntaxKind::Comma
10956            | SyntaxKind::Hash
10957            | SyntaxKind::Ident
10958            | SyntaxKind::CustomPropertyName
10959            | SyntaxKind::String
10960            | SyntaxKind::Number
10961            | SyntaxKind::Percentage
10962            | SyntaxKind::Dimension
10963            | SyntaxKind::Star
10964            | SyntaxKind::Ampersand
10965            | SyntaxKind::ScssPlaceholder
10966            | SyntaxKind::LeftBracket
10967            | SyntaxKind::RightBracket
10968            | SyntaxKind::Colon
10969            | SyntaxKind::DoubleColon
10970            | SyntaxKind::LeftParen
10971            | SyntaxKind::RightParen
10972            | SyntaxKind::Equals
10973            | SyntaxKind::IncludesMatch
10974            | SyntaxKind::DashMatch
10975            | SyntaxKind::PrefixMatch
10976            | SyntaxKind::SuffixMatch
10977            | SyntaxKind::SubstringMatch
10978            | SyntaxKind::Pipe
10979            | SyntaxKind::ColumnCombinator
10980            | SyntaxKind::GreaterThan
10981            | SyntaxKind::Plus
10982            | SyntaxKind::Minus
10983            | SyntaxKind::Tilde
10984            | SyntaxKind::KeywordAnd
10985            | SyntaxKind::KeywordOr
10986            | SyntaxKind::KeywordNot
10987    )
10988}
10989
10990fn is_at_rule_prelude_boundary(kind: SyntaxKind) -> bool {
10991    matches!(
10992        kind,
10993        SyntaxKind::LeftBrace
10994            | SyntaxKind::SassIndent
10995            | SyntaxKind::Semicolon
10996            | SyntaxKind::SassOptionalSemicolon
10997    )
10998}
10999
11000fn is_statement_end(kind: SyntaxKind) -> bool {
11001    matches!(
11002        kind,
11003        SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon
11004    )
11005}
11006
11007fn sass_token_can_end_statement(kind: SyntaxKind) -> bool {
11008    !matches!(
11009        kind,
11010        SyntaxKind::Whitespace
11011            | SyntaxKind::LineComment
11012            | SyntaxKind::BlockComment
11013            | SyntaxKind::SassIndentedNewline
11014            | SyntaxKind::SassIndent
11015            | SyntaxKind::SassDedent
11016            | SyntaxKind::SassOptionalSemicolon
11017            | SyntaxKind::Comma
11018            | SyntaxKind::Colon
11019            | SyntaxKind::DoubleColon
11020            | SyntaxKind::LeftBrace
11021            | SyntaxKind::LeftParen
11022            | SyntaxKind::LeftBracket
11023            | SyntaxKind::Plus
11024            | SyntaxKind::Minus
11025            | SyntaxKind::Star
11026            | SyntaxKind::Slash
11027            | SyntaxKind::GreaterThan
11028            | SyntaxKind::LessThan
11029            | SyntaxKind::Equals
11030            | SyntaxKind::Arrow
11031            | SyntaxKind::Pipe
11032            | SyntaxKind::Tilde
11033            | SyntaxKind::Caret
11034            | SyntaxKind::Ampersand
11035            | SyntaxKind::DoubleAmpersand
11036            | SyntaxKind::ColumnCombinator
11037            | SyntaxKind::IncludesMatch
11038            | SyntaxKind::DashMatch
11039            | SyntaxKind::PrefixMatch
11040            | SyntaxKind::SuffixMatch
11041            | SyntaxKind::SubstringMatch
11042            | SyntaxKind::PlusEquals
11043            | SyntaxKind::MinusEquals
11044            | SyntaxKind::SlashEquals
11045    )
11046}
11047
11048fn function_argument_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11049    let mut kinds = vec![SyntaxKind::RightParen];
11050    for kind in recovery {
11051        if !kinds.contains(kind) {
11052            kinds.push(*kind);
11053        }
11054    }
11055    kinds
11056}
11057
11058fn bracketed_value_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11059    let mut kinds = vec![SyntaxKind::RightBracket];
11060    for kind in recovery {
11061        if !kinds.contains(kind) {
11062            kinds.push(*kind);
11063        }
11064    }
11065    kinds
11066}
11067
11068fn simple_block_recovery(close_kind: SyntaxKind, recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11069    let mut kinds = vec![close_kind];
11070    for kind in recovery {
11071        if !kinds.contains(kind) {
11072            kinds.push(*kind);
11073        }
11074    }
11075    kinds
11076}
11077
11078fn matching_simple_block_close(open_kind: SyntaxKind) -> Option<SyntaxKind> {
11079    match open_kind {
11080        SyntaxKind::LeftBrace => Some(SyntaxKind::RightBrace),
11081        SyntaxKind::LeftBracket => Some(SyntaxKind::RightBracket),
11082        SyntaxKind::LeftParen => Some(SyntaxKind::RightParen),
11083        _ => None,
11084    }
11085}
11086
11087fn value_list_item_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11088    let mut kinds = vec![SyntaxKind::Comma];
11089    for kind in recovery {
11090        if !kinds.contains(kind) {
11091            kinds.push(*kind);
11092        }
11093    }
11094    kinds
11095}
11096
11097fn comma_separated_component_value_list_item_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11098    let mut kinds = vec![SyntaxKind::Comma];
11099    for kind in recovery {
11100        if !kinds.contains(kind) {
11101            kinds.push(*kind);
11102        }
11103    }
11104    kinds
11105}
11106
11107fn variable_declaration_node_kind(kind: SyntaxKind, has_colon: bool) -> SyntaxKind {
11108    if has_colon {
11109        return kind;
11110    }
11111    match kind {
11112        SyntaxKind::ScssVariableDeclaration => SyntaxKind::BogusScssVariable,
11113        SyntaxKind::LessVariableDeclaration => SyntaxKind::BogusLessVariable,
11114        _ => kind,
11115    }
11116}
11117
11118fn is_attribute_matcher(kind: SyntaxKind) -> bool {
11119    matches!(
11120        kind,
11121        SyntaxKind::Equals
11122            | SyntaxKind::IncludesMatch
11123            | SyntaxKind::DashMatch
11124            | SyntaxKind::PrefixMatch
11125            | SyntaxKind::SuffixMatch
11126            | SyntaxKind::SubstringMatch
11127    )
11128}
11129
11130fn attribute_name_token_can_start(kind: SyntaxKind) -> bool {
11131    matches!(
11132        kind,
11133        SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star
11134    )
11135}
11136
11137fn attribute_name_token_can_continue(kind: SyntaxKind) -> bool {
11138    matches!(
11139        kind,
11140        SyntaxKind::Ident
11141            | SyntaxKind::CustomPropertyName
11142            | SyntaxKind::Star
11143            | SyntaxKind::Pipe
11144            | SyntaxKind::ColumnCombinator
11145    )
11146}
11147
11148fn attribute_value_token_can_start(kind: SyntaxKind) -> bool {
11149    matches!(
11150        kind,
11151        SyntaxKind::Ident
11152            | SyntaxKind::CustomPropertyName
11153            | SyntaxKind::String
11154            | SyntaxKind::Hash
11155            | SyntaxKind::Number
11156            | SyntaxKind::Dimension
11157    )
11158}
11159
11160fn is_combinator(kind: SyntaxKind) -> bool {
11161    matches!(
11162        kind,
11163        SyntaxKind::GreaterThan
11164            | SyntaxKind::Plus
11165            | SyntaxKind::Tilde
11166            | SyntaxKind::ColumnCombinator
11167    )
11168}
11169
11170fn infix_binding_power(kind: SyntaxKind) -> Option<(u8, u8)> {
11171    match kind {
11172        SyntaxKind::Plus | SyntaxKind::Minus => Some((1, 2)),
11173        SyntaxKind::Star | SyntaxKind::Slash | SyntaxKind::Percent => Some((3, 4)),
11174        _ => None,
11175    }
11176}
11177
11178fn specialized_function_kind(text: &str) -> Option<SyntaxKind> {
11179    if text.eq_ignore_ascii_case("var") {
11180        return Some(SyntaxKind::VarFunction);
11181    }
11182    if text.eq_ignore_ascii_case("calc") {
11183        return Some(SyntaxKind::CalcFunction);
11184    }
11185    if text.eq_ignore_ascii_case("env") {
11186        return Some(SyntaxKind::EnvFunction);
11187    }
11188    if text.eq_ignore_ascii_case("attr") {
11189        return Some(SyntaxKind::AttrFunction);
11190    }
11191    if matches_ignore_ascii_case(text, VALUES_L4_MATH_FUNCTION_NAMES) {
11192        return Some(SyntaxKind::MathFunction);
11193    }
11194    if matches_ignore_ascii_case(text, CSS_COLOR_FUNCTION_NAMES) {
11195        return Some(SyntaxKind::ColorValue);
11196    }
11197    if matches_ignore_ascii_case(text, CSS_GRADIENT_FUNCTION_NAMES) {
11198        return Some(SyntaxKind::GradientFunction);
11199    }
11200    if matches_ignore_ascii_case(text, CSS_TRANSFORM_FUNCTION_NAMES) {
11201        return Some(SyntaxKind::TransformFunction);
11202    }
11203    if matches_ignore_ascii_case(text, CSS_FILTER_FUNCTION_NAMES) {
11204        return Some(SyntaxKind::FilterFunction);
11205    }
11206    if matches_ignore_ascii_case(text, CSS_IMAGE_FUNCTION_NAMES) {
11207        return Some(SyntaxKind::ImageFunction);
11208    }
11209    if matches_ignore_ascii_case(text, CSS_SHAPE_FUNCTION_NAMES) {
11210        return Some(SyntaxKind::ShapeFunction);
11211    }
11212    None
11213}
11214
11215fn function_argument_count_is_valid(function_name: &str, argument_count: usize) -> bool {
11216    if function_name.eq_ignore_ascii_case("calc") {
11217        return argument_count == 1;
11218    }
11219    if matches_ignore_ascii_case(function_name, &["min", "max", "hypot"]) {
11220        return argument_count >= 1;
11221    }
11222    if function_name.eq_ignore_ascii_case("clamp") {
11223        return argument_count == 3;
11224    }
11225    if function_name.eq_ignore_ascii_case("round") {
11226        return (2..=3).contains(&argument_count);
11227    }
11228    if function_name.eq_ignore_ascii_case("log") {
11229        return (1..=2).contains(&argument_count);
11230    }
11231    if matches_ignore_ascii_case(function_name, &["mod", "rem", "pow", "atan2"]) {
11232        return argument_count == 2;
11233    }
11234    if matches_ignore_ascii_case(
11235        function_name,
11236        &[
11237            "sin", "cos", "tan", "asin", "acos", "atan", "sqrt", "exp", "abs", "sign",
11238        ],
11239    ) {
11240        return argument_count == 1;
11241    }
11242    if function_name.eq_ignore_ascii_case("color-mix") {
11243        return argument_count == 3;
11244    }
11245    if function_name.eq_ignore_ascii_case("light-dark") {
11246        return argument_count == 2;
11247    }
11248    if function_name.eq_ignore_ascii_case("contrast-color") {
11249        return argument_count == 1;
11250    }
11251    true
11252}
11253
11254fn function_requires_filled_top_level_arguments(function_name: &str) -> bool {
11255    function_name.eq_ignore_ascii_case("calc")
11256        || matches_ignore_ascii_case(function_name, VALUES_L4_MATH_FUNCTION_NAMES)
11257        || matches_ignore_ascii_case(
11258            function_name,
11259            &["color-mix", "light-dark", "contrast-color"],
11260        )
11261}
11262
11263fn at_rule_prelude_head_is_custom_property_name(kind: SyntaxKind) -> bool {
11264    kind == SyntaxKind::CustomPropertyName || is_interpolation_start(kind)
11265}
11266
11267fn at_rule_prelude_head_is_custom_ident(kind: SyntaxKind) -> bool {
11268    kind == SyntaxKind::Ident || is_interpolation_start(kind)
11269}
11270
11271fn is_dynamic_function_argument_head(kind: SyntaxKind) -> bool {
11272    matches!(
11273        kind,
11274        SyntaxKind::ScssVariable
11275            | SyntaxKind::LessVariable
11276            | SyntaxKind::ScssInterpolationStart
11277            | SyntaxKind::LessInterpolationStart
11278    )
11279}
11280
11281fn is_scss_module_source_token(kind: SyntaxKind) -> bool {
11282    matches!(
11283        kind,
11284        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::ScssInterpolationStart
11285    )
11286}
11287
11288fn is_scss_module_namespace_token(kind: SyntaxKind) -> bool {
11289    matches!(
11290        kind,
11291        SyntaxKind::Ident | SyntaxKind::Star | SyntaxKind::ScssInterpolationStart
11292    )
11293}
11294
11295fn is_scss_module_visibility_name_token(kind: SyntaxKind) -> bool {
11296    matches!(
11297        kind,
11298        SyntaxKind::Ident
11299            | SyntaxKind::ScssVariable
11300            | SyntaxKind::ScssPlaceholder
11301            | SyntaxKind::ScssInterpolationStart
11302    )
11303}
11304
11305fn is_css_module_from_source_token(kind: SyntaxKind, text: &str) -> bool {
11306    matches!(
11307        kind,
11308        SyntaxKind::String
11309            | SyntaxKind::Url
11310            | SyntaxKind::ScssInterpolationStart
11311            | SyntaxKind::LessInterpolationStart
11312    ) || (kind == SyntaxKind::Ident && text == "global")
11313}
11314
11315fn is_scss_control_rule_kind(kind: SyntaxKind) -> bool {
11316    matches!(
11317        kind,
11318        SyntaxKind::ScssControlIf
11319            | SyntaxKind::ScssControlElse
11320            | SyntaxKind::ScssControlEach
11321            | SyntaxKind::ScssControlFor
11322            | SyntaxKind::ScssControlWhile
11323    )
11324}
11325
11326fn matches_ignore_ascii_case(value: &str, candidates: &[&str]) -> bool {
11327    candidates
11328        .iter()
11329        .any(|candidate| value.eq_ignore_ascii_case(candidate))
11330}
11331
11332fn css_module_scope_function_kind(text: &str) -> Option<SyntaxKind> {
11333    match text {
11334        "local" => Some(SyntaxKind::CssModuleLocalBlock),
11335        "global" => Some(SyntaxKind::CssModuleGlobalBlock),
11336        _ => None,
11337    }
11338}
11339
11340fn text_range(start: usize, end: usize) -> TextRange {
11341    TextRange::new(TextSize::from(start as u32), TextSize::from(end as u32))
11342}
11343
11344#[cfg(test)]
11345mod tests;