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
26// R1 narrow public surface: `public_product` is private; only this curated set
27// of V0 contract types + summary fns is re-exported (no wildcard). Reuse of
28// omena-parser as a building block goes through these names — keep the list
29// explicit and minimal rather than widening to `pub use public_product::*`.
30mod public_product;
31pub use public_product::{
32    ParserCanonicalCandidateBundleV0, ParserCanonicalProducerSignalV0, ParserEvaluatorCandidatesV0,
33    ParserIndexSummaryV0, dialect_for_path, summarize_css_modules_intermediate,
34    summarize_parser_canonical_candidate, summarize_parser_canonical_producer_signal,
35    summarize_parser_evaluator_candidates,
36};
37
38const VALUES_L4_MATH_FUNCTION_NAMES: &[&str] = &[
39    "min", "max", "clamp", "round", "mod", "rem", "sin", "cos", "tan", "asin", "acos", "atan",
40    "atan2", "pow", "sqrt", "hypot", "log", "exp", "abs", "sign",
41];
42
43const CSS_COLOR_FUNCTION_NAMES: &[&str] = &[
44    "rgb",
45    "rgba",
46    "hsl",
47    "hsla",
48    "hwb",
49    "lab",
50    "lch",
51    "oklab",
52    "oklch",
53    "color",
54    "color-mix",
55    "device-cmyk",
56    "light-dark",
57    "contrast-color",
58];
59
60const CSS_GRADIENT_FUNCTION_NAMES: &[&str] = &[
61    "linear-gradient",
62    "radial-gradient",
63    "conic-gradient",
64    "repeating-linear-gradient",
65    "repeating-radial-gradient",
66    "repeating-conic-gradient",
67];
68
69const CSS_TRANSFORM_FUNCTION_NAMES: &[&str] = &[
70    "matrix",
71    "matrix3d",
72    "translate",
73    "translate3d",
74    "translateX",
75    "translateY",
76    "translateZ",
77    "scale",
78    "scale3d",
79    "scaleX",
80    "scaleY",
81    "scaleZ",
82    "rotate",
83    "rotate3d",
84    "rotateX",
85    "rotateY",
86    "rotateZ",
87    "skew",
88    "skewX",
89    "skewY",
90    "perspective",
91];
92
93const CSS_FILTER_FUNCTION_NAMES: &[&str] = &[
94    "blur",
95    "brightness",
96    "contrast",
97    "drop-shadow",
98    "grayscale",
99    "hue-rotate",
100    "invert",
101    "opacity",
102    "saturate",
103    "sepia",
104];
105
106const CSS_IMAGE_FUNCTION_NAMES: &[&str] = &["image", "image-set", "cross-fade", "element", "paint"];
107
108const CSS_SHAPE_FUNCTION_NAMES: &[&str] = &[
109    "path", "shape", "ray", "inset", "circle", "ellipse", "polygon",
110];
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
113#[serde(rename_all = "camelCase")]
114pub struct ParserByteSpanV0 {
115    pub start: usize,
116    pub end: usize,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Default)]
120#[serde(rename_all = "camelCase")]
121pub struct ParserPositionV0 {
122    pub line: usize,
123    pub character: usize,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Default)]
127#[serde(rename_all = "camelCase")]
128pub struct ParserRangeV0 {
129    pub start: ParserPositionV0,
130    pub end: ParserPositionV0,
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum StyleLanguage {
135    Css,
136    Scss,
137    Less,
138}
139
140impl StyleLanguage {
141    pub fn from_module_path(path: &str) -> Option<Self> {
142        if path.ends_with(".module.css") || path.ends_with(".css") {
143            Some(Self::Css)
144        } else if path.ends_with(".module.scss") || path.ends_with(".scss") {
145            Some(Self::Scss)
146        } else if path.ends_with(".module.less") || path.ends_with(".less") {
147            Some(Self::Less)
148        } else {
149            None
150        }
151    }
152}
153
154#[derive(Debug, Clone)]
155pub struct ParseResult {
156    green: GreenNode,
157    interner: Option<Arc<TokenInterner>>,
158    errors: Vec<ParseError>,
159    token_count: usize,
160    dialect: StyleDialect,
161}
162
163impl PartialEq for ParseResult {
164    fn eq(&self, other: &Self) -> bool {
165        self.green == other.green
166            && self.errors == other.errors
167            && self.token_count == other.token_count
168            && self.dialect == other.dialect
169    }
170}
171
172impl Eq for ParseResult {}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct LexResult {
176    tokens: Vec<LexedToken>,
177    errors: Vec<ParseError>,
178    dialect: StyleDialect,
179}
180
181impl LexResult {
182    pub fn tokens(&self) -> &[LexedToken] {
183        &self.tokens
184    }
185
186    pub fn errors(&self) -> &[ParseError] {
187        &self.errors
188    }
189
190    pub fn dialect(&self) -> StyleDialect {
191        self.dialect
192    }
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub struct LexedToken {
197    pub kind: SyntaxKind,
198    pub range: TextRange,
199    pub text: String,
200}
201
202impl ParseResult {
203    pub fn green(&self) -> &GreenNode {
204        &self.green
205    }
206
207    pub fn syntax(&self) -> SyntaxNode<SyntaxKind> {
208        if let Some(interner) = &self.interner {
209            return SyntaxNode::new_root_with_resolver(self.green.clone(), Arc::clone(interner))
210                .syntax()
211                .clone();
212        }
213        SyntaxNode::new_root(self.green.clone())
214    }
215
216    pub fn source_text(&self) -> Option<String> {
217        let syntax = self.syntax();
218        syntax
219            .try_resolved()
220            .map(|resolved| resolved.text().to_string())
221    }
222
223    pub fn errors(&self) -> &[ParseError] {
224        &self.errors
225    }
226
227    pub fn token_count(&self) -> usize {
228        self.token_count
229    }
230
231    pub fn dialect(&self) -> StyleDialect {
232        self.dialect
233    }
234
235    pub fn cst(&self) -> ParsedCst {
236        ParsedCst::new(self.syntax())
237    }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct ParseError {
242    pub code: ParseErrorCode,
243    pub range: TextRange,
244    pub message: &'static str,
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum ParseErrorCode {
249    UnterminatedBlockComment,
250    UnterminatedString,
251    UnexpectedCharacter,
252    ExpectedSelectorName,
253    UnterminatedAttributeSelector,
254    ExpectedValue,
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub enum ParseEntryPoint {
259    Stylesheet,
260    RuleList,
261    Rule,
262    DeclarationList,
263    Declaration,
264    Value,
265    ComponentValue,
266    ComponentValueList,
267    CommaSeparatedComponentValueList,
268    SimpleBlock,
269}
270
271#[derive(Debug, Clone, PartialEq, Eq)]
272pub struct ParserBoundarySummary {
273    pub product: &'static str,
274    pub tree_model: &'static str,
275    pub parser_track: &'static str,
276    pub dialect_count: usize,
277    pub shared_name_kind_count: usize,
278    pub ready_surfaces: Vec<&'static str>,
279    pub not_ready_surfaces: Vec<&'static str>,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
283pub struct ParserSemanticNameConsumptionSummaryV0 {
284    pub product: &'static str,
285    pub dialect: StyleDialect,
286    pub semantic_name_count: usize,
287    pub interned_name_count: usize,
288    pub invalid_name_count: usize,
289    pub class_name_count: usize,
290    pub css_ident_count: usize,
291    pub property_name_count: usize,
292    pub selector_key_count: usize,
293    pub custom_property_name_count: usize,
294    pub keyframes_name_count: usize,
295    pub mixin_name_count: usize,
296    pub file_path_count: usize,
297    pub ready_surfaces: Vec<&'static str>,
298}
299
300#[derive(Debug, Clone, PartialEq, Eq)]
301pub struct ParserCstEquivalenceSummaryV0 {
302    pub product: &'static str,
303    pub dialect: StyleDialect,
304    pub root_kind: SyntaxKind,
305    pub parser_node_count: usize,
306    pub parser_token_count: usize,
307    pub typed_wrapper_count: usize,
308    pub source_text_round_trip_ready: bool,
309    pub syntax_kind_round_trip_ready: bool,
310    pub zero_unknown_kind_ready: bool,
311    pub typed_cst_wrapper_ready: bool,
312    pub ready_surfaces: Vec<&'static str>,
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316pub struct ParserPrattValueCoverageSummaryV0 {
317    pub product: &'static str,
318    pub infix_operator_kinds: Vec<SyntaxKind>,
319    pub prefix_operator_kinds: Vec<SyntaxKind>,
320    pub value_expression_node_kinds: Vec<SyntaxKind>,
321    pub specialized_function_family_count: usize,
322    pub css_values_l4_math_function_count: usize,
323    pub css_color_function_count: usize,
324    pub ready_surfaces: Vec<&'static str>,
325    pub next_surfaces: Vec<&'static str>,
326}
327
328#[derive(Debug, Clone, PartialEq, Eq)]
329pub struct ParserRecursiveDescentCoverageSummaryV0 {
330    pub product: &'static str,
331    pub dialect_count: usize,
332    pub entry_point_count: usize,
333    pub selector_surface_count: usize,
334    pub at_rule_surface_count: usize,
335    pub dialect_extension_surface_count: usize,
336    pub recovery_surface_count: usize,
337    pub ready_surfaces: Vec<&'static str>,
338    pub next_surfaces: Vec<&'static str>,
339}
340
341#[derive(Debug, Clone, PartialEq, Eq)]
342struct ParserSemanticNameCandidateV0 {
343    kind: NameKind,
344    text: String,
345}
346
347#[derive(Debug, Clone, PartialEq, Eq)]
348pub struct ParsedStyleFacts {
349    pub product: &'static str,
350    pub dialect: StyleDialect,
351    pub selector_count: usize,
352    pub selectors: Vec<ParsedSelectorFact>,
353    pub variable_count: usize,
354    pub variables: Vec<ParsedVariableFact>,
355    pub sass_symbol_count: usize,
356    pub sass_symbols: Vec<ParsedSassSymbolFact>,
357    pub sass_include_count: usize,
358    pub sass_includes: Vec<ParsedSassIncludeFact>,
359    pub sass_module_edge_count: usize,
360    pub sass_module_edges: Vec<ParsedSassModuleEdgeFact>,
361    pub extend_target_count: usize,
362    pub extend_targets: Vec<ParsedExtendTargetFact>,
363    pub animation_count: usize,
364    pub animations: Vec<ParsedAnimationFact>,
365    pub css_module_value_count: usize,
366    pub css_module_values: Vec<ParsedCssModuleValueFact>,
367    pub css_module_value_import_edge_count: usize,
368    pub css_module_value_import_edges: Vec<ParsedCssModuleValueImportEdgeFact>,
369    pub css_module_value_definition_edge_count: usize,
370    pub css_module_value_definition_edges: Vec<ParsedCssModuleValueDefinitionEdgeFact>,
371    pub css_module_composes_count: usize,
372    pub css_module_composes: Vec<ParsedCssModuleComposesFact>,
373    pub css_module_composes_edge_count: usize,
374    pub css_module_composes_edges: Vec<ParsedCssModuleComposesEdgeFact>,
375    pub icss_count: usize,
376    pub icss: Vec<ParsedIcssFact>,
377    pub icss_import_edge_count: usize,
378    pub icss_import_edges: Vec<ParsedIcssImportEdgeFact>,
379    pub icss_export_edge_count: usize,
380    pub icss_export_edges: Vec<ParsedIcssExportEdgeFact>,
381    pub at_rule_count: usize,
382    pub at_rules: Vec<ParsedAtRuleFact>,
383    pub error_count: usize,
384}
385
386#[derive(Debug, Clone, PartialEq, Eq)]
387pub struct ParsedSelectorFact {
388    pub kind: ParsedSelectorFactKind,
389    pub name: String,
390    pub range: TextRange,
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
394pub enum ParsedSelectorFactKind {
395    Class,
396    Id,
397    Placeholder,
398}
399
400#[derive(Debug, Clone, PartialEq, Eq)]
401pub struct ParsedVariableFact {
402    pub kind: ParsedVariableFactKind,
403    pub name: String,
404    pub range: TextRange,
405    /// For a `CustomPropertyReference` written as `var(--x, fallback)`, records that a
406    /// top-level fallback argument is present. The reference cannot be "missing" in any
407    /// observable way — the fallback guarantees a value — so the `missingCustomProperty`
408    /// lint must skip it. `false` for declarations and fallback-less references.
409    pub has_fallback: bool,
410}
411
412#[derive(Debug, Clone, Copy, PartialEq, Eq)]
413pub enum ParsedVariableFactKind {
414    ScssDeclaration,
415    ScssReference,
416    LessDeclaration,
417    LessReference,
418    CustomPropertyDeclaration,
419    CustomPropertyReference,
420}
421
422#[derive(Debug, Clone, PartialEq, Eq)]
423pub struct ParsedSassSymbolFact {
424    pub kind: ParsedSassSymbolFactKind,
425    pub symbol_kind: &'static str,
426    pub name: String,
427    pub role: &'static str,
428    pub namespace: Option<String>,
429    pub range: TextRange,
430}
431
432#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
433pub enum ParsedSassSymbolFactKind {
434    VariableDeclaration,
435    VariableReference,
436    MixinDeclaration,
437    MixinInclude,
438    FunctionDeclaration,
439    FunctionCall,
440}
441
442#[derive(Debug, Clone, PartialEq, Eq)]
443pub struct ParsedSassIncludeFact {
444    pub name: String,
445    pub namespace: Option<String>,
446    pub params: String,
447    pub range: TextRange,
448}
449
450#[derive(Debug, Clone, PartialEq, Eq)]
451pub struct ParsedSassModuleEdgeFact {
452    pub kind: ParsedSassModuleEdgeFactKind,
453    pub source: String,
454    pub namespace_kind: Option<&'static str>,
455    pub namespace: Option<String>,
456    pub forward_prefix: Option<String>,
457    pub visibility_filter_kind: Option<&'static str>,
458    pub visibility_filter_names: Vec<String>,
459    /// RFC-0007-D1 (#44): whether this `@import` target carries a trailing media
460    /// qualifier (`@import "foo" screen`, `@import "foo" (min-width: 100px)`). Sass
461    /// keeps media-qualified imports as plain CSS (NOT deprecated). Recoverable only
462    /// in the parser, where the target's comma-peer segment is still tokenized: a
463    /// non-`Comma` significant token after the target String marks the qualifier.
464    /// Always `false` for `Use`/`Forward` edges (media qualifiers are `@import`-only).
465    pub media_qualified: bool,
466    pub range: TextRange,
467}
468
469#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
470pub enum ParsedSassModuleEdgeFactKind {
471    Use,
472    Forward,
473    Import,
474}
475
476/// RFC-0007-E1 (#45): the target of an `@extend` rule. The `ScssExtendRule` node previously
477/// parsed and then discarded its target, so an `@extend %nonexistent` / `@extend .missing`
478/// (a dart-sass hard error) went unreported. This fact captures the (simple) target selector,
479/// whether it carries the `!optional` flag (an optional extend must NOT be validated — dart-sass
480/// allows a missing optional target), and its source range for diagnostic anchoring.
481#[derive(Debug, Clone, PartialEq, Eq)]
482pub struct ParsedExtendTargetFact {
483    pub kind: ParsedExtendTargetFactKind,
484    pub name: String,
485    pub optional: bool,
486    pub range: TextRange,
487}
488
489#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
490pub enum ParsedExtendTargetFactKind {
491    Class,
492    Placeholder,
493}
494
495#[derive(Debug, Clone, PartialEq, Eq)]
496pub struct ParsedAnimationFact {
497    pub kind: ParsedAnimationFactKind,
498    pub name: String,
499    pub range: TextRange,
500}
501
502#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
503pub enum ParsedAnimationFactKind {
504    KeyframesDeclaration,
505    AnimationNameReference,
506}
507
508#[derive(Debug, Clone, PartialEq, Eq)]
509pub struct ParsedCssModuleValueFact {
510    pub kind: ParsedCssModuleValueFactKind,
511    pub name: String,
512    pub range: TextRange,
513}
514
515#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
516pub enum ParsedCssModuleValueFactKind {
517    Definition,
518    Reference,
519    ImportSource,
520}
521
522#[derive(Debug, Clone, PartialEq, Eq)]
523pub struct ParsedCssModuleValueImportEdgeFact {
524    pub remote_name: String,
525    pub local_name: String,
526    pub import_source: String,
527    pub local_range: TextRange,
528    pub remote_range: TextRange,
529    pub range: TextRange,
530}
531
532#[derive(Debug, Clone, PartialEq, Eq)]
533pub struct ParsedCssModuleValueDefinitionEdgeFact {
534    pub definition_name: String,
535    pub reference_names: Vec<String>,
536    pub range: TextRange,
537}
538
539#[derive(Debug, Clone, PartialEq, Eq)]
540pub struct ParsedCssModuleComposesFact {
541    pub kind: ParsedCssModuleComposesFactKind,
542    pub name: String,
543    pub range: TextRange,
544}
545
546#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
547pub enum ParsedCssModuleComposesFactKind {
548    Target,
549    ImportSource,
550}
551
552#[derive(Debug, Clone, PartialEq, Eq)]
553pub struct ParsedCssModuleComposesEdgeFact {
554    pub kind: ParsedCssModuleComposesEdgeKind,
555    pub owner_selector_names: Vec<String>,
556    pub target_names: Vec<String>,
557    pub import_source: Option<String>,
558    pub range: TextRange,
559}
560
561#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
562pub enum ParsedCssModuleComposesEdgeKind {
563    Local,
564    Global,
565    External,
566}
567
568#[derive(Debug, Clone, PartialEq, Eq)]
569pub struct ParsedIcssFact {
570    pub kind: ParsedIcssFactKind,
571    pub name: String,
572    pub range: TextRange,
573}
574
575#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
576pub enum ParsedIcssFactKind {
577    ExportName,
578    ImportLocalName,
579    ImportRemoteName,
580    ImportSource,
581}
582
583#[derive(Debug, Clone, PartialEq, Eq)]
584pub struct ParsedIcssImportEdgeFact {
585    pub local_name: String,
586    pub remote_name: String,
587    pub import_source: String,
588    pub range: TextRange,
589}
590
591#[derive(Debug, Clone, PartialEq, Eq)]
592pub struct ParsedIcssExportEdgeFact {
593    pub export_name: String,
594    pub reference_names: Vec<String>,
595    pub range: TextRange,
596}
597
598#[derive(Debug, Clone, PartialEq, Eq)]
599pub struct ParsedAtRuleFact {
600    pub name: String,
601    pub node_kind: Option<SyntaxKind>,
602    pub range: TextRange,
603}
604
605#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
606#[serde(rename_all = "camelCase")]
607pub struct OmenaParserStyleFactsSummaryV0 {
608    pub schema_version: &'static str,
609    pub product: &'static str,
610    pub dialect: &'static str,
611    pub class_selector_names: Vec<String>,
612    pub id_selector_names: Vec<String>,
613    pub placeholder_selector_names: Vec<String>,
614    pub keyframe_names: Vec<String>,
615    pub animation_reference_names: Vec<String>,
616    pub css_module_value_definition_names: Vec<String>,
617    pub css_module_value_reference_names: Vec<String>,
618    pub css_module_value_import_sources: Vec<String>,
619    pub css_module_value_import_edges: Vec<OmenaParserCssModuleValueImportEdgeFactV0>,
620    pub css_module_value_definition_edges: Vec<OmenaParserCssModuleValueDefinitionEdgeFactV0>,
621    pub css_module_composes_target_names: Vec<String>,
622    pub css_module_composes_import_sources: Vec<String>,
623    pub css_module_composes_edges: Vec<OmenaParserCssModuleComposesEdgeFactV0>,
624    pub icss_export_names: Vec<String>,
625    pub icss_import_local_names: Vec<String>,
626    pub icss_import_remote_names: Vec<String>,
627    pub icss_import_sources: Vec<String>,
628    pub icss_import_edges: Vec<OmenaParserIcssImportEdgeFactV0>,
629    pub icss_export_edges: Vec<OmenaParserIcssExportEdgeFactV0>,
630    pub variable_names: Vec<String>,
631    pub sass_symbol_declaration_names: Vec<String>,
632    pub sass_symbol_reference_names: Vec<String>,
633    pub sass_symbol_facts: Vec<OmenaParserSassSymbolFactV0>,
634    pub sass_symbol_resolution: OmenaParserSassSymbolResolutionV0,
635    pub sass_module_use_sources: Vec<String>,
636    pub sass_module_forward_sources: Vec<String>,
637    pub sass_module_import_sources: Vec<String>,
638    pub sass_module_edges: Vec<OmenaParserSassModuleEdgeFactV0>,
639    pub custom_property_names: Vec<String>,
640    pub custom_property_decl_names: Vec<String>,
641    pub custom_property_ref_names: Vec<String>,
642    pub at_rule_names: Vec<String>,
643    pub parser_error_count: usize,
644}
645
646#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
647#[serde(rename_all = "camelCase")]
648pub struct OmenaParserCssModuleValueImportEdgeFactV0 {
649    pub remote_name: String,
650    pub local_name: String,
651    pub import_source: String,
652}
653
654#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
655#[serde(rename_all = "camelCase")]
656pub struct OmenaParserCssModuleValueDefinitionEdgeFactV0 {
657    pub definition_name: String,
658    pub reference_names: Vec<String>,
659}
660
661#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
662#[serde(rename_all = "camelCase")]
663pub struct OmenaParserCssModuleComposesEdgeFactV0 {
664    pub kind: &'static str,
665    pub owner_selector_names: Vec<String>,
666    pub target_names: Vec<String>,
667    pub import_source: Option<String>,
668}
669
670#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
671#[serde(rename_all = "camelCase")]
672pub struct OmenaParserIcssImportEdgeFactV0 {
673    pub local_name: String,
674    pub remote_name: String,
675    pub import_source: String,
676}
677
678#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
679#[serde(rename_all = "camelCase")]
680pub struct OmenaParserIcssExportEdgeFactV0 {
681    pub export_name: String,
682    pub reference_names: Vec<String>,
683}
684
685#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
686#[serde(rename_all = "camelCase")]
687pub struct OmenaParserSassSymbolFactV0 {
688    pub kind: &'static str,
689    pub symbol_kind: &'static str,
690    pub name: String,
691    pub role: &'static str,
692    pub namespace: Option<String>,
693}
694
695#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
696#[serde(rename_all = "camelCase")]
697pub struct OmenaParserSassModuleEdgeFactV0 {
698    pub kind: &'static str,
699    pub source: String,
700    pub namespace_kind: Option<&'static str>,
701    pub namespace: Option<String>,
702    pub visibility_filter_kind: Option<&'static str>,
703    pub visibility_filter_names: Vec<String>,
704}
705
706#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
707#[serde(rename_all = "camelCase")]
708pub struct OmenaParserSassSymbolResolutionV0 {
709    pub schema_version: &'static str,
710    pub product: &'static str,
711    pub resolution_scope: &'static str,
712    pub declaration_count: usize,
713    pub reference_count: usize,
714    pub resolved_reference_count: usize,
715    pub unresolved_reference_count: usize,
716    pub edges: Vec<OmenaParserSassSymbolResolutionEdgeV0>,
717    pub capabilities: OmenaParserSassSymbolResolutionCapabilitiesV0,
718}
719
720#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
721#[serde(rename_all = "camelCase")]
722pub struct OmenaParserSassSymbolResolutionEdgeV0 {
723    pub symbol_kind: &'static str,
724    pub name: String,
725    pub namespace: Option<String>,
726    pub reference_kind: &'static str,
727    pub reference_role: &'static str,
728    pub reference_source_order: usize,
729    pub declaration_kind: Option<&'static str>,
730    pub declaration_source_order: Option<usize>,
731    pub status: &'static str,
732}
733
734#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
735#[serde(rename_all = "camelCase")]
736pub struct OmenaParserSassSymbolResolutionCapabilitiesV0 {
737    pub same_file_lexical_resolution_ready: bool,
738    pub declaration_before_reference_ready: bool,
739    pub unresolved_reference_reporting_ready: bool,
740    pub cross_file_module_resolution_ready: bool,
741}
742
743#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
744#[serde(rename_all = "camelCase")]
745pub struct OmenaParserLexSummaryV0 {
746    pub schema_version: &'static str,
747    pub product: &'static str,
748    pub dialect: &'static str,
749    pub tokens: Vec<OmenaParserLexTokenV0>,
750    pub parser_error_count: usize,
751}
752
753#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
754#[serde(rename_all = "camelCase")]
755pub struct OmenaParserLexTokenV0 {
756    pub kind: String,
757    pub text: String,
758    pub start: usize,
759    pub end: usize,
760}
761
762#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
763#[serde(rename_all = "camelCase")]
764pub struct OmenaParserParityLiteSummaryV0 {
765    pub schema_version: &'static str,
766    pub language: &'static str,
767    pub selector_names: Vec<String>,
768    pub keyframes_names: Vec<String>,
769    pub value_decl_names: Vec<String>,
770    pub diagnostic_count: usize,
771    pub rule_count: usize,
772    pub declaration_count: usize,
773    pub grouped_selector_count: usize,
774    pub max_nesting_depth: usize,
775    pub at_rule_kind_counts: OmenaParserAtRuleKindCountsV0,
776    pub declaration_kind_counts: OmenaParserDeclarationKindCountsV0,
777}
778
779#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
780#[serde(rename_all = "camelCase")]
781pub struct OmenaParserAtRuleKindCountsV0 {
782    pub media: usize,
783    pub supports: usize,
784    pub layer: usize,
785    pub keyframes: usize,
786    pub value: usize,
787    pub at_root: usize,
788    pub generic: usize,
789}
790
791#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
792#[serde(rename_all = "camelCase")]
793pub struct OmenaParserDeclarationKindCountsV0 {
794    pub composes: usize,
795    pub animation: usize,
796    pub animation_name: usize,
797    pub generic: usize,
798}
799
800#[derive(Debug, Clone, PartialEq, Eq)]
801pub struct ParsedCst {
802    root: SyntaxNode<SyntaxKind>,
803}
804
805impl ParsedCst {
806    pub fn new(root: SyntaxNode<SyntaxKind>) -> Self {
807        Self { root }
808    }
809
810    pub fn root(&self) -> &SyntaxNode<SyntaxKind> {
811        &self.root
812    }
813
814    pub fn stylesheet(&self) -> Option<StylesheetCstNode> {
815        self.first_node(StylesheetCstNode::cast)
816    }
817
818    pub fn rules(&self) -> Vec<RuleCstNode> {
819        self.nodes(RuleCstNode::cast)
820    }
821
822    pub fn selectors(&self) -> Vec<SelectorCstNode> {
823        self.nodes(SelectorCstNode::cast)
824    }
825
826    pub fn declarations(&self) -> Vec<DeclarationCstNode> {
827        self.nodes(DeclarationCstNode::cast)
828    }
829
830    pub fn declaration_lists(&self) -> Vec<DeclarationListCstNode> {
831        self.nodes(DeclarationListCstNode::cast)
832    }
833
834    pub fn values(&self) -> Vec<ValueCstNode> {
835        self.nodes(ValueCstNode::cast)
836    }
837
838    pub fn component_values(&self) -> Vec<ComponentValueCstNode> {
839        self.nodes(ComponentValueCstNode::cast)
840    }
841
842    pub fn simple_blocks(&self) -> Vec<SimpleBlockCstNode> {
843        self.nodes(SimpleBlockCstNode::cast)
844    }
845
846    pub fn component_value_lists(&self) -> Vec<ComponentValueListCstNode> {
847        self.nodes(ComponentValueListCstNode::cast)
848    }
849
850    pub fn comma_separated_component_value_lists(
851        &self,
852    ) -> Vec<CommaSeparatedComponentValueListCstNode> {
853        self.nodes(CommaSeparatedComponentValueListCstNode::cast)
854    }
855
856    pub fn custom_property_values(&self) -> Vec<CustomPropertyValueCstNode> {
857        self.nodes(CustomPropertyValueCstNode::cast)
858    }
859
860    pub fn at_rules(&self) -> Vec<AtRuleCstNode> {
861        self.nodes(AtRuleCstNode::cast)
862    }
863
864    pub fn bogus_nodes(&self) -> Vec<BogusCstNode> {
865        self.nodes(BogusCstNode::cast)
866    }
867
868    pub fn has_bogus_nodes(&self) -> bool {
869        self.first_node(BogusCstNode::cast).is_some()
870    }
871
872    fn first_node<T>(&self, cast: impl Fn(SyntaxNode<SyntaxKind>) -> Option<T>) -> Option<T> {
873        let mut nodes = Vec::new();
874        collect_typed_nodes(&self.root, &cast, &mut nodes);
875        nodes.into_iter().next()
876    }
877
878    fn nodes<T>(&self, cast: impl Fn(SyntaxNode<SyntaxKind>) -> Option<T>) -> Vec<T> {
879        let mut nodes = Vec::new();
880        collect_typed_nodes(&self.root, &cast, &mut nodes);
881        nodes
882    }
883}
884
885pub trait TypedCstNode: Sized {
886    fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self>;
887    fn syntax(&self) -> &SyntaxNode<SyntaxKind>;
888
889    fn kind(&self) -> SyntaxKind {
890        self.syntax().kind()
891    }
892
893    fn text_range(&self) -> TextRange {
894        self.syntax().text_range()
895    }
896
897    fn into_syntax(self) -> SyntaxNode<SyntaxKind>;
898}
899
900macro_rules! typed_cst_node {
901    ($name:ident, $kind:expr) => {
902        #[derive(Debug, Clone, PartialEq, Eq)]
903        pub struct $name {
904            syntax: SyntaxNode<SyntaxKind>,
905        }
906
907        impl $name {
908            pub const KIND: SyntaxKind = $kind;
909        }
910
911        impl TypedCstNode for $name {
912            fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self> {
913                (syntax.kind() == Self::KIND).then_some(Self { syntax })
914            }
915
916            fn syntax(&self) -> &SyntaxNode<SyntaxKind> {
917                &self.syntax
918            }
919
920            fn into_syntax(self) -> SyntaxNode<SyntaxKind> {
921                self.syntax
922            }
923        }
924    };
925}
926
927typed_cst_node!(StylesheetCstNode, SyntaxKind::Stylesheet);
928typed_cst_node!(RuleCstNode, SyntaxKind::Rule);
929typed_cst_node!(SelectorCstNode, SyntaxKind::Selector);
930typed_cst_node!(DeclarationCstNode, SyntaxKind::Declaration);
931typed_cst_node!(DeclarationListCstNode, SyntaxKind::DeclarationList);
932typed_cst_node!(ValueCstNode, SyntaxKind::Value);
933typed_cst_node!(ComponentValueCstNode, SyntaxKind::ComponentValue);
934typed_cst_node!(SimpleBlockCstNode, SyntaxKind::SimpleBlock);
935typed_cst_node!(ComponentValueListCstNode, SyntaxKind::ComponentValueList);
936typed_cst_node!(
937    CommaSeparatedComponentValueListCstNode,
938    SyntaxKind::CommaSeparatedComponentValueList
939);
940typed_cst_node!(CustomPropertyValueCstNode, SyntaxKind::CustomPropertyValue);
941
942#[derive(Debug, Clone, PartialEq, Eq)]
943pub struct AtRuleCstNode {
944    syntax: SyntaxNode<SyntaxKind>,
945}
946
947#[derive(Debug, Clone, PartialEq, Eq)]
948pub struct BogusCstNode {
949    syntax: SyntaxNode<SyntaxKind>,
950}
951
952impl TypedCstNode for AtRuleCstNode {
953    fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self> {
954        is_at_rule_node_kind(syntax.kind()).then_some(Self { syntax })
955    }
956
957    fn syntax(&self) -> &SyntaxNode<SyntaxKind> {
958        &self.syntax
959    }
960
961    fn into_syntax(self) -> SyntaxNode<SyntaxKind> {
962        self.syntax
963    }
964}
965
966impl TypedCstNode for BogusCstNode {
967    fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self> {
968        syntax.kind().is_bogus().then_some(Self { syntax })
969    }
970
971    fn syntax(&self) -> &SyntaxNode<SyntaxKind> {
972        &self.syntax
973    }
974
975    fn into_syntax(self) -> SyntaxNode<SyntaxKind> {
976        self.syntax
977    }
978}
979
980pub fn is_at_rule_node_kind(kind: SyntaxKind) -> bool {
981    matches!(
982        kind,
983        SyntaxKind::AtRule
984            | SyntaxKind::MediaRule
985            | SyntaxKind::SupportsRule
986            | SyntaxKind::ContainerRule
987            | SyntaxKind::LayerRule
988            | SyntaxKind::ScopeRule
989            | SyntaxKind::KeyframesRule
990            | SyntaxKind::FontFaceRule
991            | SyntaxKind::PageRule
992            | SyntaxKind::NamespaceRule
993            | SyntaxKind::ImportRule
994            | SyntaxKind::CharsetRule
995            | SyntaxKind::PropertyRule
996            | SyntaxKind::StartingStyleRule
997            | SyntaxKind::PageMarginRule
998            | SyntaxKind::WhenRule
999            | SyntaxKind::ElseRule
1000            | SyntaxKind::CounterStyleRule
1001            | SyntaxKind::FontPaletteValuesRule
1002            | SyntaxKind::ColorProfileRule
1003            | SyntaxKind::PositionTryRule
1004            | SyntaxKind::FontFeatureValuesRule
1005            | SyntaxKind::FontFeatureValuesStylisticRule
1006            | SyntaxKind::FontFeatureValuesStylesetRule
1007            | SyntaxKind::FontFeatureValuesCharacterVariantRule
1008            | SyntaxKind::FontFeatureValuesSwashRule
1009            | SyntaxKind::FontFeatureValuesOrnamentsRule
1010            | SyntaxKind::FontFeatureValuesAnnotationRule
1011            | SyntaxKind::FontFeatureValuesHistoricalFormsRule
1012            | SyntaxKind::ViewTransitionRule
1013            | SyntaxKind::NestRule
1014            | SyntaxKind::CustomMediaRule
1015            | SyntaxKind::ScssUseRule
1016            | SyntaxKind::ScssForwardRule
1017            | SyntaxKind::ScssMixinDeclaration
1018            | SyntaxKind::ScssIncludeRule
1019            | SyntaxKind::ScssFunctionDeclaration
1020            | SyntaxKind::ScssReturnRule
1021            | SyntaxKind::ScssAtRootRule
1022            | SyntaxKind::ScssErrorRule
1023            | SyntaxKind::ScssWarnRule
1024            | SyntaxKind::ScssDebugRule
1025            | SyntaxKind::ScssContentRule
1026    )
1027}
1028
1029fn collect_typed_nodes<T>(
1030    node: &SyntaxNode<SyntaxKind>,
1031    cast: &impl Fn(SyntaxNode<SyntaxKind>) -> Option<T>,
1032    nodes: &mut Vec<T>,
1033) {
1034    if let Some(typed) = cast(node.clone()) {
1035        nodes.push(typed);
1036    }
1037    for child in node.children() {
1038        collect_typed_nodes(child, cast, nodes);
1039    }
1040}
1041
1042#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1043pub struct TokenSet {
1044    kinds: &'static [SyntaxKind],
1045}
1046
1047impl TokenSet {
1048    pub const fn new(kinds: &'static [SyntaxKind]) -> Self {
1049        Self { kinds }
1050    }
1051
1052    pub fn contains(self, kind: SyntaxKind) -> bool {
1053        self.kinds.contains(&kind)
1054    }
1055
1056    pub fn len(self) -> usize {
1057        self.kinds.len()
1058    }
1059
1060    pub fn is_empty(self) -> bool {
1061        self.kinds.is_empty()
1062    }
1063}
1064
1065pub const RECOVERY_TOP: TokenSet = TokenSet::new(&[
1066    SyntaxKind::AtKeyword,
1067    SyntaxKind::Dot,
1068    SyntaxKind::Hash,
1069    SyntaxKind::RightBrace,
1070    SyntaxKind::Semicolon,
1071]);
1072
1073pub const RECOVERY_DECLARATION: TokenSet =
1074    TokenSet::new(&[SyntaxKind::Semicolon, SyntaxKind::RightBrace]);
1075
1076pub const RECOVERY_SELECTOR: TokenSet = TokenSet::new(&[
1077    SyntaxKind::Comma,
1078    SyntaxKind::LeftBrace,
1079    SyntaxKind::RightBrace,
1080]);
1081
1082pub trait DialectExtension {
1083    fn dialect(&self) -> StyleDialect;
1084
1085    fn classify_variable_token(&self, text: &str) -> Option<SyntaxKind> {
1086        match self.dialect() {
1087            StyleDialect::Css => None,
1088            StyleDialect::Scss | StyleDialect::Sass if text.starts_with('$') => {
1089                Some(SyntaxKind::ScssVariable)
1090            }
1091            StyleDialect::Less if text.starts_with('@') => Some(SyntaxKind::LessVariable),
1092            StyleDialect::Scss | StyleDialect::Sass | StyleDialect::Less => None,
1093        }
1094    }
1095}
1096
1097#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1098pub struct BuiltinDialectExtension {
1099    dialect: StyleDialect,
1100}
1101
1102impl BuiltinDialectExtension {
1103    pub const fn new(dialect: StyleDialect) -> Self {
1104        Self { dialect }
1105    }
1106}
1107
1108impl DialectExtension for BuiltinDialectExtension {
1109    fn dialect(&self) -> StyleDialect {
1110        self.dialect
1111    }
1112}
1113
1114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1115struct Token<'text> {
1116    kind: SyntaxKind,
1117    text: &'text str,
1118    range: TextRange,
1119}
1120
1121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1122struct AtRuleSpec {
1123    node_kind: SyntaxKind,
1124    block_kind: AtRuleBlockKind,
1125}
1126
1127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1128enum AtRuleBlockKind {
1129    GroupRuleList,
1130    DeclarationList,
1131    Keyframes,
1132    Raw,
1133}
1134
1135pub fn parse(text: &str, dialect: StyleDialect) -> ParseResult {
1136    parse_entry_point(text, dialect, ParseEntryPoint::Stylesheet)
1137}
1138
1139pub fn parse_entry_point(
1140    text: &str,
1141    dialect: StyleDialect,
1142    entry_point: ParseEntryPoint,
1143) -> ParseResult {
1144    let extension = BuiltinDialectExtension::new(dialect);
1145    parse_entry_point_with_extension(text, &extension, entry_point)
1146}
1147
1148pub fn lex(text: &str, dialect: StyleDialect) -> LexResult {
1149    let extension = BuiltinDialectExtension::new(dialect);
1150    lex_with_extension(text, &extension)
1151}
1152
1153pub fn lex_with_extension(text: &str, extension: &impl DialectExtension) -> LexResult {
1154    let (tokens, errors) = tokenize(text, extension);
1155    LexResult {
1156        tokens: tokens
1157            .into_iter()
1158            .map(|token| LexedToken {
1159                kind: token.kind,
1160                range: token.range,
1161                text: public_token_text(token.text),
1162            })
1163            .collect(),
1164        errors,
1165        dialect: extension.dialect(),
1166    }
1167}
1168
1169pub fn parse_with_extension(text: &str, extension: &impl DialectExtension) -> ParseResult {
1170    parse_entry_point_with_extension(text, extension, ParseEntryPoint::Stylesheet)
1171}
1172
1173pub fn parse_entry_point_with_extension(
1174    text: &str,
1175    extension: &impl DialectExtension,
1176    entry_point: ParseEntryPoint,
1177) -> ParseResult {
1178    let (tokens, errors) = tokenize(text, extension);
1179    let token_count = tokens.len();
1180    let mut parser = Parser::new(tokens, errors, extension.dialect());
1181    let (green, interner) = parser.parse_entry_point(entry_point);
1182
1183    ParseResult {
1184        green,
1185        interner,
1186        errors: parser.into_errors(),
1187        token_count,
1188        dialect: extension.dialect(),
1189    }
1190}
1191
1192pub fn collect_style_facts(text: &str, dialect: StyleDialect) -> ParsedStyleFacts {
1193    let extension = BuiltinDialectExtension::new(dialect);
1194    collect_style_facts_with_extension(text, &extension)
1195}
1196
1197pub fn summarize_omena_parser_style_facts(
1198    style_source: &str,
1199    dialect: StyleDialect,
1200) -> OmenaParserStyleFactsSummaryV0 {
1201    let facts = collect_style_facts(style_source, dialect);
1202    let sass_symbol_resolution = summarize_omena_parser_sass_symbol_resolution(&facts.sass_symbols);
1203    let mut class_selector_names = Vec::new();
1204    let mut id_selector_names = Vec::new();
1205    let mut placeholder_selector_names = Vec::new();
1206    let mut keyframe_names = Vec::new();
1207    let mut animation_reference_names = Vec::new();
1208    let mut css_module_value_definition_names = BTreeSet::new();
1209    let mut css_module_value_reference_names = BTreeSet::new();
1210    let mut css_module_value_import_sources = BTreeSet::new();
1211    let mut css_module_composes_target_names = BTreeSet::new();
1212    let mut css_module_composes_import_sources = BTreeSet::new();
1213    let mut icss_export_names = BTreeSet::new();
1214    let mut icss_import_local_names = BTreeSet::new();
1215    let mut icss_import_remote_names = BTreeSet::new();
1216    let mut icss_import_sources = BTreeSet::new();
1217    let mut variable_names = BTreeSet::new();
1218    let mut sass_symbol_declaration_names = BTreeSet::new();
1219    let mut sass_symbol_reference_names = BTreeSet::new();
1220    let mut sass_module_use_sources = BTreeSet::new();
1221    let mut sass_module_forward_sources = BTreeSet::new();
1222    let mut sass_module_import_sources = BTreeSet::new();
1223    let mut custom_property_names = BTreeSet::new();
1224    let mut custom_property_decl_names = BTreeSet::new();
1225    let mut custom_property_ref_names = BTreeSet::new();
1226
1227    for selector in facts.selectors {
1228        match selector.kind {
1229            ParsedSelectorFactKind::Class => class_selector_names.push(selector.name),
1230            ParsedSelectorFactKind::Id => id_selector_names.push(selector.name),
1231            ParsedSelectorFactKind::Placeholder => placeholder_selector_names.push(selector.name),
1232        }
1233    }
1234
1235    for variable in facts.variables {
1236        match variable.kind {
1237            ParsedVariableFactKind::ScssDeclaration
1238            | ParsedVariableFactKind::ScssReference
1239            | ParsedVariableFactKind::LessDeclaration
1240            | ParsedVariableFactKind::LessReference => {
1241                variable_names.insert(variable.name);
1242            }
1243            ParsedVariableFactKind::CustomPropertyDeclaration
1244            | ParsedVariableFactKind::CustomPropertyReference => {
1245                custom_property_names.insert(variable.name.clone());
1246                match variable.kind {
1247                    ParsedVariableFactKind::CustomPropertyDeclaration => {
1248                        custom_property_decl_names.insert(variable.name);
1249                    }
1250                    ParsedVariableFactKind::CustomPropertyReference => {
1251                        custom_property_ref_names.insert(variable.name);
1252                    }
1253                    _ => {}
1254                }
1255            }
1256        }
1257    }
1258
1259    for symbol in &facts.sass_symbols {
1260        match symbol.role {
1261            "declaration" => {
1262                sass_symbol_declaration_names.insert(symbol.name.clone());
1263            }
1264            _ => {
1265                sass_symbol_reference_names.insert(symbol.name.clone());
1266            }
1267        }
1268    }
1269
1270    for edge in &facts.sass_module_edges {
1271        match edge.kind {
1272            ParsedSassModuleEdgeFactKind::Use => {
1273                sass_module_use_sources.insert(edge.source.clone());
1274            }
1275            ParsedSassModuleEdgeFactKind::Forward => {
1276                sass_module_forward_sources.insert(edge.source.clone());
1277            }
1278            ParsedSassModuleEdgeFactKind::Import => {
1279                sass_module_import_sources.insert(edge.source.clone());
1280            }
1281        }
1282    }
1283
1284    for animation in facts.animations {
1285        match animation.kind {
1286            ParsedAnimationFactKind::KeyframesDeclaration => keyframe_names.push(animation.name),
1287            ParsedAnimationFactKind::AnimationNameReference => {
1288                animation_reference_names.push(animation.name);
1289            }
1290        }
1291    }
1292
1293    for value in facts.css_module_values {
1294        match value.kind {
1295            ParsedCssModuleValueFactKind::Definition => {
1296                css_module_value_definition_names.insert(value.name);
1297            }
1298            ParsedCssModuleValueFactKind::Reference => {
1299                css_module_value_reference_names.insert(value.name);
1300            }
1301            ParsedCssModuleValueFactKind::ImportSource => {
1302                css_module_value_import_sources.insert(value.name);
1303            }
1304        }
1305    }
1306
1307    for composes in facts.css_module_composes {
1308        match composes.kind {
1309            ParsedCssModuleComposesFactKind::Target => {
1310                css_module_composes_target_names.insert(composes.name);
1311            }
1312            ParsedCssModuleComposesFactKind::ImportSource => {
1313                css_module_composes_import_sources.insert(composes.name);
1314            }
1315        }
1316    }
1317
1318    for icss in facts.icss {
1319        match icss.kind {
1320            ParsedIcssFactKind::ExportName => {
1321                icss_export_names.insert(icss.name);
1322            }
1323            ParsedIcssFactKind::ImportLocalName => {
1324                icss_import_local_names.insert(icss.name);
1325            }
1326            ParsedIcssFactKind::ImportRemoteName => {
1327                icss_import_remote_names.insert(icss.name);
1328            }
1329            ParsedIcssFactKind::ImportSource => {
1330                icss_import_sources.insert(icss.name);
1331            }
1332        }
1333    }
1334
1335    OmenaParserStyleFactsSummaryV0 {
1336        schema_version: "0",
1337        product: "omena-parser.style-facts",
1338        dialect: style_dialect_label(dialect),
1339        class_selector_names,
1340        id_selector_names,
1341        placeholder_selector_names,
1342        keyframe_names,
1343        animation_reference_names,
1344        css_module_value_definition_names: css_module_value_definition_names.into_iter().collect(),
1345        css_module_value_reference_names: css_module_value_reference_names.into_iter().collect(),
1346        css_module_value_import_sources: css_module_value_import_sources.into_iter().collect(),
1347        css_module_value_import_edges: facts
1348            .css_module_value_import_edges
1349            .into_iter()
1350            .map(|edge| OmenaParserCssModuleValueImportEdgeFactV0 {
1351                remote_name: edge.remote_name,
1352                local_name: edge.local_name,
1353                import_source: edge.import_source,
1354            })
1355            .collect(),
1356        css_module_value_definition_edges: facts
1357            .css_module_value_definition_edges
1358            .into_iter()
1359            .map(|edge| OmenaParserCssModuleValueDefinitionEdgeFactV0 {
1360                definition_name: edge.definition_name,
1361                reference_names: edge.reference_names,
1362            })
1363            .collect(),
1364        css_module_composes_target_names: css_module_composes_target_names.into_iter().collect(),
1365        css_module_composes_import_sources: css_module_composes_import_sources
1366            .into_iter()
1367            .collect(),
1368        css_module_composes_edges: facts
1369            .css_module_composes_edges
1370            .into_iter()
1371            .map(|edge| OmenaParserCssModuleComposesEdgeFactV0 {
1372                kind: css_module_composes_edge_kind_label(edge.kind),
1373                owner_selector_names: edge.owner_selector_names,
1374                target_names: edge.target_names,
1375                import_source: edge.import_source,
1376            })
1377            .collect(),
1378        icss_export_names: icss_export_names.into_iter().collect(),
1379        icss_import_local_names: icss_import_local_names.into_iter().collect(),
1380        icss_import_remote_names: icss_import_remote_names.into_iter().collect(),
1381        icss_import_sources: icss_import_sources.into_iter().collect(),
1382        icss_import_edges: facts
1383            .icss_import_edges
1384            .into_iter()
1385            .map(|edge| OmenaParserIcssImportEdgeFactV0 {
1386                local_name: edge.local_name,
1387                remote_name: edge.remote_name,
1388                import_source: edge.import_source,
1389            })
1390            .collect(),
1391        icss_export_edges: facts
1392            .icss_export_edges
1393            .into_iter()
1394            .map(|edge| OmenaParserIcssExportEdgeFactV0 {
1395                export_name: edge.export_name,
1396                reference_names: edge.reference_names,
1397            })
1398            .collect(),
1399        variable_names: variable_names.into_iter().collect(),
1400        sass_symbol_declaration_names: sass_symbol_declaration_names.into_iter().collect(),
1401        sass_symbol_reference_names: sass_symbol_reference_names.into_iter().collect(),
1402        sass_symbol_facts: facts
1403            .sass_symbols
1404            .into_iter()
1405            .map(|symbol| OmenaParserSassSymbolFactV0 {
1406                kind: sass_symbol_fact_kind_label(symbol.kind),
1407                symbol_kind: symbol.symbol_kind,
1408                name: symbol.name,
1409                role: symbol.role,
1410                namespace: symbol.namespace,
1411            })
1412            .collect(),
1413        sass_symbol_resolution,
1414        sass_module_use_sources: sass_module_use_sources.into_iter().collect(),
1415        sass_module_forward_sources: sass_module_forward_sources.into_iter().collect(),
1416        sass_module_import_sources: sass_module_import_sources.into_iter().collect(),
1417        sass_module_edges: facts
1418            .sass_module_edges
1419            .into_iter()
1420            .map(|edge| OmenaParserSassModuleEdgeFactV0 {
1421                kind: sass_module_edge_fact_kind_label(edge.kind),
1422                source: edge.source,
1423                namespace_kind: edge.namespace_kind,
1424                namespace: edge.namespace,
1425                visibility_filter_kind: edge.visibility_filter_kind,
1426                visibility_filter_names: edge.visibility_filter_names,
1427            })
1428            .collect(),
1429        custom_property_names: custom_property_names.into_iter().collect(),
1430        custom_property_decl_names: custom_property_decl_names.into_iter().collect(),
1431        custom_property_ref_names: custom_property_ref_names.into_iter().collect(),
1432        at_rule_names: facts
1433            .at_rules
1434            .into_iter()
1435            .map(|at_rule| at_rule.name)
1436            .collect(),
1437        parser_error_count: facts.error_count,
1438    }
1439}
1440
1441pub fn summarize_omena_parser_lex(source: &str, dialect: StyleDialect) -> OmenaParserLexSummaryV0 {
1442    let result = lex(source, dialect);
1443    OmenaParserLexSummaryV0 {
1444        schema_version: "0",
1445        product: "omena-parser.lex-result",
1446        dialect: style_dialect_label(result.dialect()),
1447        tokens: result
1448            .tokens()
1449            .iter()
1450            .map(|token| OmenaParserLexTokenV0 {
1451                kind: format!("{:?}", token.kind),
1452                text: token.text.clone(),
1453                start: token.range.start().into(),
1454                end: token.range.end().into(),
1455            })
1456            .collect(),
1457        parser_error_count: result.errors().len(),
1458    }
1459}
1460
1461pub fn summarize_omena_parser_parity_lite(
1462    source: &str,
1463    dialect: StyleDialect,
1464) -> OmenaParserParityLiteSummaryV0 {
1465    let facts = collect_style_facts(source, dialect);
1466    let result = parse(source, dialect);
1467    let (tokens, _) = tokenize(source, &BuiltinDialectExtension::new(dialect));
1468    let mut structural = ParserStructuralSummary::default();
1469    summarize_parser_structural_range(&tokens, 0, tokens.len(), 0, &mut structural);
1470    let mut selector_names = collect_parity_lite_selector_names_from_tokens(&tokens);
1471    selector_names.sort();
1472
1473    OmenaParserParityLiteSummaryV0 {
1474        schema_version: "0",
1475        language: style_dialect_label(dialect),
1476        selector_names,
1477        keyframes_names: sorted_unique(
1478            facts
1479                .animations
1480                .iter()
1481                .filter(|animation| animation.kind == ParsedAnimationFactKind::KeyframesDeclaration)
1482                .map(|animation| animation.name.clone()),
1483        ),
1484        value_decl_names: sorted_unique(
1485            facts
1486                .css_module_values
1487                .iter()
1488                .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
1489                .map(|value| value.name.clone()),
1490        ),
1491        diagnostic_count: result.errors().len(),
1492        rule_count: structural.rule_count,
1493        declaration_count: structural.declaration_count,
1494        grouped_selector_count: structural.grouped_selector_count,
1495        max_nesting_depth: structural.max_nesting_depth,
1496        at_rule_kind_counts: structural.at_rule_kind_counts,
1497        declaration_kind_counts: structural.declaration_kind_counts,
1498    }
1499}
1500
1501fn style_dialect_label(dialect: StyleDialect) -> &'static str {
1502    match dialect {
1503        StyleDialect::Css => "css",
1504        StyleDialect::Scss => "scss",
1505        StyleDialect::Sass => "sass",
1506        StyleDialect::Less => "less",
1507    }
1508}
1509
1510#[derive(Default)]
1511struct ParserStructuralSummary {
1512    rule_count: usize,
1513    declaration_count: usize,
1514    grouped_selector_count: usize,
1515    max_nesting_depth: usize,
1516    at_rule_kind_counts: OmenaParserAtRuleKindCountsV0,
1517    declaration_kind_counts: OmenaParserDeclarationKindCountsV0,
1518}
1519
1520fn summarize_parser_structural_range(
1521    tokens: &[Token<'_>],
1522    start: usize,
1523    end: usize,
1524    depth: usize,
1525    summary: &mut ParserStructuralSummary,
1526) {
1527    let mut index = start;
1528    while index < end {
1529        index = skip_trivia_tokens(tokens, index, end);
1530        if index >= end {
1531            break;
1532        }
1533
1534        if tokens[index].kind == SyntaxKind::AtKeyword {
1535            increment_omena_parser_at_rule_kind_count(
1536                &mut summary.at_rule_kind_counts,
1537                classify_omena_parser_at_rule_kind(tokens[index].text),
1538            );
1539            let next_depth = depth + 1;
1540            summary.max_nesting_depth = summary.max_nesting_depth.max(next_depth);
1541            if let Some((open, close)) = find_block_after_header(tokens, index, end) {
1542                summarize_parser_structural_range(tokens, open + 1, close, next_depth, summary);
1543                index = close + 1;
1544            } else {
1545                index = skip_statement(tokens, index, end);
1546            }
1547            continue;
1548        }
1549
1550        let statement_end = css_module_value_statement_end(tokens, index);
1551        if is_root_less_variable_statement(tokens, index, statement_end.min(end), depth) {
1552            increment_omena_parser_at_rule_kind_count(
1553                &mut summary.at_rule_kind_counts,
1554                keyof_omena_parser_at_rule_kind_counts::Kind::Generic,
1555            );
1556            if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
1557                break;
1558            }
1559            index = statement_end + 1;
1560            continue;
1561        }
1562
1563        if statement_end < end && tokens[statement_end].kind == SyntaxKind::LeftBrace {
1564            summary.rule_count += 1;
1565            let next_depth = depth + 1;
1566            summary.max_nesting_depth = summary.max_nesting_depth.max(next_depth);
1567            let group_count = count_omena_parser_selector_groups(tokens, index, statement_end);
1568            if group_count > 1 {
1569                summary.grouped_selector_count += group_count;
1570            }
1571            if let Some(close) = matching_right_brace(tokens, statement_end, end) {
1572                summarize_parser_structural_range(
1573                    tokens,
1574                    statement_end + 1,
1575                    close,
1576                    next_depth,
1577                    summary,
1578                );
1579                index = close + 1;
1580            } else {
1581                index = statement_end + 1;
1582            }
1583            continue;
1584        }
1585
1586        if let Some(colon_index) = declaration_colon_index(tokens, index, statement_end.min(end)) {
1587            summary.declaration_count += 1;
1588            let property = previous_non_trivia_token_index(tokens, colon_index, index)
1589                .map(|property| tokens[property].text)
1590                .unwrap_or_default();
1591            increment_omena_parser_declaration_kind_count(
1592                &mut summary.declaration_kind_counts,
1593                classify_omena_parser_declaration_kind(property),
1594            );
1595        }
1596
1597        if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
1598            break;
1599        }
1600        index = statement_end + 1;
1601    }
1602}
1603
1604fn is_root_less_variable_statement(
1605    tokens: &[Token<'_>],
1606    start: usize,
1607    end: usize,
1608    depth: usize,
1609) -> bool {
1610    if depth != 0 {
1611        return false;
1612    }
1613    let Some(first) = next_non_trivia_token_index_until(tokens, start, end) else {
1614        return false;
1615    };
1616    tokens[first].kind == SyntaxKind::LessVariable
1617        && declaration_colon_index(tokens, first, end).is_some()
1618}
1619
1620fn count_omena_parser_selector_groups(tokens: &[Token<'_>], start: usize, end: usize) -> usize {
1621    split_selector_groups(tokens, start, end)
1622        .into_iter()
1623        .filter(|(group_start, group_end)| {
1624            *group_start < *group_end
1625                && next_non_trivia_token_index_until(tokens, *group_start, *group_end).is_some()
1626        })
1627        .count()
1628}
1629
1630fn collect_parity_lite_selector_names_from_tokens(tokens: &[Token<'_>]) -> Vec<String> {
1631    let mut names = Vec::new();
1632    collect_parity_lite_selector_names_in_range(tokens, 0, tokens.len(), &[], None, &mut names);
1633    names
1634}
1635
1636fn collect_parity_lite_selector_names_in_range(
1637    tokens: &[Token<'_>],
1638    start: usize,
1639    end: usize,
1640    parent_branches: &[SelectorBranch],
1641    css_module_scope: Option<&'static str>,
1642    names: &mut Vec<String>,
1643) {
1644    let mut index = start;
1645    while index < end {
1646        index = skip_trivia_tokens(tokens, index, end);
1647        if index >= end {
1648            break;
1649        }
1650
1651        if tokens[index].kind == SyntaxKind::AtKeyword {
1652            let block = find_block_after_header(tokens, index, end);
1653            if let Some((open, close)) = block {
1654                if tokens[index].text == "@nest" {
1655                    if css_module_scope == Some("global") {
1656                        collect_parity_lite_selector_names_in_range(
1657                            tokens,
1658                            open + 1,
1659                            close,
1660                            &[],
1661                            css_module_scope,
1662                            names,
1663                        );
1664                    } else {
1665                        let branches =
1666                            resolve_selector_header(tokens, index + 1, open, parent_branches);
1667                        names.extend(branches.iter().map(|branch| branch.name.clone()));
1668                        collect_grouped_ampersand_compound_selector_duplicates(
1669                            tokens,
1670                            index + 1,
1671                            open,
1672                            parent_branches.len(),
1673                            names,
1674                        );
1675                        collect_parity_lite_selector_names_in_range(
1676                            tokens,
1677                            open + 1,
1678                            close,
1679                            &branches,
1680                            css_module_scope,
1681                            names,
1682                        );
1683                    }
1684                } else if style_wrapper_at_rule(tokens[index].text) {
1685                    collect_parity_lite_selector_names_in_range(
1686                        tokens,
1687                        open + 1,
1688                        close,
1689                        parent_branches,
1690                        css_module_scope,
1691                        names,
1692                    );
1693                }
1694                index = close + 1;
1695            } else {
1696                index = skip_statement(tokens, index, end);
1697            }
1698            continue;
1699        }
1700
1701        let Some((open, close)) = find_block_after_header(tokens, index, end) else {
1702            index = skip_statement(tokens, index, end);
1703            continue;
1704        };
1705
1706        let effective_scope = css_module_scope
1707            .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
1708        if effective_scope == Some("global") {
1709            collect_parity_lite_selector_names_in_range(
1710                tokens,
1711                open + 1,
1712                close,
1713                &[],
1714                effective_scope,
1715                names,
1716            );
1717        } else {
1718            let branches = resolve_selector_header(tokens, index, open, parent_branches);
1719            names.extend(branches.iter().map(|branch| branch.name.clone()));
1720            collect_grouped_ampersand_compound_selector_duplicates(
1721                tokens,
1722                index,
1723                open,
1724                parent_branches.len(),
1725                names,
1726            );
1727            collect_parity_lite_selector_names_in_range(
1728                tokens,
1729                open + 1,
1730                close,
1731                &branches,
1732                effective_scope,
1733                names,
1734            );
1735        }
1736        index = close + 1;
1737    }
1738}
1739
1740fn collect_grouped_ampersand_compound_selector_duplicates(
1741    tokens: &[Token<'_>],
1742    start: usize,
1743    end: usize,
1744    parent_branch_count: usize,
1745    names: &mut Vec<String>,
1746) {
1747    if parent_branch_count <= 1 || !header_contains_ampersand(tokens, start, end) {
1748        return;
1749    }
1750    for (name, _) in collect_class_selector_names_from_header(tokens, start, end) {
1751        names.extend(std::iter::repeat_n(name, parent_branch_count - 1));
1752    }
1753}
1754
1755fn header_contains_ampersand(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
1756    tokens[start..end]
1757        .iter()
1758        .any(|token| token.kind == SyntaxKind::Ampersand)
1759}
1760
1761fn classify_omena_parser_at_rule_kind(text: &str) -> keyof_omena_parser_at_rule_kind_counts::Kind {
1762    match text.trim_start_matches('@').to_ascii_lowercase().as_str() {
1763        "media" => keyof_omena_parser_at_rule_kind_counts::Kind::Media,
1764        "supports" => keyof_omena_parser_at_rule_kind_counts::Kind::Supports,
1765        "layer" => keyof_omena_parser_at_rule_kind_counts::Kind::Layer,
1766        "keyframes" | "-webkit-keyframes" => {
1767            keyof_omena_parser_at_rule_kind_counts::Kind::Keyframes
1768        }
1769        "value" => keyof_omena_parser_at_rule_kind_counts::Kind::Value,
1770        "at-root" => keyof_omena_parser_at_rule_kind_counts::Kind::AtRoot,
1771        _ => keyof_omena_parser_at_rule_kind_counts::Kind::Generic,
1772    }
1773}
1774
1775fn increment_omena_parser_at_rule_kind_count(
1776    counts: &mut OmenaParserAtRuleKindCountsV0,
1777    kind: keyof_omena_parser_at_rule_kind_counts::Kind,
1778) {
1779    match kind {
1780        keyof_omena_parser_at_rule_kind_counts::Kind::Media => counts.media += 1,
1781        keyof_omena_parser_at_rule_kind_counts::Kind::Supports => counts.supports += 1,
1782        keyof_omena_parser_at_rule_kind_counts::Kind::Layer => counts.layer += 1,
1783        keyof_omena_parser_at_rule_kind_counts::Kind::Keyframes => counts.keyframes += 1,
1784        keyof_omena_parser_at_rule_kind_counts::Kind::Value => counts.value += 1,
1785        keyof_omena_parser_at_rule_kind_counts::Kind::AtRoot => counts.at_root += 1,
1786        keyof_omena_parser_at_rule_kind_counts::Kind::Generic => counts.generic += 1,
1787    }
1788}
1789
1790fn classify_omena_parser_declaration_kind(
1791    property: &str,
1792) -> keyof_omena_parser_declaration_kind_counts::Kind {
1793    match property.trim().to_ascii_lowercase().as_str() {
1794        "composes" => keyof_omena_parser_declaration_kind_counts::Kind::Composes,
1795        "animation" => keyof_omena_parser_declaration_kind_counts::Kind::Animation,
1796        "animation-name" => keyof_omena_parser_declaration_kind_counts::Kind::AnimationName,
1797        _ => keyof_omena_parser_declaration_kind_counts::Kind::Generic,
1798    }
1799}
1800
1801fn increment_omena_parser_declaration_kind_count(
1802    counts: &mut OmenaParserDeclarationKindCountsV0,
1803    kind: keyof_omena_parser_declaration_kind_counts::Kind,
1804) {
1805    match kind {
1806        keyof_omena_parser_declaration_kind_counts::Kind::Composes => counts.composes += 1,
1807        keyof_omena_parser_declaration_kind_counts::Kind::Animation => counts.animation += 1,
1808        keyof_omena_parser_declaration_kind_counts::Kind::AnimationName => {
1809            counts.animation_name += 1
1810        }
1811        keyof_omena_parser_declaration_kind_counts::Kind::Generic => counts.generic += 1,
1812    }
1813}
1814
1815mod keyof_omena_parser_at_rule_kind_counts {
1816    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1817    pub enum Kind {
1818        Media,
1819        Supports,
1820        Layer,
1821        Keyframes,
1822        Value,
1823        AtRoot,
1824        Generic,
1825    }
1826}
1827
1828mod keyof_omena_parser_declaration_kind_counts {
1829    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1830    pub enum Kind {
1831        Composes,
1832        Animation,
1833        AnimationName,
1834        Generic,
1835    }
1836}
1837
1838fn sorted_unique(values: impl IntoIterator<Item = String>) -> Vec<String> {
1839    values
1840        .into_iter()
1841        .collect::<BTreeSet<_>>()
1842        .into_iter()
1843        .collect()
1844}
1845
1846fn css_module_composes_edge_kind_label(kind: ParsedCssModuleComposesEdgeKind) -> &'static str {
1847    match kind {
1848        ParsedCssModuleComposesEdgeKind::Local => "local",
1849        ParsedCssModuleComposesEdgeKind::Global => "global",
1850        ParsedCssModuleComposesEdgeKind::External => "external",
1851    }
1852}
1853
1854fn sass_symbol_fact_kind_label(kind: ParsedSassSymbolFactKind) -> &'static str {
1855    match kind {
1856        ParsedSassSymbolFactKind::VariableDeclaration => "sassVariableDeclaration",
1857        ParsedSassSymbolFactKind::VariableReference => "sassVariableReference",
1858        ParsedSassSymbolFactKind::MixinDeclaration => "sassMixinDeclaration",
1859        ParsedSassSymbolFactKind::MixinInclude => "sassMixinInclude",
1860        ParsedSassSymbolFactKind::FunctionDeclaration => "sassFunctionDeclaration",
1861        ParsedSassSymbolFactKind::FunctionCall => "sassFunctionCall",
1862    }
1863}
1864
1865fn sass_module_edge_fact_kind_label(kind: ParsedSassModuleEdgeFactKind) -> &'static str {
1866    match kind {
1867        ParsedSassModuleEdgeFactKind::Use => "sassUse",
1868        ParsedSassModuleEdgeFactKind::Forward => "sassForward",
1869        ParsedSassModuleEdgeFactKind::Import => "sassImport",
1870    }
1871}
1872
1873fn summarize_omena_parser_sass_symbol_resolution(
1874    symbols: &[ParsedSassSymbolFact],
1875) -> OmenaParserSassSymbolResolutionV0 {
1876    let mut declaration_by_symbol: BTreeMap<
1877        (&'static str, Option<String>, String),
1878        (usize, &'static str),
1879    > = BTreeMap::new();
1880    let mut declaration_count = 0usize;
1881    let mut reference_count = 0usize;
1882    let mut edges = Vec::new();
1883
1884    for (source_order, symbol) in symbols.iter().enumerate() {
1885        let kind = sass_symbol_fact_kind_label(symbol.kind);
1886        if sass_symbol_fact_kind_is_declaration(symbol.kind) {
1887            declaration_count += 1;
1888            declaration_by_symbol.insert(
1889                (
1890                    symbol.symbol_kind,
1891                    symbol.namespace.clone(),
1892                    symbol.name.clone(),
1893                ),
1894                (source_order, kind),
1895            );
1896            continue;
1897        }
1898        if !sass_symbol_fact_kind_is_reference(symbol.kind) {
1899            continue;
1900        }
1901
1902        reference_count += 1;
1903        let declaration = declaration_by_symbol.get(&(
1904            symbol.symbol_kind,
1905            symbol.namespace.clone(),
1906            symbol.name.clone(),
1907        ));
1908        edges.push(OmenaParserSassSymbolResolutionEdgeV0 {
1909            symbol_kind: symbol.symbol_kind,
1910            name: symbol.name.clone(),
1911            namespace: symbol.namespace.clone(),
1912            reference_kind: kind,
1913            reference_role: symbol.role,
1914            reference_source_order: source_order,
1915            declaration_kind: declaration.map(|(_, declaration_kind)| *declaration_kind),
1916            declaration_source_order: declaration.map(|(declaration_order, _)| *declaration_order),
1917            status: if declaration.is_some() {
1918                "resolved"
1919            } else {
1920                "unresolved"
1921            },
1922        });
1923    }
1924
1925    let resolved_reference_count = edges
1926        .iter()
1927        .filter(|edge| edge.status == "resolved")
1928        .count();
1929
1930    OmenaParserSassSymbolResolutionV0 {
1931        schema_version: "0",
1932        product: "omena-parser.sass-symbol-same-file-resolution",
1933        resolution_scope: "same-file",
1934        declaration_count,
1935        reference_count,
1936        resolved_reference_count,
1937        unresolved_reference_count: reference_count.saturating_sub(resolved_reference_count),
1938        edges,
1939        capabilities: OmenaParserSassSymbolResolutionCapabilitiesV0 {
1940            same_file_lexical_resolution_ready: true,
1941            declaration_before_reference_ready: true,
1942            unresolved_reference_reporting_ready: true,
1943            cross_file_module_resolution_ready: false,
1944        },
1945    }
1946}
1947
1948fn sass_symbol_fact_kind_is_declaration(kind: ParsedSassSymbolFactKind) -> bool {
1949    matches!(
1950        kind,
1951        ParsedSassSymbolFactKind::VariableDeclaration
1952            | ParsedSassSymbolFactKind::MixinDeclaration
1953            | ParsedSassSymbolFactKind::FunctionDeclaration
1954    )
1955}
1956
1957fn sass_symbol_fact_kind_is_reference(kind: ParsedSassSymbolFactKind) -> bool {
1958    matches!(
1959        kind,
1960        ParsedSassSymbolFactKind::VariableReference
1961            | ParsedSassSymbolFactKind::MixinInclude
1962            | ParsedSassSymbolFactKind::FunctionCall
1963    )
1964}
1965
1966pub fn summarize_parser_cst_equivalence(
1967    text: &str,
1968    dialect: StyleDialect,
1969) -> ParserCstEquivalenceSummaryV0 {
1970    let result = parse(text, dialect);
1971    let syntax = result.syntax();
1972    let cst = result.cst();
1973
1974    let mut node_count = 0;
1975    let mut token_count = 0;
1976    let mut syntax_kind_round_trip_ready = true;
1977    let mut zero_unknown_kind_ready = true;
1978
1979    for node in syntax.descendants() {
1980        node_count += 1;
1981        let kind = node.kind();
1982        syntax_kind_round_trip_ready &= SyntaxKind::from_raw(kind.into_raw()) == kind;
1983        zero_unknown_kind_ready &= SyntaxKind::ALL.contains(&kind);
1984    }
1985
1986    for token in syntax
1987        .descendants_with_tokens()
1988        .filter_map(|element| element.into_token())
1989    {
1990        token_count += 1;
1991        let kind = token.kind();
1992        syntax_kind_round_trip_ready &= SyntaxKind::from_raw(kind.into_raw()) == kind;
1993        zero_unknown_kind_ready &= SyntaxKind::ALL.contains(&kind);
1994    }
1995
1996    let typed_wrapper_count = usize::from(cst.stylesheet().is_some())
1997        + cst.rules().len()
1998        + cst.selectors().len()
1999        + cst.declarations().len()
2000        + cst.declaration_lists().len()
2001        + cst.values().len()
2002        + cst.component_values().len()
2003        + cst.simple_blocks().len()
2004        + cst.component_value_lists().len()
2005        + cst.comma_separated_component_value_lists().len()
2006        + cst.custom_property_values().len()
2007        + cst.at_rules().len()
2008        + cst.bogus_nodes().len();
2009
2010    ParserCstEquivalenceSummaryV0 {
2011        product: "omena-parser.cst-equivalence",
2012        dialect,
2013        root_kind: syntax.kind(),
2014        parser_node_count: node_count,
2015        parser_token_count: token_count,
2016        typed_wrapper_count,
2017        source_text_round_trip_ready: result.source_text().as_deref() == Some(text),
2018        syntax_kind_round_trip_ready,
2019        zero_unknown_kind_ready,
2020        typed_cst_wrapper_ready: cst.stylesheet().is_some() && typed_wrapper_count > 1,
2021        ready_surfaces: vec![
2022            "parserCstEquivalence",
2023            "parserUsesOmenaSyntaxKind",
2024            "parserCstSourceTextRoundTrip",
2025            "typedCstWrapperEquivalence",
2026        ],
2027    }
2028}
2029
2030pub fn collect_style_facts_with_extension(
2031    text: &str,
2032    extension: &impl DialectExtension,
2033) -> ParsedStyleFacts {
2034    let (tokens, lex_errors) = tokenize(text, extension);
2035    let mut parser = Parser::new(tokens.clone(), lex_errors, extension.dialect());
2036    let _green = parser.parse();
2037    let errors = parser.into_errors();
2038    let selectors = collect_selector_facts_from_tokens(&tokens);
2039    let variables = collect_variable_facts_from_tokens(&tokens);
2040    let sass_symbols = collect_sass_symbol_facts_from_tokens(&tokens);
2041    let sass_includes = collect_sass_include_facts_from_tokens(text, &tokens);
2042    let sass_module_edges = collect_sass_module_edge_facts_from_tokens(&tokens);
2043    let extend_targets = collect_extend_target_facts_from_tokens(&tokens);
2044    let animations = collect_animation_facts_from_tokens(&tokens);
2045    let css_module_values = collect_css_module_value_facts_from_tokens(&tokens);
2046    let css_module_value_import_edges =
2047        collect_css_module_value_import_edge_facts_from_tokens(&tokens);
2048    let css_module_value_definition_edges =
2049        collect_css_module_value_definition_edge_facts_from_tokens(&tokens);
2050    let css_module_composes = collect_css_module_composes_facts_from_tokens(&tokens);
2051    let css_module_composes_edges = collect_css_module_composes_edge_facts_from_tokens(&tokens);
2052    let icss = collect_icss_facts_from_tokens(&tokens);
2053    let icss_import_edges = collect_icss_import_edge_facts_from_tokens(&tokens);
2054    let icss_export_edges = collect_icss_export_edge_facts_from_tokens(&tokens);
2055    let at_rules = collect_at_rule_facts_from_tokens(&tokens, extension.dialect());
2056
2057    ParsedStyleFacts {
2058        product: "omena-parser.style-facts",
2059        dialect: extension.dialect(),
2060        selector_count: selectors.len(),
2061        selectors,
2062        variable_count: variables.len(),
2063        variables,
2064        sass_symbol_count: sass_symbols.len(),
2065        sass_symbols,
2066        sass_include_count: sass_includes.len(),
2067        sass_includes,
2068        sass_module_edge_count: sass_module_edges.len(),
2069        sass_module_edges,
2070        extend_target_count: extend_targets.len(),
2071        extend_targets,
2072        animation_count: animations.len(),
2073        animations,
2074        css_module_value_count: css_module_values.len(),
2075        css_module_values,
2076        css_module_value_import_edge_count: css_module_value_import_edges.len(),
2077        css_module_value_import_edges,
2078        css_module_value_definition_edge_count: css_module_value_definition_edges.len(),
2079        css_module_value_definition_edges,
2080        css_module_composes_count: css_module_composes.len(),
2081        css_module_composes,
2082        css_module_composes_edge_count: css_module_composes_edges.len(),
2083        css_module_composes_edges,
2084        icss_count: icss.len(),
2085        icss,
2086        icss_import_edge_count: icss_import_edges.len(),
2087        icss_import_edges,
2088        icss_export_edge_count: icss_export_edges.len(),
2089        icss_export_edges,
2090        at_rule_count: at_rules.len(),
2091        at_rules,
2092        error_count: errors.len(),
2093    }
2094}
2095
2096pub fn summarize_parser_semantic_name_consumption(
2097    text: &str,
2098    dialect: StyleDialect,
2099    db: &dyn salsa::Database,
2100) -> ParserSemanticNameConsumptionSummaryV0 {
2101    let facts = collect_style_facts(text, dialect);
2102    let candidates = parser_semantic_name_candidates(&facts);
2103    let interned_name_count = candidates
2104        .iter()
2105        .filter(|candidate| intern_parser_semantic_name(db, candidate.kind, &candidate.text))
2106        .count();
2107    let invalid_name_count = candidates.len().saturating_sub(interned_name_count);
2108
2109    ParserSemanticNameConsumptionSummaryV0 {
2110        product: "omena-parser.semantic-name-consumption",
2111        dialect,
2112        semantic_name_count: candidates.len(),
2113        interned_name_count,
2114        invalid_name_count,
2115        class_name_count: count_parser_semantic_name_kind(&candidates, NameKind::ClassName),
2116        css_ident_count: count_parser_semantic_name_kind(&candidates, NameKind::CssIdent),
2117        property_name_count: count_parser_semantic_name_kind(&candidates, NameKind::PropertyName),
2118        selector_key_count: count_parser_semantic_name_kind(&candidates, NameKind::SelectorKey),
2119        custom_property_name_count: count_parser_semantic_name_kind(
2120            &candidates,
2121            NameKind::CustomPropertyName,
2122        ),
2123        keyframes_name_count: count_parser_semantic_name_kind(&candidates, NameKind::KeyframesName),
2124        mixin_name_count: count_parser_semantic_name_kind(&candidates, NameKind::MixinName),
2125        file_path_count: count_parser_semantic_name_kind(&candidates, NameKind::FilePath),
2126        ready_surfaces: vec![
2127            "parserSemanticNameConsumption",
2128            "typedInternerValidation",
2129            "styleFactNameKindProjection",
2130        ],
2131    }
2132}
2133
2134fn parser_semantic_name_candidates(facts: &ParsedStyleFacts) -> Vec<ParserSemanticNameCandidateV0> {
2135    let mut candidates = Vec::new();
2136
2137    for selector in &facts.selectors {
2138        let kind = match selector.kind {
2139            ParsedSelectorFactKind::Class => NameKind::ClassName,
2140            ParsedSelectorFactKind::Id | ParsedSelectorFactKind::Placeholder => {
2141                NameKind::SelectorKey
2142            }
2143        };
2144        push_parser_semantic_name_candidate(&mut candidates, kind, &selector.name);
2145    }
2146
2147    for variable in &facts.variables {
2148        let kind = match variable.kind {
2149            ParsedVariableFactKind::CustomPropertyDeclaration
2150            | ParsedVariableFactKind::CustomPropertyReference => NameKind::CustomPropertyName,
2151            ParsedVariableFactKind::ScssDeclaration
2152            | ParsedVariableFactKind::ScssReference
2153            | ParsedVariableFactKind::LessDeclaration
2154            | ParsedVariableFactKind::LessReference => NameKind::CssIdent,
2155        };
2156        push_parser_semantic_name_candidate(&mut candidates, kind, &variable.name);
2157    }
2158
2159    for symbol in &facts.sass_symbols {
2160        let kind = match symbol.kind {
2161            ParsedSassSymbolFactKind::MixinDeclaration | ParsedSassSymbolFactKind::MixinInclude => {
2162                NameKind::MixinName
2163            }
2164            ParsedSassSymbolFactKind::VariableDeclaration
2165            | ParsedSassSymbolFactKind::VariableReference
2166            | ParsedSassSymbolFactKind::FunctionDeclaration
2167            | ParsedSassSymbolFactKind::FunctionCall => NameKind::CssIdent,
2168        };
2169        push_parser_semantic_name_candidate(&mut candidates, kind, &symbol.name);
2170        if let Some(namespace) = &symbol.namespace {
2171            push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
2172        }
2173    }
2174
2175    for include in &facts.sass_includes {
2176        push_parser_semantic_name_candidate(&mut candidates, NameKind::MixinName, &include.name);
2177        if let Some(namespace) = &include.namespace {
2178            push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
2179        }
2180    }
2181
2182    for edge in &facts.sass_module_edges {
2183        push_parser_semantic_name_candidate(&mut candidates, NameKind::FilePath, &edge.source);
2184        if let Some(namespace) = &edge.namespace {
2185            push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
2186        }
2187    }
2188
2189    for animation in &facts.animations {
2190        push_parser_semantic_name_candidate(
2191            &mut candidates,
2192            NameKind::KeyframesName,
2193            &animation.name,
2194        );
2195    }
2196
2197    for value in &facts.css_module_values {
2198        let kind = match value.kind {
2199            ParsedCssModuleValueFactKind::Definition | ParsedCssModuleValueFactKind::Reference => {
2200                NameKind::CssIdent
2201            }
2202            ParsedCssModuleValueFactKind::ImportSource => NameKind::FilePath,
2203        };
2204        push_parser_semantic_name_candidate(&mut candidates, kind, &value.name);
2205    }
2206
2207    for edge in &facts.css_module_value_import_edges {
2208        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.local_name);
2209        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.remote_name);
2210        push_parser_semantic_name_candidate(
2211            &mut candidates,
2212            NameKind::FilePath,
2213            &edge.import_source,
2214        );
2215    }
2216
2217    for edge in &facts.css_module_value_definition_edges {
2218        push_parser_semantic_name_candidate(
2219            &mut candidates,
2220            NameKind::CssIdent,
2221            &edge.definition_name,
2222        );
2223        for reference_name in &edge.reference_names {
2224            push_parser_semantic_name_candidate(
2225                &mut candidates,
2226                NameKind::CssIdent,
2227                reference_name,
2228            );
2229        }
2230    }
2231
2232    for composes in &facts.css_module_composes {
2233        let kind = match composes.kind {
2234            ParsedCssModuleComposesFactKind::Target => NameKind::ClassName,
2235            ParsedCssModuleComposesFactKind::ImportSource => NameKind::FilePath,
2236        };
2237        push_parser_semantic_name_candidate(&mut candidates, kind, &composes.name);
2238    }
2239
2240    for edge in &facts.css_module_composes_edges {
2241        for owner_selector_name in &edge.owner_selector_names {
2242            push_parser_semantic_name_candidate(
2243                &mut candidates,
2244                NameKind::ClassName,
2245                owner_selector_name,
2246            );
2247        }
2248        for target_name in &edge.target_names {
2249            push_parser_semantic_name_candidate(&mut candidates, NameKind::ClassName, target_name);
2250        }
2251        if let Some(import_source) = &edge.import_source {
2252            push_parser_semantic_name_candidate(&mut candidates, NameKind::FilePath, import_source);
2253        }
2254    }
2255
2256    for icss in &facts.icss {
2257        let kind = match icss.kind {
2258            ParsedIcssFactKind::ImportSource => NameKind::FilePath,
2259            ParsedIcssFactKind::ExportName
2260            | ParsedIcssFactKind::ImportLocalName
2261            | ParsedIcssFactKind::ImportRemoteName => NameKind::CssIdent,
2262        };
2263        push_parser_semantic_name_candidate(&mut candidates, kind, &icss.name);
2264    }
2265
2266    for edge in &facts.icss_import_edges {
2267        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.local_name);
2268        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.remote_name);
2269        push_parser_semantic_name_candidate(
2270            &mut candidates,
2271            NameKind::FilePath,
2272            &edge.import_source,
2273        );
2274    }
2275
2276    for edge in &facts.icss_export_edges {
2277        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.export_name);
2278        for reference_name in &edge.reference_names {
2279            push_parser_semantic_name_candidate(
2280                &mut candidates,
2281                NameKind::CssIdent,
2282                reference_name,
2283            );
2284        }
2285    }
2286
2287    for at_rule in &facts.at_rules {
2288        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &at_rule.name);
2289    }
2290
2291    candidates
2292}
2293
2294fn push_parser_semantic_name_candidate(
2295    candidates: &mut Vec<ParserSemanticNameCandidateV0>,
2296    kind: NameKind,
2297    text: &str,
2298) {
2299    candidates.push(ParserSemanticNameCandidateV0 {
2300        kind,
2301        text: text.to_string(),
2302    });
2303}
2304
2305fn count_parser_semantic_name_kind(
2306    candidates: &[ParserSemanticNameCandidateV0],
2307    kind: NameKind,
2308) -> usize {
2309    candidates
2310        .iter()
2311        .filter(|candidate| candidate.kind == kind)
2312        .count()
2313}
2314
2315fn intern_parser_semantic_name(db: &dyn salsa::Database, kind: NameKind, text: &str) -> bool {
2316    match kind {
2317        NameKind::ClassName => intern_class_name(db, text).is_ok(),
2318        NameKind::CssIdent => intern_css_ident(db, text).is_ok(),
2319        NameKind::PropertyName => intern_property_name(db, text).is_ok(),
2320        NameKind::SelectorKey => intern_selector_key(db, text).is_ok(),
2321        NameKind::CustomPropertyName => intern_custom_property_name(db, text).is_ok(),
2322        NameKind::KeyframesName => intern_keyframes_name(db, text).is_ok(),
2323        NameKind::MixinName => intern_mixin_name(db, text).is_ok(),
2324        NameKind::FilePath => intern_file_path(db, text).is_ok(),
2325    }
2326}
2327
2328pub fn summarize_pratt_value_parser_coverage() -> ParserPrattValueCoverageSummaryV0 {
2329    ParserPrattValueCoverageSummaryV0 {
2330        product: "omena-parser.pratt-value-coverage",
2331        infix_operator_kinds: vec![
2332            SyntaxKind::Plus,
2333            SyntaxKind::Minus,
2334            SyntaxKind::Star,
2335            SyntaxKind::Slash,
2336            SyntaxKind::Percent,
2337        ],
2338        prefix_operator_kinds: vec![SyntaxKind::Plus, SyntaxKind::Minus],
2339        value_expression_node_kinds: vec![
2340            SyntaxKind::UnaryExpression,
2341            SyntaxKind::BinaryExpression,
2342            SyntaxKind::ParenthesizedExpression,
2343            SyntaxKind::FunctionCall,
2344            SyntaxKind::FunctionArguments,
2345            SyntaxKind::ValueList,
2346            SyntaxKind::ComponentValueList,
2347            SyntaxKind::SimpleBlock,
2348            SyntaxKind::BogusValue,
2349        ],
2350        specialized_function_family_count: 10,
2351        css_values_l4_math_function_count: VALUES_L4_MATH_FUNCTION_NAMES.len(),
2352        css_color_function_count: CSS_COLOR_FUNCTION_NAMES.len(),
2353        ready_surfaces: vec![
2354            "prattValueParserCore",
2355            "prefixUnaryExpressions",
2356            "additiveMultiplicativePrecedence",
2357            "parenthesizedValueExpressions",
2358            "functionArgumentValueLists",
2359            "specializedCssValueFunctionFamilies",
2360            "valuesL4MathFunctionArityChecks",
2361            "varEnvAttrFunctionHeadChecks",
2362            "dynamicInterpolationEscapeHatches",
2363            "valueBogusRecovery",
2364        ],
2365        next_surfaces: vec!["fullPropertyValueGrammarRegistry"],
2366    }
2367}
2368
2369pub fn summarize_recursive_descent_parser_coverage() -> ParserRecursiveDescentCoverageSummaryV0 {
2370    ParserRecursiveDescentCoverageSummaryV0 {
2371        product: "omena-parser.recursive-descent-coverage",
2372        dialect_count: 4,
2373        entry_point_count: 10,
2374        selector_surface_count: 12,
2375        at_rule_surface_count: 19,
2376        dialect_extension_surface_count: 17,
2377        recovery_surface_count: 8,
2378        ready_surfaces: vec![
2379            "recursiveDescentParserCore",
2380            "stylesheetRuleDeclarationEntryPoints",
2381            "selectorsLevelFourCstNodes",
2382            "registeredAtRulePreludeParsers",
2383            "cssNestingRuleItems",
2384            "scssDialectStatements",
2385            "sassIndentedBlocks",
2386            "lessDialectStatements",
2387            "bogusRecoverySkeleton",
2388            "styleFactExtractionSurface",
2389        ],
2390        next_surfaces: vec!["completeExternalSpecMirror"],
2391    }
2392}
2393
2394pub fn summarize_parser_boundary() -> ParserBoundarySummary {
2395    ParserBoundarySummary {
2396        product: "omena-parser.boundary",
2397        tree_model: "cstree-green-root",
2398        parser_track: "greenFieldNextToEngineStyleParser",
2399        dialect_count: 4,
2400        shared_name_kind_count: NameKind::ALL.len(),
2401        ready_surfaces: vec![
2402            "lexResult",
2403            "lexedTokenTextSurface",
2404            "parseResult",
2405            "panicFreeTokenizer",
2406            "cstreeGreenBuilder",
2407            "tokenSetRecoveryScaffold",
2408            "dialectExtensionScaffold",
2409            "recursiveDescentParserCore",
2410            "recursiveDescentCoverageSummary",
2411            "selectorCstSkeleton",
2412            "atRuleRegistrySkeleton",
2413            "prattValueExpressionSkeleton",
2414            "prattValueParserCore",
2415            "prattValueCoverageSummary",
2416            "attributeMatcherTokenization",
2417            "attributeMatcherCstNodes",
2418            "attributeNameValueModifierCstNodes",
2419            "specializedValueFunctionCstNodes",
2420            "caseInsensitiveFunctionRegistry",
2421            "caseInsensitiveAtRuleRegistry",
2422            "valueAtomCstNodes",
2423            "identifierValueCstNodes",
2424            "stringValueCstNodes",
2425            "unicodeRangeValueCstNodes",
2426            "functionArgumentValueLists",
2427            "cssModuleScopeFunctionCstNodes",
2428            "cssModuleGlobalSelectorFactFiltering",
2429            "cssModuleLocalIdSelectorFacts",
2430            "cssModuleValueStyleFacts",
2431            "cssModuleValueDeclarationReferenceFacts",
2432            "cssModuleComposesStyleFacts",
2433            "icssStyleFacts",
2434            "animationNameStyleFacts",
2435            "animationShorthandStyleFacts",
2436            "scssStructuredBlockAtRules",
2437            "scssControlPreludeValidation",
2438            "scssControlStyleFactExtraction",
2439            "scssIncludeContentBlockStyleFacts",
2440            "scssSassModuleEdgeStyleFacts",
2441            "scssSassSymbolStyleFacts",
2442            "scssUtilityAtRules",
2443            "scssVariableFlagCstNodes",
2444            "scssNestedPropertyCstNodes",
2445            "scssModulePreludeSourceValidation",
2446            "scssModulePreludeClauseValidation",
2447            "scssModuleConfigCstNodes",
2448            "scssModuleConfigBogusRecovery",
2449            "scssPlaceholderSelectorCstNodes",
2450            "lessMixinDeclarationCstNodes",
2451            "lessMixinCallCstNodes",
2452            "lessMixinGuardCstNodes",
2453            "lessExtendPseudoCstNodes",
2454            "lessDetachedRulesetCstNodes",
2455            "lessNamespaceAccessCstNodes",
2456            "lessPropertyVariableTokenization",
2457            "lessPropertyVariableCstNodes",
2458            "lessEscapedStringTokenization",
2459            "lessEscapedStringValueCstNodes",
2460            "importantAnnotationTokenization",
2461            "urlTokenization",
2462            "urlValueCstNodes",
2463            "quotedUrlFunctionValueCstNodes",
2464            "conditionalAtRulePreludeCstNodes",
2465            "supportsAtRulePreludeValidation",
2466            "conditionalLevel5AtRuleCstNodes",
2467            "mediaQueryCstNodes",
2468            "mediaQueryListValidation",
2469            "importPreludeCstNodes",
2470            "importSourcePreludeValidation",
2471            "importTailPreludeValidation",
2472            "customMediaPreludeValidation",
2473            "propertyAtRuleNameValidation",
2474            "namedAtRulePreludeValidation",
2475            "containerAtRulePreludeValidation",
2476            "charsetNamespaceAtRulePreludeValidation",
2477            "keyframesAtRuleNameValidation",
2478            "emptyBlockAtRulePreludeValidation",
2479            "layerScopePreludeCstNodes",
2480            "layerAtRulePreludeValidation",
2481            "scopeAtRulePreludeValidation",
2482            "pageAtRulePreludeValidation",
2483            "pageMarginAtRuleCstNodes",
2484            "modernDeclarationAtRuleCstNodes",
2485            "fontFeatureValuesAtRuleCstNodes",
2486            "fontFeatureValuesPreludeValidation",
2487            "keyframeSelectorListValidation",
2488            "viewTransitionAtRuleCstNodes",
2489            "genericAtRulePreludeCstNodes",
2490            "bogusAtRulePreludeCstNodes",
2491            "nestingAtRuleCstNodes",
2492            "customMediaAtRuleCstNodes",
2493            "cssColorFunctionCstNodes",
2494            "colorFunctionArgumentChecks",
2495            "gradientFunctionCstNodes",
2496            "transformFunctionCstNodes",
2497            "filterFunctionCstNodes",
2498            "imageFunctionCstNodes",
2499            "shapeFunctionCstNodes",
2500            "envAttrFunctionCstNodes",
2501            "mathFunctionCstNodes",
2502            "mathFunctionArityChecks",
2503            "mathFunctionEmptyArgumentChecks",
2504            "varEnvAttrFunctionHeadChecks",
2505            "scssInterpolationTokenization",
2506            "scssInterpolationCstNodes",
2507            "lessInterpolationTokenization",
2508            "lessInterpolationCstNodes",
2509            "interpolationBogusRecovery",
2510            "unicodeRangeTokenization",
2511            "badStringTokenRecovery",
2512            "badStringValueBogusNodes",
2513            "emptyDeclarationValueRecovery",
2514            "emptyVariableValueRecovery",
2515            "missingSemicolonDeclarationRecovery",
2516            "coreBogusPopulationSlice",
2517            "dialectBogusPopulationSlice",
2518            "cssModuleValueCstNodes",
2519            "cssModuleComposesCstNodes",
2520            "icssModuleBlockCstNodes",
2521            "icssImportSourceValidation",
2522            "cssModuleFromClauseSourceValidation",
2523            "cssModuleComposesMultipleFromValidation",
2524            "cssModuleGlobalComposesValidation",
2525            "cssModuleBogusRecovery",
2526            "valueListCstNodes",
2527            "valueListBogusRecovery",
2528            "genericRecoveryBogusNodes",
2529            "sassIndentedTokenization",
2530            "sassIndentedBlockCstNodes",
2531            "sassIndentedStyleFacts",
2532            "differentialCorpusSeed",
2533            "differentialCorpus",
2534            "lightningCssDifferentialCorpusSlice",
2535            "lightningCssSelectorIdAndAtRuleDifferentialSlice",
2536            "midTypingNoPanicPropertySlice",
2537            "deterministicPanicFreeCorpus",
2538            "losslessCstTextRoundTripSmoke",
2539            "parseResultSourceTextSurface",
2540            "parseSourceParseRoundTripSmoke",
2541            "typedNumericValueAtomCstNodes",
2542            "bracketedValueCstNodes",
2543            "importantAnnotationCstNodes",
2544            "splitImportantAnnotationCstNodes",
2545            "unexpectedValueTokenBogusNodes",
2546            "cdoCdcTokenization",
2547            "cssIdentifierEscapeTokenization",
2548            "nullAndBomInputPreprocessingSlice",
2549            "hashDelimiterTokenization",
2550            "cssDashIdentTokenization",
2551            "signedNumericTokenization",
2552            "exponentNumericTokenization",
2553            "badUrlWhitespaceRecovery",
2554            "parserEntryPointApiSlice",
2555            "ruleListEntryPointApiSlice",
2556            "componentValueEntryPointApiSlice",
2557            "componentValueListEntryPointApiSlice",
2558            "commaSeparatedComponentValueListEntryPointApiSlice",
2559            "simpleBlockEntryPointApiSlice",
2560            "typedCstWrapperSlice",
2561            "parserCstEquivalence",
2562            "typedBogusCstWrapperSlice",
2563            "componentValueCstNodes",
2564            "simpleBlockCstNodes",
2565            "fullBogusPopulation",
2566            "componentValueListCstNodes",
2567            "commaSeparatedComponentValueListCstNodes",
2568            "customPropertyAnyValueComponentList",
2569            "customPropertyValueCstNodes",
2570            "functionalPseudoSelectorListCstNodes",
2571            "strictNotPseudoSelectorListCstNodes",
2572            "nthSelectorOfSelectorListCstNodes",
2573            "nthSelectorFormulaCstNodes",
2574            "hasRelativeSelectorListCstNodes",
2575            "langDirSelectorArgumentCstNodes",
2576            "namespaceQualifiedSelectorCstNodes",
2577            "selectorFunctionArgumentFactExclusion",
2578            "missingBlockCloseBogusTrivia",
2579            "initialDialectStatementNodes",
2580            "recoveryBogusSkeleton",
2581            "styleFactExtractionSurface",
2582            "parserSemanticNameConsumption",
2583            "productCutoverGate",
2584        ],
2585        not_ready_surfaces: vec![
2586            "completeExternalSpecMirror",
2587            "fullPropertyValueGrammarRegistry",
2588        ],
2589    }
2590}
2591
2592fn tokenize<'text>(
2593    text: &'text str,
2594    extension: &impl DialectExtension,
2595) -> (Vec<Token<'text>>, Vec<ParseError>) {
2596    let mut tokenizer = Tokenizer::new(text, extension);
2597    tokenizer.tokenize();
2598    (tokenizer.tokens, tokenizer.errors)
2599}
2600
2601struct Tokenizer<'text, 'extension, E> {
2602    text: &'text str,
2603    extension: &'extension E,
2604    offset: usize,
2605    scss_interpolation_depth: usize,
2606    less_interpolation_depth: usize,
2607    sass_indent_stack: Vec<usize>,
2608    tokens: Vec<Token<'text>>,
2609    errors: Vec<ParseError>,
2610}
2611
2612struct Parser<'text> {
2613    tokens: Vec<Token<'text>>,
2614    position: usize,
2615    dialect: StyleDialect,
2616    builder: GreenNodeBuilder<'static, 'static, SyntaxKind>,
2617    errors: Vec<ParseError>,
2618}
2619
2620impl<'text> Parser<'text> {
2621    fn new(tokens: Vec<Token<'text>>, errors: Vec<ParseError>, dialect: StyleDialect) -> Self {
2622        Self {
2623            tokens,
2624            position: 0,
2625            dialect,
2626            builder: GreenNodeBuilder::new(),
2627            errors,
2628        }
2629    }
2630
2631    fn parse(&mut self) -> (GreenNode, Option<Arc<TokenInterner>>) {
2632        self.parse_entry_point(ParseEntryPoint::Stylesheet)
2633    }
2634
2635    fn parse_entry_point(
2636        &mut self,
2637        entry_point: ParseEntryPoint,
2638    ) -> (GreenNode, Option<Arc<TokenInterner>>) {
2639        self.builder.start_node(SyntaxKind::Root);
2640        match entry_point {
2641            ParseEntryPoint::Stylesheet => {
2642                self.builder.start_node(SyntaxKind::Stylesheet);
2643                self.parse_stylesheet_items();
2644                self.builder.finish_node();
2645            }
2646            ParseEntryPoint::RuleList => {
2647                self.builder.start_node(SyntaxKind::RuleList);
2648                self.parse_rule_list_items();
2649                self.builder.finish_node();
2650            }
2651            ParseEntryPoint::Rule => self.parse_rule(),
2652            ParseEntryPoint::DeclarationList => {
2653                self.builder.start_node(SyntaxKind::DeclarationList);
2654                self.parse_declaration_list();
2655                self.builder.finish_node();
2656            }
2657            ParseEntryPoint::Declaration => self.parse_declaration(),
2658            ParseEntryPoint::Value => {
2659                self.builder.start_node(SyntaxKind::Value);
2660                self.parse_value_or_value_list_until(&[]);
2661                self.builder.finish_node();
2662            }
2663            ParseEntryPoint::ComponentValue => self.parse_component_value(&[]),
2664            ParseEntryPoint::ComponentValueList => self.parse_component_value_list_until(&[]),
2665            ParseEntryPoint::CommaSeparatedComponentValueList => {
2666                self.parse_comma_separated_component_value_list_until(&[])
2667            }
2668            ParseEntryPoint::SimpleBlock => self.parse_simple_block_entry_point(&[]),
2669        }
2670        self.parse_sass_indentation_bogus();
2671        self.parse_entry_point_trailing_bogus();
2672        self.builder.finish_node();
2673
2674        let builder = std::mem::take(&mut self.builder);
2675        let (green, cache) = builder.finish();
2676        let interner = cache.and_then(|cache| cache.into_interner()).map(Arc::new);
2677        (green, interner)
2678    }
2679
2680    fn parse_sass_indentation_bogus(&mut self) {
2681        if self.dialect != StyleDialect::Sass
2682            || !self
2683                .errors
2684                .iter()
2685                .any(|error| error.message == "inconsistent Sass indentation")
2686        {
2687            return;
2688        }
2689        self.builder.start_node(SyntaxKind::BogusSassIndentation);
2690        self.builder.finish_node();
2691    }
2692
2693    fn parse_entry_point_trailing_bogus(&mut self) {
2694        self.eat_trivia();
2695        if self.at_end() {
2696            return;
2697        }
2698        self.builder.start_node(SyntaxKind::BogusRecovery);
2699        while !self.at_end() {
2700            self.token_current();
2701        }
2702        self.builder.finish_node();
2703    }
2704
2705    fn into_errors(self) -> Vec<ParseError> {
2706        self.errors
2707    }
2708
2709    fn parse_stylesheet_items(&mut self) {
2710        while !self.at_end() {
2711            self.eat_trivia();
2712            if self.at_end() {
2713                break;
2714            }
2715            match self.current_kind() {
2716                Some(SyntaxKind::AtKeyword) if self.current_is_css_module_value_rule() => {
2717                    self.parse_css_module_value_rule()
2718                }
2719                Some(SyntaxKind::AtKeyword) if self.current_dialect_at_rule_spec().is_some() => {
2720                    self.parse_dialect_at_rule()
2721                }
2722                Some(SyntaxKind::AtKeyword) => self.parse_at_rule(),
2723                Some(SyntaxKind::ScssVariable)
2724                    if matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass) =>
2725                {
2726                    self.parse_variable_declaration(SyntaxKind::ScssVariableDeclaration)
2727                }
2728                Some(SyntaxKind::LessVariable) if self.dialect == StyleDialect::Less => {
2729                    self.parse_variable_declaration(SyntaxKind::LessVariableDeclaration)
2730                }
2731                Some(SyntaxKind::Cdo | SyntaxKind::Cdc) => self.token_current(),
2732                Some(SyntaxKind::RightBrace | SyntaxKind::SassDedent) => self.token_current(),
2733                Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) => {
2734                    self.token_current()
2735                }
2736                Some(_) => self.parse_rule(),
2737                None => break,
2738            }
2739        }
2740    }
2741
2742    fn parse_rule(&mut self) {
2743        let starts_less_mixin =
2744            self.dialect == StyleDialect::Less && self.current_starts_less_callable_signature();
2745        let has_rule_block = self.find_rule_block_open_before_recovery(&[
2746            SyntaxKind::Semicolon,
2747            SyntaxKind::SassOptionalSemicolon,
2748            SyntaxKind::RightBrace,
2749            SyntaxKind::SassDedent,
2750        ]);
2751        let kind = if let Some(kind) = self
2752            .current_icss_module_rule_kind()
2753            .filter(|_| has_rule_block)
2754        {
2755            kind
2756        } else if self.current_starts_less_mixin_declaration() {
2757            SyntaxKind::LessMixinDeclaration
2758        } else if starts_less_mixin {
2759            SyntaxKind::BogusLessMixin
2760        } else if has_rule_block {
2761            SyntaxKind::Rule
2762        } else {
2763            SyntaxKind::BogusRule
2764        };
2765
2766        self.builder.start_node(kind);
2767        if kind == SyntaxKind::CssModuleImportBlock && !self.current_icss_import_has_source() {
2768            self.error_at_current(ParseErrorCode::ExpectedValue, "expected ICSS import source");
2769        }
2770        if kind == SyntaxKind::LessMixinDeclaration {
2771            self.parse_less_mixin_header();
2772        } else if kind == SyntaxKind::BogusLessMixin {
2773            self.parse_until_recovery_with_optional_less_guard(&[
2774                SyntaxKind::Semicolon,
2775                SyntaxKind::RightBrace,
2776                SyntaxKind::SassDedent,
2777            ]);
2778            self.error_at_current(
2779                ParseErrorCode::UnexpectedCharacter,
2780                "expected Less mixin block",
2781            );
2782        } else {
2783            self.parse_selector_list();
2784        }
2785        if self.current_kind() == Some(SyntaxKind::LeftBrace) {
2786            self.token_current();
2787            self.builder
2788                .start_node(if self.previous_left_brace_has_match() {
2789                    SyntaxKind::DeclarationList
2790                } else {
2791                    SyntaxKind::BogusDeclarationList
2792                });
2793            self.parse_declaration_list();
2794            self.builder.finish_node();
2795            if self.current_kind() == Some(SyntaxKind::RightBrace) {
2796                self.token_current();
2797            } else {
2798                self.missing_token_bogus_trivia(
2799                    ParseErrorCode::UnexpectedCharacter,
2800                    "unterminated declaration block",
2801                );
2802            }
2803        } else if self.current_kind() == Some(SyntaxKind::SassIndent) {
2804            self.builder.start_node(SyntaxKind::SassIndentedBlock);
2805            self.token_current();
2806            self.builder.start_node(SyntaxKind::DeclarationList);
2807            self.parse_declaration_list();
2808            self.builder.finish_node();
2809            if self.current_kind() == Some(SyntaxKind::SassDedent) {
2810                self.token_current();
2811            } else {
2812                self.missing_token_bogus_trivia(
2813                    ParseErrorCode::UnexpectedCharacter,
2814                    "unterminated Sass indented declaration block",
2815                );
2816            }
2817            self.builder.finish_node();
2818        } else {
2819            self.consume_until_recovery(&[
2820                SyntaxKind::Semicolon,
2821                SyntaxKind::SassOptionalSemicolon,
2822                SyntaxKind::RightBrace,
2823                SyntaxKind::SassDedent,
2824            ]);
2825            if self.current_kind().is_some_and(is_statement_end) {
2826                self.token_current();
2827            }
2828        }
2829        self.builder.finish_node();
2830    }
2831
2832    fn current_icss_module_rule_kind(&self) -> Option<SyntaxKind> {
2833        if self.current_kind() != Some(SyntaxKind::Colon) {
2834            return None;
2835        }
2836        let (name_index, name_kind) = self.non_trivia_token_from(self.position + 1)?;
2837        if name_kind != SyntaxKind::Ident {
2838            return None;
2839        }
2840        match self.tokens.get(name_index)?.text {
2841            "export" => Some(SyntaxKind::CssModuleExportBlock),
2842            "import" => Some(SyntaxKind::CssModuleImportBlock),
2843            _ => None,
2844        }
2845    }
2846
2847    fn current_icss_import_has_source(&self) -> bool {
2848        let Some((name_index, SyntaxKind::Ident)) = self.non_trivia_token_from(self.position + 1)
2849        else {
2850            return false;
2851        };
2852        if self
2853            .tokens
2854            .get(name_index)
2855            .is_none_or(|token| token.text != "import")
2856        {
2857            return false;
2858        }
2859        let Some((open_index, SyntaxKind::LeftParen)) = self.non_trivia_token_from(name_index + 1)
2860        else {
2861            return false;
2862        };
2863        let Some((_, source_kind)) = self.non_trivia_token_from(open_index + 1) else {
2864            return false;
2865        };
2866        matches!(
2867            source_kind,
2868            SyntaxKind::String | SyntaxKind::Url | SyntaxKind::ScssInterpolationStart
2869        )
2870    }
2871
2872    fn parse_selector_list(&mut self) {
2873        self.parse_selector_list_until(&[]);
2874    }
2875
2876    fn parse_selector_list_until(&mut self, recovery: &[SyntaxKind]) {
2877        let kind = if self.current_kind() == Some(SyntaxKind::LeftBrace) {
2878            SyntaxKind::BogusSelectorList
2879        } else {
2880            SyntaxKind::SelectorList
2881        };
2882        self.builder.start_node(kind);
2883        while !self.at_end() {
2884            match self.current_kind() {
2885                Some(SyntaxKind::Comma) => self.token_current(),
2886                Some(kind) if is_selector_boundary_until(kind, recovery) => break,
2887                Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
2888                Some(_)
2889                    if recovery.contains(&SyntaxKind::RightParen)
2890                        && self.current_selector_item_is_bogus(recovery) =>
2891                {
2892                    self.parse_bogus_selector_until(recovery)
2893                }
2894                Some(_) => self.parse_selector_until(recovery),
2895                None => break,
2896            }
2897        }
2898        self.builder.finish_node();
2899    }
2900
2901    fn parse_strict_selector_list_until(&mut self, recovery: &[SyntaxKind]) {
2902        self.builder.start_node(
2903            if self.selector_list_contains_bogus_item_until(recovery)
2904                && self.current_kind() != Some(SyntaxKind::RightParen)
2905            {
2906                SyntaxKind::BogusSelectorList
2907            } else {
2908                SyntaxKind::SelectorList
2909            },
2910        );
2911        while !self.at_end() {
2912            match self.current_kind() {
2913                Some(SyntaxKind::Comma) => self.token_current(),
2914                Some(kind) if is_selector_boundary_until(kind, recovery) => break,
2915                Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
2916                Some(_)
2917                    if self.current_selector_item_is_bogus(recovery)
2918                        && self.current_kind() != Some(SyntaxKind::RightParen) =>
2919                {
2920                    self.parse_bogus_selector_until(recovery)
2921                }
2922                Some(_) => self.parse_selector_until(recovery),
2923                None => break,
2924            }
2925        }
2926        self.builder.finish_node();
2927    }
2928
2929    fn parse_relative_selector_list_until(&mut self, recovery: &[SyntaxKind]) {
2930        self.builder.start_node(
2931            if self.current_selector_item_is_bogus(recovery)
2932                && self.current_kind() != Some(SyntaxKind::RightParen)
2933            {
2934                SyntaxKind::BogusSelectorList
2935            } else {
2936                SyntaxKind::RelativeSelectorList
2937            },
2938        );
2939        while !self.at_end() {
2940            match self.current_kind() {
2941                Some(SyntaxKind::Comma) => self.token_current(),
2942                Some(kind) if is_selector_boundary_until(kind, recovery) => break,
2943                Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
2944                Some(_)
2945                    if self.current_selector_item_is_bogus(recovery)
2946                        && self.current_kind() != Some(SyntaxKind::RightParen) =>
2947                {
2948                    self.parse_bogus_selector_until(recovery)
2949                }
2950                Some(_) => self.parse_relative_selector_until(recovery),
2951                None => break,
2952            }
2953        }
2954        self.builder.finish_node();
2955    }
2956
2957    fn parse_relative_selector_until(&mut self, recovery: &[SyntaxKind]) {
2958        self.builder.start_node(SyntaxKind::RelativeSelector);
2959        self.builder.start_node(SyntaxKind::ComplexSelector);
2960        self.parse_complex_selector_until(recovery);
2961        self.builder.finish_node();
2962        self.builder.finish_node();
2963    }
2964
2965    fn parse_bogus_selector_until(&mut self, recovery: &[SyntaxKind]) {
2966        self.builder.start_node(SyntaxKind::BogusSelector);
2967        self.error_at_current(
2968            ParseErrorCode::UnexpectedCharacter,
2969            "invalid selector in selector list",
2970        );
2971        let mut paren_depth = 0usize;
2972        let mut bracket_depth = 0usize;
2973        while !self.at_end() {
2974            let Some(kind) = self.current_kind() else {
2975                break;
2976            };
2977            if paren_depth == 0
2978                && bracket_depth == 0
2979                && (kind == SyntaxKind::Comma || is_selector_boundary_until(kind, recovery))
2980            {
2981                break;
2982            }
2983            match kind {
2984                SyntaxKind::LeftParen => paren_depth += 1,
2985                SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
2986                SyntaxKind::LeftBracket => bracket_depth += 1,
2987                SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
2988                _ => {}
2989            }
2990            self.token_current();
2991        }
2992        self.builder.finish_node();
2993    }
2994
2995    fn parse_selector_until(&mut self, recovery: &[SyntaxKind]) {
2996        self.builder.start_node(SyntaxKind::Selector);
2997        self.builder.start_node(SyntaxKind::ComplexSelector);
2998        self.parse_complex_selector_until(recovery);
2999        self.builder.finish_node();
3000        self.builder.finish_node();
3001    }
3002
3003    fn parse_complex_selector_until(&mut self, recovery: &[SyntaxKind]) {
3004        let mut has_component = false;
3005        while !self.at_end() {
3006            match self.current_kind() {
3007                Some(kind) if is_selector_boundary_until(kind, recovery) => break,
3008                Some(SyntaxKind::Whitespace) => {
3009                    if has_component
3010                        && self.next_non_trivia_kind().is_some_and(|kind| {
3011                            !is_selector_boundary_until(kind, recovery) && !is_combinator(kind)
3012                        })
3013                    {
3014                        self.parse_whitespace_combinator();
3015                        has_component = false;
3016                    } else {
3017                        self.token_current();
3018                    }
3019                }
3020                Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
3021                Some(kind) if is_combinator(kind) => {
3022                    self.parse_combinator();
3023                    has_component = false;
3024                }
3025                Some(_) => {
3026                    self.parse_compound_selector_until(recovery);
3027                    has_component = true;
3028                }
3029                None => break,
3030            }
3031        }
3032    }
3033
3034    fn parse_compound_selector_until(&mut self, recovery: &[SyntaxKind]) {
3035        let starts_valid = self.current_kind().is_some_and(|kind| {
3036            selector_component_can_start(kind)
3037                || self.current_starts_namespace_qualified_selector(kind)
3038                || is_interpolation_start(kind)
3039        });
3040        self.builder.start_node(if starts_valid {
3041            SyntaxKind::CompoundSelector
3042        } else {
3043            SyntaxKind::BogusCompoundSelector
3044        });
3045        let start = self.position;
3046        while !self.at_end() {
3047            match self.current_kind() {
3048                Some(kind)
3049                    if is_selector_boundary_until(kind, recovery)
3050                        || kind == SyntaxKind::Whitespace
3051                        || kind == SyntaxKind::SassIndentedNewline
3052                        || is_combinator(kind) =>
3053                {
3054                    break;
3055                }
3056                Some(SyntaxKind::Dot) => self.parse_class_selector(),
3057                Some(SyntaxKind::Hash) => self.parse_id_selector(),
3058                Some(kind) if self.current_starts_namespace_qualified_selector(kind) => {
3059                    self.parse_namespace_qualified_selector()
3060                }
3061                Some(SyntaxKind::Ident) => self.parse_type_selector(),
3062                Some(SyntaxKind::Star) => self.parse_universal_selector(),
3063                Some(SyntaxKind::Ampersand) => self.parse_nesting_selector(),
3064                Some(SyntaxKind::ScssPlaceholder) => self.parse_scss_placeholder_selector(),
3065                Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3066                    kind,
3067                    &[
3068                        SyntaxKind::Comma,
3069                        SyntaxKind::LeftBrace,
3070                        SyntaxKind::SassIndent,
3071                        SyntaxKind::RightBrace,
3072                        SyntaxKind::SassDedent,
3073                        SyntaxKind::RightParen,
3074                        SyntaxKind::Semicolon,
3075                        SyntaxKind::SassOptionalSemicolon,
3076                    ],
3077                ),
3078                Some(SyntaxKind::LeftBracket) => self.parse_attribute_selector(),
3079                Some(SyntaxKind::Colon) if self.current_starts_less_extend_rule() => {
3080                    self.parse_less_extend_rule()
3081                }
3082                Some(SyntaxKind::Colon) => {
3083                    self.parse_pseudo_selector(SyntaxKind::PseudoClassSelector)
3084                }
3085                Some(SyntaxKind::DoubleColon) => {
3086                    self.parse_pseudo_selector(SyntaxKind::PseudoElementSelector)
3087                }
3088                Some(_) => self.token_current(),
3089                None => break,
3090            }
3091        }
3092        if self.position == start {
3093            self.token_current();
3094        }
3095        if !starts_valid {
3096            self.error_at_current(
3097                ParseErrorCode::UnexpectedCharacter,
3098                "expected selector component",
3099            );
3100        }
3101        self.builder.finish_node();
3102    }
3103
3104    fn parse_class_selector(&mut self) {
3105        self.builder.start_node(SyntaxKind::ClassSelector);
3106        self.token_current();
3107        if matches!(
3108            self.current_kind(),
3109            Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
3110        ) {
3111            self.token_current();
3112        } else {
3113            self.empty_bogus_node(
3114                SyntaxKind::BogusSelector,
3115                ParseErrorCode::ExpectedSelectorName,
3116                "expected class selector name",
3117            );
3118        }
3119        self.builder.finish_node();
3120    }
3121
3122    fn parse_id_selector(&mut self) {
3123        self.builder.start_node(SyntaxKind::IdSelector);
3124        self.token_current();
3125        self.builder.finish_node();
3126    }
3127
3128    fn parse_type_selector(&mut self) {
3129        self.builder.start_node(SyntaxKind::TypeSelector);
3130        self.token_current();
3131        self.builder.finish_node();
3132    }
3133
3134    fn parse_universal_selector(&mut self) {
3135        self.builder.start_node(SyntaxKind::UniversalSelector);
3136        self.token_current();
3137        self.builder.finish_node();
3138    }
3139
3140    fn parse_namespace_qualified_selector(&mut self) {
3141        let selector_kind =
3142            if self.namespace_qualified_selector_target_kind() == Some(SyntaxKind::Star) {
3143                SyntaxKind::UniversalSelector
3144            } else {
3145                SyntaxKind::TypeSelector
3146            };
3147        self.builder.start_node(selector_kind);
3148        self.builder.start_node(SyntaxKind::NamespacePrefix);
3149        if self.current_kind() != Some(SyntaxKind::Pipe) {
3150            self.token_current();
3151        }
3152        self.token_current();
3153        self.builder.finish_node();
3154        if matches!(
3155            self.current_kind(),
3156            Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star)
3157        ) {
3158            self.token_current();
3159        } else {
3160            self.empty_bogus_node(
3161                SyntaxKind::BogusSelector,
3162                ParseErrorCode::ExpectedSelectorName,
3163                "expected namespace-qualified selector name",
3164            );
3165        }
3166        self.builder.finish_node();
3167    }
3168
3169    fn parse_nesting_selector(&mut self) {
3170        self.builder.start_node(SyntaxKind::NestingSelectorNode);
3171        self.token_current();
3172        self.builder.finish_node();
3173    }
3174
3175    fn parse_scss_placeholder_selector(&mut self) {
3176        self.builder.start_node(SyntaxKind::ScssPlaceholderSelector);
3177        self.token_current();
3178        self.builder.finish_node();
3179    }
3180
3181    fn parse_attribute_selector(&mut self) {
3182        let kind = if self.find_before_recovery(
3183            SyntaxKind::RightBracket,
3184            &[
3185                SyntaxKind::Comma,
3186                SyntaxKind::LeftBrace,
3187                SyntaxKind::RightBrace,
3188                SyntaxKind::Semicolon,
3189            ],
3190        ) {
3191            SyntaxKind::AttributeSelector
3192        } else {
3193            SyntaxKind::BogusSelector
3194        };
3195        self.builder.start_node(kind);
3196        self.token_current();
3197        let mut saw_matcher = false;
3198        let mut saw_value = false;
3199        let mut closed = false;
3200        while !self.at_end() {
3201            match self.current_kind() {
3202                Some(SyntaxKind::RightBracket) => {
3203                    self.token_current();
3204                    closed = true;
3205                    break;
3206                }
3207                Some(kind) if is_attribute_matcher(kind) => {
3208                    self.parse_attribute_matcher();
3209                    saw_matcher = true;
3210                }
3211                Some(kind) if is_selector_boundary(kind) => break,
3212                Some(kind) if !saw_matcher && attribute_name_token_can_start(kind) => {
3213                    self.parse_attribute_name()
3214                }
3215                Some(kind)
3216                    if saw_matcher && !saw_value && attribute_value_token_can_start(kind) =>
3217                {
3218                    self.parse_attribute_value();
3219                    saw_value = true;
3220                }
3221                Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) if saw_value => {
3222                    self.parse_attribute_modifier()
3223                }
3224                Some(_) => self.token_current(),
3225                None => break,
3226            }
3227        }
3228        if !closed {
3229            self.error_at_current(
3230                ParseErrorCode::UnterminatedAttributeSelector,
3231                "unterminated attribute selector",
3232            );
3233        }
3234        self.builder.finish_node();
3235    }
3236
3237    fn parse_attribute_matcher(&mut self) {
3238        self.builder.start_node(SyntaxKind::AttributeMatcher);
3239        self.token_current();
3240        self.builder.finish_node();
3241    }
3242
3243    fn parse_attribute_name(&mut self) {
3244        self.builder.start_node(SyntaxKind::AttributeName);
3245        while !self.at_end() {
3246            match self.current_kind() {
3247                Some(SyntaxKind::RightBracket) => break,
3248                Some(kind) if is_attribute_matcher(kind) || is_selector_boundary(kind) => break,
3249                Some(kind) if attribute_name_token_can_continue(kind) => self.token_current(),
3250                Some(_) => break,
3251                None => break,
3252            }
3253        }
3254        self.builder.finish_node();
3255    }
3256
3257    fn parse_attribute_value(&mut self) {
3258        self.builder.start_node(SyntaxKind::AttributeValue);
3259        self.token_current();
3260        self.builder.finish_node();
3261    }
3262
3263    fn parse_attribute_modifier(&mut self) {
3264        self.builder.start_node(SyntaxKind::AttributeModifier);
3265        self.token_current();
3266        self.builder.finish_node();
3267    }
3268
3269    fn parse_pseudo_selector(&mut self, kind: SyntaxKind) {
3270        self.builder.start_node(kind);
3271        self.token_current();
3272        let pseudo_name = self.current_text().map(str::to_owned);
3273        let css_module_scope_kind = if kind == SyntaxKind::PseudoClassSelector {
3274            self.current_text().and_then(css_module_scope_function_kind)
3275        } else {
3276            None
3277        };
3278        if self.current_kind() == Some(SyntaxKind::Ident) {
3279            if let Some(kind) = css_module_scope_kind {
3280                self.builder.start_node(kind);
3281            }
3282            self.token_current();
3283        } else {
3284            self.empty_bogus_node(
3285                SyntaxKind::BogusSelector,
3286                ParseErrorCode::ExpectedSelectorName,
3287                "expected pseudo selector name",
3288            );
3289        }
3290        if self.current_kind() == Some(SyntaxKind::LeftParen) {
3291            self.token_current();
3292            self.builder.start_node(SyntaxKind::PseudoSelectorArgument);
3293            if kind == SyntaxKind::PseudoClassSelector
3294                && pseudo_name
3295                    .as_deref()
3296                    .is_some_and(is_selector_list_pseudo_class)
3297            {
3298                self.parse_selector_list_until(&[SyntaxKind::RightParen]);
3299            } else if kind == SyntaxKind::PseudoClassSelector
3300                && pseudo_name.as_deref() == Some("not")
3301            {
3302                self.parse_strict_selector_list_until(&[SyntaxKind::RightParen]);
3303            } else if kind == SyntaxKind::PseudoClassSelector
3304                && pseudo_name.as_deref() == Some("has")
3305            {
3306                self.parse_relative_selector_list_until(&[SyntaxKind::RightParen]);
3307            } else if kind == SyntaxKind::PseudoClassSelector
3308                && pseudo_name.as_deref().is_some_and(is_nth_pseudo_class)
3309            {
3310                self.parse_nth_selector_argument();
3311            } else if kind == SyntaxKind::PseudoClassSelector
3312                && pseudo_name.as_deref() == Some("lang")
3313            {
3314                self.parse_language_selector_argument();
3315            } else if kind == SyntaxKind::PseudoClassSelector
3316                && pseudo_name.as_deref() == Some("dir")
3317            {
3318                self.parse_directionality_selector_argument();
3319            } else {
3320                while !self.at_end() {
3321                    match self.current_kind() {
3322                        Some(SyntaxKind::RightParen) => break,
3323                        Some(kind) if is_selector_boundary(kind) => break,
3324                        Some(_) => self.token_current(),
3325                        None => break,
3326                    }
3327                }
3328            }
3329            self.builder.finish_node();
3330            if self.current_kind() == Some(SyntaxKind::RightParen) {
3331                self.token_current();
3332            }
3333        }
3334        if css_module_scope_kind.is_some() {
3335            self.builder.finish_node();
3336        }
3337        self.builder.finish_node();
3338    }
3339
3340    fn parse_nth_selector_argument(&mut self) {
3341        self.builder.start_node(SyntaxKind::NthSelectorArgument);
3342        self.builder.start_node(SyntaxKind::NthSelectorFormula);
3343        while !self.at_end() {
3344            match self.current_kind() {
3345                Some(SyntaxKind::RightParen) => break,
3346                Some(kind) if is_selector_boundary(kind) => break,
3347                Some(SyntaxKind::Ident) if self.current_text() == Some("of") => break,
3348                Some(_) => self.token_current(),
3349                None => break,
3350            }
3351        }
3352        self.builder.finish_node();
3353
3354        if self.current_kind() == Some(SyntaxKind::Ident) && self.current_text() == Some("of") {
3355            self.builder
3356                .start_node(SyntaxKind::NthSelectorOfSelectorList);
3357            self.token_current();
3358            self.parse_selector_list_until(&[SyntaxKind::RightParen]);
3359            self.builder.finish_node();
3360        }
3361
3362        self.builder.finish_node();
3363    }
3364
3365    fn parse_language_selector_argument(&mut self) {
3366        self.builder
3367            .start_node(SyntaxKind::LanguageSelectorArgument);
3368        while !self.at_end() {
3369            match self.current_kind() {
3370                Some(SyntaxKind::RightParen) => break,
3371                Some(SyntaxKind::Comma) => self.token_current(),
3372                Some(kind) if is_selector_boundary(kind) => break,
3373                Some(kind) if language_tag_token_can_start(kind) => self.parse_language_tag(),
3374                Some(_) => self.token_current(),
3375                None => break,
3376            }
3377        }
3378        self.builder.finish_node();
3379    }
3380
3381    fn parse_language_tag(&mut self) {
3382        self.builder.start_node(SyntaxKind::LanguageTag);
3383        self.token_current();
3384        self.builder.finish_node();
3385    }
3386
3387    fn parse_directionality_selector_argument(&mut self) {
3388        self.builder
3389            .start_node(SyntaxKind::DirectionalitySelectorArgument);
3390        if self
3391            .current_kind()
3392            .is_some_and(language_tag_token_can_start)
3393        {
3394            self.token_current();
3395        }
3396        while !self.at_end() {
3397            match self.current_kind() {
3398                Some(SyntaxKind::RightParen) => break,
3399                Some(kind) if is_selector_boundary(kind) => break,
3400                Some(_) => self.token_current(),
3401                None => break,
3402            }
3403        }
3404        self.builder.finish_node();
3405    }
3406
3407    fn parse_less_extend_rule(&mut self) {
3408        self.builder.start_node(SyntaxKind::LessExtendRule);
3409        if self.current_kind() == Some(SyntaxKind::Colon) {
3410            self.token_current();
3411        }
3412        if self.current_text() == Some("extend") {
3413            self.token_current();
3414        } else {
3415            self.empty_bogus_node(
3416                SyntaxKind::BogusSelector,
3417                ParseErrorCode::ExpectedSelectorName,
3418                "expected Less extend selector",
3419            );
3420        }
3421        if self.current_kind() == Some(SyntaxKind::LeftParen) {
3422            self.token_current();
3423            self.builder.start_node(SyntaxKind::PseudoSelectorArgument);
3424            while !self.at_end() {
3425                match self.current_kind() {
3426                    Some(SyntaxKind::RightParen) => break,
3427                    Some(kind) if is_selector_boundary(kind) => break,
3428                    Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3429                        kind,
3430                        &[
3431                            SyntaxKind::RightParen,
3432                            SyntaxKind::Comma,
3433                            SyntaxKind::LeftBrace,
3434                            SyntaxKind::SassIndent,
3435                            SyntaxKind::Semicolon,
3436                            SyntaxKind::SassOptionalSemicolon,
3437                        ],
3438                    ),
3439                    Some(_) => self.token_current(),
3440                    None => break,
3441                }
3442            }
3443            self.builder.finish_node();
3444            if self.current_kind() == Some(SyntaxKind::RightParen) {
3445                self.token_current();
3446            }
3447        }
3448        self.builder.finish_node();
3449    }
3450
3451    fn parse_combinator(&mut self) {
3452        let has_rhs = self
3453            .next_non_trivia_kind()
3454            .is_some_and(|kind| selector_component_can_start(kind) || is_interpolation_start(kind));
3455        self.builder.start_node(if has_rhs {
3456            SyntaxKind::Combinator
3457        } else {
3458            SyntaxKind::BogusCombinator
3459        });
3460        self.token_current();
3461        if !has_rhs {
3462            self.error_at_current(
3463                ParseErrorCode::UnexpectedCharacter,
3464                "expected selector after combinator",
3465            );
3466        }
3467        self.builder.finish_node();
3468    }
3469
3470    fn parse_whitespace_combinator(&mut self) {
3471        self.builder.start_node(SyntaxKind::Combinator);
3472        while self.current_kind() == Some(SyntaxKind::Whitespace) {
3473            self.token_current();
3474        }
3475        self.builder.finish_node();
3476    }
3477
3478    fn parse_declaration_list(&mut self) {
3479        while !self.at_end() {
3480            self.eat_trivia();
3481            match self.current_kind() {
3482                Some(SyntaxKind::RightBrace | SyntaxKind::SassDedent) | None => break,
3483                Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) => {
3484                    self.token_current()
3485                }
3486                Some(SyntaxKind::AtKeyword) if self.current_is_css_module_value_rule() => {
3487                    self.parse_css_module_value_rule()
3488                }
3489                Some(SyntaxKind::AtKeyword) if self.current_dialect_at_rule_spec().is_some() => {
3490                    self.parse_dialect_at_rule()
3491                }
3492                Some(SyntaxKind::AtKeyword) => self.parse_at_rule(),
3493                Some(_) if self.current_starts_less_namespace_access() => {
3494                    self.parse_less_namespace_access()
3495                }
3496                Some(_) if self.current_starts_less_mixin_call() => self.parse_less_mixin_call(),
3497                Some(_) if self.current_starts_scss_nested_property() => {
3498                    self.parse_scss_nested_property()
3499                }
3500                Some(_) if self.current_starts_nested_rule() => self.parse_rule(),
3501                Some(SyntaxKind::ScssVariable)
3502                    if matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass) =>
3503                {
3504                    self.parse_variable_declaration(SyntaxKind::ScssVariableDeclaration)
3505                }
3506                Some(SyntaxKind::LessVariable) if self.dialect == StyleDialect::Less => {
3507                    self.parse_variable_declaration(SyntaxKind::LessVariableDeclaration)
3508                }
3509                Some(SyntaxKind::LeftBrace) => {
3510                    self.builder.start_node(SyntaxKind::BogusDeclaration);
3511                    self.token_current();
3512                    self.builder.finish_node();
3513                }
3514                Some(_) => self.parse_declaration(),
3515            }
3516        }
3517    }
3518
3519    fn parse_scss_nested_property(&mut self) {
3520        self.builder.start_node(SyntaxKind::ScssNestedProperty);
3521        self.builder.start_node(SyntaxKind::PropertyName);
3522        while !self.at_end() {
3523            match self.current_kind() {
3524                Some(SyntaxKind::Colon) => break,
3525                Some(
3526                    SyntaxKind::Semicolon
3527                    | SyntaxKind::SassOptionalSemicolon
3528                    | SyntaxKind::RightBrace
3529                    | SyntaxKind::SassDedent,
3530                ) => break,
3531                Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3532                    kind,
3533                    &[
3534                        SyntaxKind::Colon,
3535                        SyntaxKind::Semicolon,
3536                        SyntaxKind::SassOptionalSemicolon,
3537                        SyntaxKind::RightBrace,
3538                        SyntaxKind::SassDedent,
3539                    ],
3540                ),
3541                Some(_) => self.token_current(),
3542                None => break,
3543            }
3544        }
3545        self.builder.finish_node();
3546
3547        if self.current_kind() == Some(SyntaxKind::Colon) {
3548            self.token_current();
3549        }
3550
3551        let block_recovery = [
3552            SyntaxKind::LeftBrace,
3553            SyntaxKind::SassIndent,
3554            SyntaxKind::Semicolon,
3555            SyntaxKind::SassOptionalSemicolon,
3556            SyntaxKind::RightBrace,
3557            SyntaxKind::SassDedent,
3558        ];
3559        if !matches!(
3560            self.current_kind(),
3561            Some(
3562                SyntaxKind::LeftBrace
3563                    | SyntaxKind::SassIndent
3564                    | SyntaxKind::Semicolon
3565                    | SyntaxKind::SassOptionalSemicolon
3566                    | SyntaxKind::RightBrace
3567                    | SyntaxKind::SassDedent
3568            )
3569        ) {
3570            self.builder.start_node(SyntaxKind::Value);
3571            self.parse_value_or_value_list_until(&block_recovery);
3572            self.builder.finish_node();
3573        }
3574
3575        match self.current_kind() {
3576            Some(SyntaxKind::LeftBrace) => self.parse_declaration_block(),
3577            Some(SyntaxKind::SassIndent) => self.parse_sass_indented_nested_property_block(),
3578            Some(_) => self.consume_until_recovery(&[
3579                SyntaxKind::Semicolon,
3580                SyntaxKind::SassOptionalSemicolon,
3581                SyntaxKind::RightBrace,
3582                SyntaxKind::SassDedent,
3583            ]),
3584            None => {}
3585        }
3586
3587        if self.current_kind().is_some_and(is_statement_end) {
3588            self.token_current();
3589        }
3590        self.builder.finish_node();
3591    }
3592
3593    fn parse_sass_indented_nested_property_block(&mut self) {
3594        self.builder.start_node(SyntaxKind::SassIndentedBlock);
3595        if self.current_kind() == Some(SyntaxKind::SassIndent) {
3596            self.token_current();
3597        }
3598        self.builder.start_node(SyntaxKind::DeclarationList);
3599        self.parse_declaration_list();
3600        self.builder.finish_node();
3601        if self.current_kind() == Some(SyntaxKind::SassDedent) {
3602            self.token_current();
3603        } else {
3604            self.error_at_current(
3605                ParseErrorCode::UnexpectedCharacter,
3606                "unterminated Sass indented nested property block",
3607            );
3608        }
3609        self.builder.finish_node();
3610    }
3611
3612    fn parse_variable_declaration(&mut self, kind: SyntaxKind) {
3613        let has_colon = self.find_before_recovery(
3614            SyntaxKind::Colon,
3615            &[
3616                SyntaxKind::Semicolon,
3617                SyntaxKind::SassOptionalSemicolon,
3618                SyntaxKind::RightBrace,
3619                SyntaxKind::SassDedent,
3620            ],
3621        );
3622        self.builder
3623            .start_node(variable_declaration_node_kind(kind, has_colon));
3624        self.token_current();
3625        if self.current_kind() == Some(SyntaxKind::Colon) {
3626            self.token_current();
3627            self.eat_value_trivia();
3628            let value_recovery = [
3629                SyntaxKind::Semicolon,
3630                SyntaxKind::SassOptionalSemicolon,
3631                SyntaxKind::RightBrace,
3632                SyntaxKind::SassDedent,
3633            ];
3634            if kind == SyntaxKind::LessVariableDeclaration
3635                && self.current_kind() == Some(SyntaxKind::LeftBrace)
3636            {
3637                self.parse_less_detached_ruleset();
3638            } else {
3639                let has_value = self
3640                    .non_trivia_token_from(self.position)
3641                    .is_some_and(|(_, kind)| !value_recovery.contains(&kind));
3642                self.builder.start_node(SyntaxKind::Value);
3643                if has_value {
3644                    self.parse_value_or_value_list_until(&value_recovery);
3645                } else {
3646                    self.empty_bogus_node(
3647                        SyntaxKind::BogusValue,
3648                        ParseErrorCode::ExpectedValue,
3649                        "expected variable value",
3650                    );
3651                }
3652                self.builder.finish_node();
3653            }
3654        } else {
3655            self.error_at_current(
3656                ParseErrorCode::UnexpectedCharacter,
3657                "expected variable declaration colon",
3658            );
3659            self.consume_until_recovery(&[
3660                SyntaxKind::Semicolon,
3661                SyntaxKind::SassOptionalSemicolon,
3662                SyntaxKind::RightBrace,
3663                SyntaxKind::SassDedent,
3664            ]);
3665        }
3666        if self.current_kind().is_some_and(is_statement_end) {
3667            self.token_current();
3668        }
3669        self.builder.finish_node();
3670    }
3671
3672    fn parse_less_detached_ruleset(&mut self) {
3673        let closed = self.current_left_brace_has_match();
3674        self.builder.start_node(if closed {
3675            SyntaxKind::LessDetachedRulesetNode
3676        } else {
3677            SyntaxKind::BogusLessDetachedRuleset
3678        });
3679        if self.current_kind() == Some(SyntaxKind::LeftBrace) {
3680            self.token_current();
3681            self.builder.start_node(SyntaxKind::DeclarationList);
3682            self.parse_declaration_list();
3683            self.builder.finish_node();
3684        }
3685        if self.current_kind() == Some(SyntaxKind::RightBrace) {
3686            self.token_current();
3687        } else {
3688            self.error_at_current(
3689                ParseErrorCode::UnexpectedCharacter,
3690                "unterminated Less detached ruleset",
3691            );
3692        }
3693        self.builder.finish_node();
3694    }
3695
3696    fn parse_declaration(&mut self) {
3697        let starts_composes = self.current_text() == Some("composes");
3698        let starts_custom_property = self.current_kind() == Some(SyntaxKind::CustomPropertyName);
3699        let has_colon = self.find_before_recovery(
3700            SyntaxKind::Colon,
3701            &[
3702                SyntaxKind::Semicolon,
3703                SyntaxKind::SassOptionalSemicolon,
3704                SyntaxKind::RightBrace,
3705                SyntaxKind::SassDedent,
3706                SyntaxKind::LeftBrace,
3707                SyntaxKind::SassIndent,
3708            ],
3709        );
3710        let kind = if starts_composes && has_colon {
3711            SyntaxKind::CssModuleComposesDeclaration
3712        } else if starts_composes {
3713            SyntaxKind::BogusComposesDeclaration
3714        } else if has_colon {
3715            SyntaxKind::Declaration
3716        } else {
3717            SyntaxKind::BogusDeclaration
3718        };
3719        self.builder.start_node(kind);
3720        if kind == SyntaxKind::CssModuleComposesDeclaration
3721            && self.current_css_module_scope_context() == Some("global")
3722        {
3723            self.error_at_current(
3724                ParseErrorCode::UnexpectedCharacter,
3725                "composes is not allowed inside :global scope",
3726            );
3727        }
3728        let property_kind = if matches!(
3729            self.current_kind(),
3730            Some(
3731                SyntaxKind::Colon
3732                    | SyntaxKind::Semicolon
3733                    | SyntaxKind::SassOptionalSemicolon
3734                    | SyntaxKind::LeftBrace
3735                    | SyntaxKind::SassIndent
3736                    | SyntaxKind::RightBrace
3737                    | SyntaxKind::SassDedent
3738            )
3739        ) {
3740            SyntaxKind::BogusPropertyName
3741        } else {
3742            SyntaxKind::PropertyName
3743        };
3744        self.builder.start_node(property_kind);
3745        while !self.at_end() {
3746            match self.current_kind() {
3747                Some(
3748                    SyntaxKind::Colon
3749                    | SyntaxKind::Semicolon
3750                    | SyntaxKind::SassOptionalSemicolon
3751                    | SyntaxKind::RightBrace
3752                    | SyntaxKind::SassDedent,
3753                ) => break,
3754                Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3755                    kind,
3756                    &[
3757                        SyntaxKind::Colon,
3758                        SyntaxKind::Semicolon,
3759                        SyntaxKind::SassOptionalSemicolon,
3760                        SyntaxKind::RightBrace,
3761                        SyntaxKind::SassDedent,
3762                    ],
3763                ),
3764                Some(_) => self.token_current(),
3765                None => break,
3766            }
3767        }
3768        self.builder.finish_node();
3769        if property_kind == SyntaxKind::BogusPropertyName {
3770            self.error_at_current(
3771                ParseErrorCode::UnexpectedCharacter,
3772                "expected declaration property name",
3773            );
3774        }
3775
3776        if self.current_kind() == Some(SyntaxKind::Colon) {
3777            self.token_current();
3778            let value_recovery = [
3779                SyntaxKind::Semicolon,
3780                SyntaxKind::SassOptionalSemicolon,
3781                SyntaxKind::RightBrace,
3782                SyntaxKind::SassDedent,
3783            ];
3784            let has_value = self
3785                .non_trivia_token_from(self.position)
3786                .is_some_and(|(_, kind)| !value_recovery.contains(&kind));
3787            self.builder.start_node(SyntaxKind::Value);
3788            if kind == SyntaxKind::CssModuleComposesDeclaration {
3789                self.parse_composes_value_until(&value_recovery);
3790            } else if starts_custom_property {
3791                self.builder.start_node(SyntaxKind::CustomPropertyValue);
3792                self.parse_component_value_list_until(&value_recovery);
3793                self.builder.finish_node();
3794            } else if !has_value {
3795                self.empty_bogus_node(
3796                    SyntaxKind::BogusValue,
3797                    ParseErrorCode::ExpectedValue,
3798                    "expected declaration value",
3799                );
3800            } else {
3801                self.parse_declaration_value_or_value_list_until(&value_recovery);
3802            }
3803            self.builder.finish_node();
3804        } else {
3805            self.consume_until_recovery(&[
3806                SyntaxKind::Semicolon,
3807                SyntaxKind::SassOptionalSemicolon,
3808                SyntaxKind::RightBrace,
3809                SyntaxKind::SassDedent,
3810            ]);
3811        }
3812
3813        if self.current_kind().is_some_and(is_statement_end) {
3814            self.token_current();
3815        }
3816        self.builder.finish_node();
3817    }
3818
3819    fn parse_composes_value_until(&mut self, recovery: &[SyntaxKind]) {
3820        let mut saw_target = false;
3821        if self.current_composes_value_has_multiple_from_clauses(recovery) {
3822            self.error_at_current(
3823                ParseErrorCode::UnexpectedCharacter,
3824                "multiple composes from clauses are not allowed",
3825            );
3826        }
3827        while !self.at_end() {
3828            self.eat_value_trivia();
3829            match self.current_kind() {
3830                Some(kind) if recovery.contains(&kind) => break,
3831                Some(SyntaxKind::Ident) if self.current_text() == Some("from") => {
3832                    if !saw_target {
3833                        self.empty_bogus_node(
3834                            SyntaxKind::BogusComposesTarget,
3835                            ParseErrorCode::UnexpectedCharacter,
3836                            "expected composes target before from clause",
3837                        );
3838                        saw_target = true;
3839                    }
3840                    self.parse_css_module_from_clause(recovery);
3841                }
3842                Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
3843                    self.builder.start_node(SyntaxKind::CssModuleComposesTarget);
3844                    self.token_current();
3845                    self.builder.finish_node();
3846                    saw_target = true;
3847                }
3848                Some(kind) if is_interpolation_start(kind) => {
3849                    self.parse_interpolation(kind, recovery)
3850                }
3851                Some(_) => self.token_current(),
3852                None => break,
3853            }
3854        }
3855        if !saw_target {
3856            self.empty_bogus_node(
3857                SyntaxKind::BogusComposesTarget,
3858                ParseErrorCode::UnexpectedCharacter,
3859                "expected composes target",
3860            );
3861        }
3862    }
3863
3864    fn current_composes_value_has_multiple_from_clauses(&self, recovery: &[SyntaxKind]) -> bool {
3865        let mut index = self.position;
3866        let mut paren_depth = 0usize;
3867        let mut bracket_depth = 0usize;
3868        let mut brace_depth = 0usize;
3869        let mut from_count = 0usize;
3870        while let Some(token) = self.tokens.get(index) {
3871            if paren_depth == 0
3872                && bracket_depth == 0
3873                && brace_depth == 0
3874                && recovery.contains(&token.kind)
3875            {
3876                break;
3877            }
3878            match token.kind {
3879                SyntaxKind::LeftParen => paren_depth += 1,
3880                SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
3881                SyntaxKind::LeftBracket => bracket_depth += 1,
3882                SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
3883                SyntaxKind::LeftBrace => brace_depth += 1,
3884                SyntaxKind::RightBrace => brace_depth = brace_depth.saturating_sub(1),
3885                SyntaxKind::Ident
3886                    if paren_depth == 0
3887                        && bracket_depth == 0
3888                        && brace_depth == 0
3889                        && token.text == "from" =>
3890                {
3891                    from_count += 1;
3892                    if from_count > 1 {
3893                        return true;
3894                    }
3895                }
3896                _ => {}
3897            }
3898            index += 1;
3899        }
3900        false
3901    }
3902
3903    fn parse_css_module_from_clause(&mut self, recovery: &[SyntaxKind]) {
3904        let source = self.non_trivia_token_from(self.position + 1);
3905        let has_source = source.is_some_and(|(_, kind)| !recovery.contains(&kind));
3906        let has_valid_source = source.is_some_and(|(index, kind)| {
3907            self.tokens
3908                .get(index)
3909                .is_some_and(|token| is_css_module_from_source_token(kind, token.text))
3910        });
3911        self.builder.start_node(if has_valid_source {
3912            SyntaxKind::CssModuleFromClause
3913        } else {
3914            SyntaxKind::BogusFromClause
3915        });
3916        self.token_current();
3917        while !self.at_end() {
3918            match self.current_kind() {
3919                Some(kind) if recovery.contains(&kind) => break,
3920                Some(_) => self.token_current(),
3921                None => break,
3922            }
3923        }
3924        if !has_source {
3925            self.error_at_current(
3926                ParseErrorCode::UnexpectedCharacter,
3927                "expected CSS Modules from-clause source",
3928            );
3929        } else if !has_valid_source {
3930            self.error_at_current(
3931                ParseErrorCode::ExpectedValue,
3932                "invalid CSS Modules from-clause source",
3933            );
3934        }
3935        self.builder.finish_node();
3936    }
3937
3938    fn current_css_module_scope_context(&self) -> Option<&'static str> {
3939        let mut open_blocks = Vec::new();
3940        for (index, token) in self.tokens.iter().take(self.position).enumerate() {
3941            match token.kind {
3942                SyntaxKind::LeftBrace | SyntaxKind::SassIndent => open_blocks.push(index),
3943                SyntaxKind::RightBrace | SyntaxKind::SassDedent => {
3944                    open_blocks.pop();
3945                }
3946                _ => {}
3947            }
3948        }
3949
3950        if let Some(scope) = open_blocks.iter().copied().find_map(|block_start| {
3951            let header_start = self.header_start_for_block(block_start);
3952            css_module_block_scope_marker_in_header(&self.tokens, header_start, block_start)
3953        }) {
3954            return Some(scope);
3955        }
3956
3957        let block_start = open_blocks.last().copied()?;
3958        let header_start = self.header_start_for_block(block_start);
3959        css_module_header_is_global_only(&self.tokens, header_start, block_start)
3960            .then_some("global")
3961    }
3962
3963    fn header_start_for_block(&self, block_start: usize) -> usize {
3964        let mut index = block_start;
3965        while index > 0 {
3966            let previous = index - 1;
3967            if matches!(
3968                self.tokens[previous].kind,
3969                SyntaxKind::LeftBrace
3970                    | SyntaxKind::RightBrace
3971                    | SyntaxKind::SassIndent
3972                    | SyntaxKind::SassDedent
3973                    | SyntaxKind::Semicolon
3974                    | SyntaxKind::SassOptionalSemicolon
3975            ) {
3976                break;
3977            }
3978            index = previous;
3979        }
3980        index
3981    }
3982
3983    fn parse_dialect_at_rule(&mut self) {
3984        let Some(spec) = self.current_dialect_at_rule_spec() else {
3985            self.parse_at_rule();
3986            return;
3987        };
3988
3989        self.builder
3990            .start_node(self.current_dialect_at_rule_node_kind(spec));
3991        if self.current_kind() == Some(SyntaxKind::AtKeyword) {
3992            self.token_current();
3993        }
3994        if matches!(
3995            spec.node_kind,
3996            SyntaxKind::ScssUseRule | SyntaxKind::ScssForwardRule
3997        ) {
3998            self.parse_scss_module_prelude(spec.node_kind);
3999        }
4000        if is_scss_control_rule_kind(spec.node_kind)
4001            && !self.current_scss_control_prelude_is_valid(spec.node_kind)
4002        {
4003            self.error_at_current(
4004                ParseErrorCode::ExpectedValue,
4005                "invalid SCSS control prelude",
4006            );
4007        }
4008        while !self.at_end() {
4009            match self.current_kind() {
4010                Some(kind) if is_statement_end(kind) => {
4011                    self.token_current();
4012                    break;
4013                }
4014                Some(SyntaxKind::LeftBrace) => {
4015                    match spec.block_kind {
4016                        AtRuleBlockKind::GroupRuleList => self.parse_group_at_rule_block(),
4017                        AtRuleBlockKind::DeclarationList => self.parse_declaration_block(),
4018                        AtRuleBlockKind::Keyframes => self.parse_keyframes_block(),
4019                        AtRuleBlockKind::Raw => self.consume_balanced_block(),
4020                    }
4021                    break;
4022                }
4023                Some(SyntaxKind::SassIndent) => {
4024                    self.parse_sass_indented_at_rule_block(spec.block_kind);
4025                    break;
4026                }
4027                Some(_) => self.token_current(),
4028                None => break,
4029            }
4030        }
4031        self.builder.finish_node();
4032    }
4033
4034    fn parse_scss_module_prelude(&mut self, node_kind: SyntaxKind) {
4035        self.validate_scss_module_prelude(node_kind);
4036        while !self.at_end() {
4037            match self.current_kind() {
4038                Some(kind)
4039                    if is_statement_end(kind)
4040                        || kind == SyntaxKind::LeftBrace
4041                        || kind == SyntaxKind::SassIndent =>
4042                {
4043                    break;
4044                }
4045                Some(SyntaxKind::Ident | SyntaxKind::KeywordWith)
4046                    if self.current_text() == Some("with")
4047                        && self
4048                            .non_trivia_token_from(self.position + 1)
4049                            .is_some_and(|(_, kind)| kind == SyntaxKind::LeftParen) =>
4050                {
4051                    self.parse_scss_module_config()
4052                }
4053                Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
4054                    kind,
4055                    &[
4056                        SyntaxKind::Semicolon,
4057                        SyntaxKind::SassOptionalSemicolon,
4058                        SyntaxKind::LeftBrace,
4059                        SyntaxKind::SassIndent,
4060                    ],
4061                ),
4062                Some(_) => self.token_current(),
4063                None => break,
4064            }
4065        }
4066    }
4067
4068    fn validate_scss_module_prelude(&mut self, node_kind: SyntaxKind) {
4069        let recovery = [
4070            SyntaxKind::Semicolon,
4071            SyntaxKind::SassOptionalSemicolon,
4072            SyntaxKind::LeftBrace,
4073            SyntaxKind::SassIndent,
4074        ];
4075        let Some((source_index, source_kind)) = self.non_trivia_token_from(self.position) else {
4076            self.error_at_current(ParseErrorCode::ExpectedValue, "expected SCSS module source");
4077            return;
4078        };
4079        if recovery.contains(&source_kind) || !is_scss_module_source_token(source_kind) {
4080            let range = self
4081                .tokens
4082                .get(source_index)
4083                .map(|token| token.range)
4084                .unwrap_or_else(|| self.current_range());
4085            self.errors.push(ParseError {
4086                code: ParseErrorCode::ExpectedValue,
4087                range,
4088                message: "expected SCSS module source",
4089            });
4090        }
4091
4092        let mut index = source_index;
4093        while let Some(token) = self.tokens.get(index).copied() {
4094            if recovery.contains(&token.kind) {
4095                break;
4096            }
4097            if token.kind == SyntaxKind::Ident {
4098                if token.text.eq_ignore_ascii_case("as") {
4099                    let next_kind = self.non_trivia_token_from(index + 1).map(|(_, kind)| kind);
4100                    if next_kind.is_none_or(|kind| {
4101                        recovery.contains(&kind) || !is_scss_module_namespace_token(kind)
4102                    }) {
4103                        self.errors.push(ParseError {
4104                            code: ParseErrorCode::ExpectedValue,
4105                            range: token.range,
4106                            message: "expected SCSS module namespace",
4107                        });
4108                    }
4109                } else if token.text.eq_ignore_ascii_case("with") {
4110                    let next_kind = self.non_trivia_token_from(index + 1).map(|(_, kind)| kind);
4111                    if next_kind != Some(SyntaxKind::LeftParen) {
4112                        self.errors.push(ParseError {
4113                            code: ParseErrorCode::ExpectedValue,
4114                            range: token.range,
4115                            message: "expected SCSS module configuration",
4116                        });
4117                    }
4118                } else if matches_ignore_ascii_case(token.text, &["show", "hide"]) {
4119                    if node_kind != SyntaxKind::ScssForwardRule {
4120                        self.errors.push(ParseError {
4121                            code: ParseErrorCode::UnexpectedCharacter,
4122                            range: token.range,
4123                            message: "unexpected SCSS module visibility clause",
4124                        });
4125                    }
4126                    let next_kind = self.non_trivia_token_from(index + 1).map(|(_, kind)| kind);
4127                    if next_kind.is_none_or(|kind| {
4128                        recovery.contains(&kind) || !is_scss_module_visibility_name_token(kind)
4129                    }) {
4130                        self.errors.push(ParseError {
4131                            code: ParseErrorCode::ExpectedValue,
4132                            range: token.range,
4133                            message: "expected SCSS module visibility name",
4134                        });
4135                    }
4136                }
4137            }
4138            index += 1;
4139        }
4140    }
4141
4142    fn current_scss_control_prelude_is_valid(&self, node_kind: SyntaxKind) -> bool {
4143        let recovery = [
4144            SyntaxKind::LeftBrace,
4145            SyntaxKind::SassIndent,
4146            SyntaxKind::Semicolon,
4147            SyntaxKind::SassOptionalSemicolon,
4148            SyntaxKind::RightBrace,
4149            SyntaxKind::SassDedent,
4150        ];
4151        match node_kind {
4152            SyntaxKind::ScssControlIf | SyntaxKind::ScssControlWhile => self
4153                .non_trivia_token_from(self.position)
4154                .is_some_and(|(_, kind)| !recovery.contains(&kind)),
4155            SyntaxKind::ScssControlFor => {
4156                self.non_trivia_token_from(self.position)
4157                    .is_some_and(|(_, kind)| kind == SyntaxKind::ScssVariable)
4158                    && self.find_text_before_recovery("from", &recovery)
4159                    && (self.find_text_before_recovery("to", &recovery)
4160                        || self.find_text_before_recovery("through", &recovery))
4161            }
4162            SyntaxKind::ScssControlEach => {
4163                self.non_trivia_token_from(self.position)
4164                    .is_some_and(|(_, kind)| kind == SyntaxKind::ScssVariable)
4165                    && self.find_text_before_recovery("in", &recovery)
4166            }
4167            SyntaxKind::ScssControlElse => true,
4168            _ => true,
4169        }
4170    }
4171
4172    fn parse_scss_module_config(&mut self) {
4173        let has_balanced_config = self.current_scss_module_config_has_balanced_parens();
4174        self.builder.start_node(if has_balanced_config {
4175            SyntaxKind::ScssModuleConfig
4176        } else {
4177            SyntaxKind::BogusScssModuleConfig
4178        });
4179        self.token_current();
4180        self.eat_trivia();
4181        if self.current_kind() == Some(SyntaxKind::LeftParen) {
4182            self.parse_balanced_parenthesized_prelude_until(
4183                None,
4184                &[
4185                    SyntaxKind::LeftBrace,
4186                    SyntaxKind::SassIndent,
4187                    SyntaxKind::Semicolon,
4188                    SyntaxKind::SassOptionalSemicolon,
4189                ],
4190            );
4191        }
4192        self.builder.finish_node();
4193    }
4194
4195    fn parse_css_module_value_rule(&mut self) {
4196        let has_name = self
4197            .non_trivia_token_from(self.position + 1)
4198            .and_then(|(index, kind)| {
4199                self.tokens
4200                    .get(index)
4201                    .map(|token| (kind, token.text != "from"))
4202            })
4203            .is_some_and(|(kind, allowed_name)| {
4204                allowed_name && matches!(kind, SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
4205            });
4206        let has_from = self.find_text_before_recovery(
4207            "from",
4208            &[
4209                SyntaxKind::Semicolon,
4210                SyntaxKind::SassOptionalSemicolon,
4211                SyntaxKind::LeftBrace,
4212                SyntaxKind::SassIndent,
4213            ],
4214        );
4215        let has_colon = self.find_before_recovery(
4216            SyntaxKind::Colon,
4217            &[
4218                SyntaxKind::Semicolon,
4219                SyntaxKind::SassOptionalSemicolon,
4220                SyntaxKind::LeftBrace,
4221                SyntaxKind::SassIndent,
4222            ],
4223        );
4224        let kind = if !has_name {
4225            SyntaxKind::BogusCssModuleBlock
4226        } else if has_from && !has_colon {
4227            SyntaxKind::CssModuleImportBlock
4228        } else {
4229            SyntaxKind::CssModuleExportBlock
4230        };
4231
4232        self.builder.start_node(kind);
4233        self.token_current();
4234        if !has_name {
4235            self.error_at_current(
4236                ParseErrorCode::UnexpectedCharacter,
4237                "expected CSS Modules @value name",
4238            );
4239        }
4240        if has_colon {
4241            self.parse_css_module_value_export();
4242        } else {
4243            self.parse_css_module_value_import_or_statement();
4244        }
4245        if self.current_kind().is_some_and(is_statement_end) {
4246            self.token_current();
4247        }
4248        self.builder.finish_node();
4249    }
4250
4251    fn parse_css_module_value_export(&mut self) {
4252        self.parse_css_module_token_definitions_until(&[
4253            SyntaxKind::Colon,
4254            SyntaxKind::Semicolon,
4255            SyntaxKind::SassOptionalSemicolon,
4256        ]);
4257        if self.current_kind() == Some(SyntaxKind::Colon) {
4258            self.token_current();
4259            self.builder.start_node(SyntaxKind::Value);
4260            self.parse_css_module_token_references_until(&[
4261                SyntaxKind::Semicolon,
4262                SyntaxKind::SassOptionalSemicolon,
4263            ]);
4264            self.builder.finish_node();
4265        }
4266    }
4267
4268    fn parse_css_module_value_import_or_statement(&mut self) {
4269        self.parse_css_module_token_definitions_until(&[
4270            SyntaxKind::Semicolon,
4271            SyntaxKind::SassOptionalSemicolon,
4272        ]);
4273    }
4274
4275    fn parse_css_module_token_definitions_until(&mut self, recovery: &[SyntaxKind]) {
4276        while !self.at_end() {
4277            match self.current_kind() {
4278                Some(kind) if recovery.contains(&kind) => break,
4279                Some(SyntaxKind::Ident) if self.current_text() == Some("from") => {
4280                    self.parse_css_module_from_clause(recovery);
4281                    break;
4282                }
4283                Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
4284                    self.builder.start_node(SyntaxKind::TokenDefinition);
4285                    self.token_current();
4286                    self.builder.finish_node();
4287                }
4288                Some(_) => self.token_current(),
4289                None => break,
4290            }
4291        }
4292    }
4293
4294    fn parse_css_module_token_references_until(&mut self, recovery: &[SyntaxKind]) {
4295        while !self.at_end() {
4296            self.eat_value_trivia();
4297            match self.current_kind() {
4298                Some(kind) if recovery.contains(&kind) => break,
4299                Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
4300                    self.builder.start_node(SyntaxKind::TokenReference);
4301                    self.token_current();
4302                    self.builder.finish_node();
4303                }
4304                Some(kind) if is_interpolation_start(kind) => {
4305                    self.parse_interpolation(kind, recovery)
4306                }
4307                Some(_) => self.token_current(),
4308                None => break,
4309            }
4310        }
4311    }
4312
4313    fn parse_less_mixin_header(&mut self) {
4314        self.builder.start_node(SyntaxKind::SelectorList);
4315        self.parse_until_recovery_with_optional_less_guard(&[SyntaxKind::LeftBrace]);
4316        self.builder.finish_node();
4317    }
4318
4319    fn parse_less_mixin_call(&mut self) {
4320        self.builder.start_node(SyntaxKind::LessMixinCall);
4321        self.parse_until_recovery_with_optional_less_guard(&[
4322            SyntaxKind::Semicolon,
4323            SyntaxKind::SassOptionalSemicolon,
4324            SyntaxKind::RightBrace,
4325            SyntaxKind::SassDedent,
4326        ]);
4327        if self.current_kind().is_some_and(is_statement_end) {
4328            self.token_current();
4329        }
4330        self.builder.finish_node();
4331    }
4332
4333    fn parse_less_namespace_access(&mut self) {
4334        self.builder.start_node(SyntaxKind::LessNamespaceAccess);
4335        while !self.at_end() {
4336            match self.current_kind() {
4337                Some(
4338                    SyntaxKind::Semicolon
4339                    | SyntaxKind::SassOptionalSemicolon
4340                    | SyntaxKind::RightBrace
4341                    | SyntaxKind::SassDedent
4342                    | SyntaxKind::LeftBrace
4343                    | SyntaxKind::SassIndent,
4344                ) => break,
4345                Some(_) if self.current_starts_less_mixin_call() => {
4346                    self.parse_less_mixin_call();
4347                    break;
4348                }
4349                Some(_) => self.token_current(),
4350                None => break,
4351            }
4352        }
4353        if self.current_kind().is_some_and(is_statement_end) {
4354            self.token_current();
4355        }
4356        self.builder.finish_node();
4357    }
4358
4359    fn parse_until_recovery_with_optional_less_guard(&mut self, recovery: &[SyntaxKind]) {
4360        let mut guard_open = false;
4361        while !self.at_end() {
4362            match self.current_kind() {
4363                Some(kind) if recovery.contains(&kind) => break,
4364                Some(SyntaxKind::Ident) if self.current_text() == Some("when") && !guard_open => {
4365                    self.builder.start_node(
4366                        if self.current_less_guard_has_condition_before(recovery) {
4367                            SyntaxKind::LessMixinGuard
4368                        } else {
4369                            SyntaxKind::BogusLessGuard
4370                        },
4371                    );
4372                    guard_open = true;
4373                    self.token_current();
4374                }
4375                Some(_) => self.token_current(),
4376                None => break,
4377            }
4378        }
4379        if guard_open {
4380            self.builder.finish_node();
4381        }
4382    }
4383
4384    fn parse_value_until(&mut self, recovery: &[SyntaxKind]) {
4385        while !self.at_end() {
4386            self.eat_value_trivia();
4387            if matches!(self.current_kind(), Some(kind) if recovery.contains(&kind)) {
4388                break;
4389            }
4390            if self.at_end() {
4391                break;
4392            }
4393            self.parse_value_expression(0, recovery);
4394        }
4395    }
4396
4397    fn parse_value_or_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4398        if self.current_value_has_top_level_comma_before(recovery) {
4399            self.parse_value_list_until(recovery);
4400        } else {
4401            self.parse_value_until(recovery);
4402        }
4403    }
4404
4405    fn parse_declaration_value_or_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4406        if self.current_value_has_top_level_comma_before(recovery) {
4407            self.parse_declaration_value_list_until(recovery);
4408        } else {
4409            self.parse_declaration_value_until(recovery);
4410        }
4411    }
4412
4413    fn parse_declaration_value_until(&mut self, recovery: &[SyntaxKind]) {
4414        let mut saw_value = false;
4415        while !self.at_end() {
4416            self.eat_value_trivia();
4417            if matches!(self.current_kind(), Some(kind) if recovery.contains(&kind)) {
4418                break;
4419            }
4420            if saw_value && self.current_starts_missing_semicolon_declaration(recovery) {
4421                self.error_at_current(
4422                    ParseErrorCode::UnexpectedCharacter,
4423                    "expected semicolon between declarations",
4424                );
4425                break;
4426            }
4427            if self.at_end() {
4428                break;
4429            }
4430            self.parse_value_expression(0, recovery);
4431            saw_value = true;
4432        }
4433    }
4434
4435    fn parse_declaration_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4436        self.builder
4437            .start_node(if self.current_value_list_is_bogus(recovery) {
4438                SyntaxKind::BogusValueList
4439            } else {
4440                SyntaxKind::ValueList
4441            });
4442        let item_recovery = value_list_item_recovery(recovery);
4443        let mut saw_item = false;
4444        while !self.at_end() {
4445            self.eat_value_trivia();
4446            match self.current_kind() {
4447                Some(kind) if recovery.contains(&kind) => break,
4448                Some(SyntaxKind::Comma) => self.token_current(),
4449                Some(_)
4450                    if saw_item && self.current_starts_missing_semicolon_declaration(recovery) =>
4451                {
4452                    self.error_at_current(
4453                        ParseErrorCode::UnexpectedCharacter,
4454                        "expected semicolon between declarations",
4455                    );
4456                    break;
4457                }
4458                Some(_) => {
4459                    self.parse_value_expression(0, &item_recovery);
4460                    saw_item = true;
4461                }
4462                None => break,
4463            }
4464        }
4465        self.builder.finish_node();
4466    }
4467
4468    fn parse_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4469        self.builder
4470            .start_node(if self.current_value_list_is_bogus(recovery) {
4471                SyntaxKind::BogusValueList
4472            } else {
4473                SyntaxKind::ValueList
4474            });
4475        let item_recovery = value_list_item_recovery(recovery);
4476        while !self.at_end() {
4477            self.eat_value_trivia();
4478            match self.current_kind() {
4479                Some(kind) if recovery.contains(&kind) => break,
4480                Some(SyntaxKind::Comma) => self.token_current(),
4481                Some(_) => self.parse_value_expression(0, &item_recovery),
4482                None => break,
4483            }
4484        }
4485        self.builder.finish_node();
4486    }
4487
4488    fn parse_component_value(&mut self, recovery: &[SyntaxKind]) {
4489        self.builder.start_node(SyntaxKind::ComponentValue);
4490        self.parse_component_value_inner(recovery);
4491        self.builder.finish_node();
4492    }
4493
4494    fn parse_component_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4495        self.builder.start_node(SyntaxKind::ComponentValueList);
4496        while !self.at_end() {
4497            self.eat_value_trivia();
4498            match self.current_kind() {
4499                Some(kind) if recovery.contains(&kind) => break,
4500                Some(_) => self.parse_component_value(recovery),
4501                None => break,
4502            }
4503        }
4504        self.builder.finish_node();
4505    }
4506
4507    fn parse_comma_separated_component_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4508        self.builder
4509            .start_node(SyntaxKind::CommaSeparatedComponentValueList);
4510        let item_recovery = comma_separated_component_value_list_item_recovery(recovery);
4511        while !self.at_end() {
4512            self.eat_value_trivia();
4513            match self.current_kind() {
4514                Some(kind) if recovery.contains(&kind) => break,
4515                Some(SyntaxKind::Comma) => self.token_current(),
4516                Some(_) => self.parse_component_value(&item_recovery),
4517                None => break,
4518            }
4519        }
4520        self.builder.finish_node();
4521    }
4522
4523    fn parse_component_value_inner(&mut self, recovery: &[SyntaxKind]) {
4524        self.eat_value_trivia();
4525        match self.current_kind() {
4526            Some(kind) if recovery.contains(&kind) => {
4527                self.empty_bogus_node(
4528                    SyntaxKind::BogusValue,
4529                    ParseErrorCode::ExpectedValue,
4530                    "expected component value",
4531                );
4532            }
4533            Some(SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen) => {
4534                self.parse_simple_block(recovery)
4535            }
4536            Some(SyntaxKind::Ident) if self.next_kind() == Some(SyntaxKind::LeftParen) => {
4537                self.parse_function_call(recovery)
4538            }
4539            Some(kind) if is_component_value_atom_start(kind) => self.parse_value_prefix(recovery),
4540            Some(_) => self.token_current(),
4541            None => {
4542                self.empty_bogus_node(
4543                    SyntaxKind::BogusValue,
4544                    ParseErrorCode::ExpectedValue,
4545                    "expected component value",
4546                );
4547            }
4548        }
4549    }
4550
4551    fn parse_simple_block_entry_point(&mut self, recovery: &[SyntaxKind]) {
4552        self.eat_value_trivia();
4553        match self.current_kind() {
4554            Some(SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen) => {
4555                self.parse_simple_block(recovery)
4556            }
4557            Some(_) | None => {
4558                self.empty_bogus_node(
4559                    SyntaxKind::BogusSimpleBlock,
4560                    ParseErrorCode::ExpectedValue,
4561                    "expected simple block",
4562                );
4563            }
4564        }
4565    }
4566
4567    fn parse_simple_block(&mut self, recovery: &[SyntaxKind]) {
4568        let Some(open_kind) = self.current_kind() else {
4569            self.empty_bogus_node(
4570                SyntaxKind::BogusSimpleBlock,
4571                ParseErrorCode::ExpectedValue,
4572                "expected simple block",
4573            );
4574            return;
4575        };
4576        let Some(close_kind) = matching_simple_block_close(open_kind) else {
4577            self.empty_bogus_node(
4578                SyntaxKind::BogusSimpleBlock,
4579                ParseErrorCode::ExpectedValue,
4580                "expected simple block",
4581            );
4582            return;
4583        };
4584
4585        let block_kind = if self.current_simple_block_has_matching_close(recovery) {
4586            SyntaxKind::SimpleBlock
4587        } else {
4588            SyntaxKind::BogusSimpleBlock
4589        };
4590        self.builder.start_node(block_kind);
4591        self.token_current();
4592
4593        let block_recovery = simple_block_recovery(close_kind, recovery);
4594        while !self.at_end() {
4595            self.eat_value_trivia();
4596            match self.current_kind() {
4597                Some(kind) if kind == close_kind => break,
4598                Some(kind) if recovery.contains(&kind) => break,
4599                Some(_) => self.parse_component_value(&block_recovery),
4600                None => break,
4601            }
4602        }
4603
4604        if self.current_kind() == Some(close_kind) {
4605            self.token_current();
4606        } else {
4607            self.error_at_current(
4608                ParseErrorCode::UnexpectedCharacter,
4609                "unterminated simple block",
4610            );
4611        }
4612        self.builder.finish_node();
4613    }
4614
4615    fn parse_value_expression(&mut self, min_binding_power: u8, recovery: &[SyntaxKind]) {
4616        self.eat_value_trivia();
4617        let checkpoint = self.builder.checkpoint();
4618        self.parse_value_prefix(recovery);
4619
4620        loop {
4621            self.eat_value_trivia();
4622            let Some(operator) = self.current_kind() else {
4623                break;
4624            };
4625            if recovery.contains(&operator) {
4626                break;
4627            }
4628            let Some((left_binding_power, right_binding_power)) = infix_binding_power(operator)
4629            else {
4630                break;
4631            };
4632            if left_binding_power < min_binding_power {
4633                break;
4634            }
4635
4636            self.builder
4637                .start_node_at(checkpoint, SyntaxKind::BinaryExpression);
4638            self.token_current();
4639            self.parse_value_expression(right_binding_power, recovery);
4640            self.builder.finish_node();
4641        }
4642    }
4643
4644    fn parse_value_prefix(&mut self, recovery: &[SyntaxKind]) {
4645        match self.current_kind() {
4646            Some(SyntaxKind::Plus | SyntaxKind::Minus) => {
4647                self.builder.start_node(SyntaxKind::UnaryExpression);
4648                self.token_current();
4649                self.parse_value_expression(5, recovery);
4650                self.builder.finish_node();
4651            }
4652            Some(SyntaxKind::Ident)
4653                if self
4654                    .current_text()
4655                    .is_some_and(|text| text.eq_ignore_ascii_case("url"))
4656                    && self.next_kind() == Some(SyntaxKind::LeftParen) =>
4657            {
4658                self.builder.start_node(SyntaxKind::UrlValue);
4659                self.parse_function_call(recovery);
4660                self.builder.finish_node();
4661            }
4662            Some(SyntaxKind::Ident) if self.next_kind() == Some(SyntaxKind::LeftParen) => {
4663                self.parse_function_call(recovery)
4664            }
4665            Some(SyntaxKind::Number) => {
4666                self.builder.start_node(SyntaxKind::NumberValue);
4667                self.token_current();
4668                self.builder.finish_node();
4669            }
4670            Some(SyntaxKind::Percentage) => {
4671                self.builder.start_node(SyntaxKind::PercentageValue);
4672                self.token_current();
4673                self.builder.finish_node();
4674            }
4675            Some(SyntaxKind::Dimension) => {
4676                self.builder.start_node(SyntaxKind::DimensionValue);
4677                self.token_current();
4678                self.builder.finish_node();
4679            }
4680            Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
4681                self.builder.start_node(SyntaxKind::IdentifierValue);
4682                self.token_current();
4683                self.builder.finish_node();
4684            }
4685            Some(SyntaxKind::String | SyntaxKind::LessEscapedString) => {
4686                self.builder.start_node(SyntaxKind::StringValue);
4687                self.token_current();
4688                self.builder.finish_node();
4689            }
4690            Some(SyntaxKind::UnicodeRange) => {
4691                self.builder.start_node(SyntaxKind::UnicodeRangeValue);
4692                self.token_current();
4693                self.builder.finish_node();
4694            }
4695            Some(SyntaxKind::Hash) => {
4696                self.builder.start_node(SyntaxKind::ColorValue);
4697                self.token_current();
4698                self.builder.finish_node();
4699            }
4700            Some(SyntaxKind::Url) => {
4701                self.builder.start_node(SyntaxKind::UrlValue);
4702                self.token_current();
4703                self.builder.finish_node();
4704            }
4705            Some(SyntaxKind::BadUrl) => {
4706                self.builder.start_node(SyntaxKind::BogusValue);
4707                self.token_current();
4708                self.builder.finish_node();
4709            }
4710            Some(SyntaxKind::BadString) => {
4711                self.builder.start_node(SyntaxKind::BogusValue);
4712                self.token_current();
4713                self.builder.finish_node();
4714            }
4715            Some(SyntaxKind::Important) => {
4716                self.builder.start_node(SyntaxKind::ImportantAnnotation);
4717                self.token_current();
4718                self.builder.finish_node();
4719            }
4720            Some(SyntaxKind::Delim) if self.current_split_important_annotation() => {
4721                self.parse_split_important_annotation()
4722            }
4723            Some(SyntaxKind::Delim) if self.current_scss_variable_flag_annotation() => {
4724                self.parse_scss_variable_flag_annotation()
4725            }
4726            Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(kind, recovery),
4727            Some(SyntaxKind::ScssVariable) => {
4728                self.builder.start_node(SyntaxKind::ScssVariableReference);
4729                self.token_current();
4730                self.builder.finish_node();
4731            }
4732            Some(SyntaxKind::LessVariable) => {
4733                self.builder.start_node(SyntaxKind::LessVariableReference);
4734                self.token_current();
4735                self.builder.finish_node();
4736            }
4737            Some(SyntaxKind::LessPropertyVariableToken) => {
4738                self.builder.start_node(SyntaxKind::LessPropertyVariable);
4739                self.token_current();
4740                self.builder.finish_node();
4741            }
4742            Some(SyntaxKind::LeftBrace) => self.parse_simple_block(recovery),
4743            Some(SyntaxKind::LeftParen) => self.parse_parenthesized_expression(recovery),
4744            Some(SyntaxKind::LeftBracket) => self.parse_bracketed_value(recovery),
4745            Some(kind) if recovery.contains(&kind) => {
4746                self.empty_bogus_node(
4747                    SyntaxKind::BogusValue,
4748                    ParseErrorCode::ExpectedValue,
4749                    "expected value",
4750                );
4751            }
4752            Some(SyntaxKind::Delim) => {
4753                self.builder.start_node(SyntaxKind::BogusToken);
4754                self.token_current();
4755                self.builder.finish_node();
4756            }
4757            Some(_) => {
4758                self.builder.start_node(SyntaxKind::BogusValue);
4759                self.error_at_current(ParseErrorCode::ExpectedValue, "expected value");
4760                self.token_current();
4761                self.builder.finish_node();
4762            }
4763            None => {
4764                self.empty_bogus_node(
4765                    SyntaxKind::BogusValue,
4766                    ParseErrorCode::ExpectedValue,
4767                    "expected value",
4768                );
4769            }
4770        }
4771    }
4772
4773    fn parse_split_important_annotation(&mut self) {
4774        self.builder.start_node(SyntaxKind::ImportantAnnotation);
4775        self.token_current();
4776        self.eat_value_trivia();
4777        if self
4778            .current_text()
4779            .is_some_and(|text| text.eq_ignore_ascii_case("important"))
4780        {
4781            self.token_current();
4782        }
4783        self.builder.finish_node();
4784    }
4785
4786    fn parse_scss_variable_flag_annotation(&mut self) {
4787        self.builder.start_node(SyntaxKind::ScssVariableFlag);
4788        self.token_current();
4789        self.eat_value_trivia();
4790        self.token_current();
4791        self.builder.finish_node();
4792    }
4793
4794    fn eat_value_trivia(&mut self) {
4795        while matches!(self.current_kind(), Some(kind) if kind.is_trivia()) {
4796            self.token_current();
4797        }
4798    }
4799
4800    fn parse_function_call(&mut self, recovery: &[SyntaxKind]) {
4801        let function_name = self.current_text().map(str::to_owned);
4802        let function_range = self.current_range();
4803        let argument_count = self.current_function_top_level_argument_count_before(recovery);
4804        let has_empty_argument_slot =
4805            self.current_function_has_empty_top_level_argument_slot_before(recovery);
4806        let argument_head = self.current_function_first_argument_token_before(recovery);
4807        let specialized_kind = function_name.as_deref().and_then(specialized_function_kind);
4808        let closed = self.current_function_has_closing_paren_before(recovery);
4809        let function_kind = if closed {
4810            SyntaxKind::FunctionCall
4811        } else {
4812            SyntaxKind::BogusFunctionCall
4813        };
4814        let arguments_kind = if closed {
4815            SyntaxKind::FunctionArguments
4816        } else {
4817            SyntaxKind::BogusFunctionArguments
4818        };
4819
4820        self.builder.start_node(function_kind);
4821        if let Some(kind) = specialized_kind {
4822            self.builder.start_node(kind);
4823        }
4824        self.token_current();
4825        if self.current_kind() == Some(SyntaxKind::LeftParen) {
4826            self.token_current();
4827            self.builder.start_node(arguments_kind);
4828            let argument_recovery = function_argument_recovery(recovery);
4829            self.parse_value_or_value_list_until(&argument_recovery);
4830            self.builder.finish_node();
4831            if self.current_kind() == Some(SyntaxKind::RightParen) {
4832                self.token_current();
4833            } else {
4834                self.error_at_current(
4835                    ParseErrorCode::UnexpectedCharacter,
4836                    "unterminated function call",
4837                );
4838            }
4839        }
4840        if let Some(function_name) = function_name {
4841            if let Some(argument_count) = argument_count {
4842                self.validate_function_argument_count(
4843                    &function_name,
4844                    argument_count,
4845                    function_range,
4846                );
4847            }
4848            if let Some(true) = has_empty_argument_slot {
4849                self.validate_function_argument_slots(&function_name, function_range);
4850            }
4851            self.validate_function_argument_head(&function_name, argument_head, function_range);
4852        }
4853        if specialized_kind.is_some() {
4854            self.builder.finish_node();
4855        }
4856        self.builder.finish_node();
4857    }
4858
4859    fn current_function_top_level_argument_count_before(
4860        &self,
4861        recovery: &[SyntaxKind],
4862    ) -> Option<usize> {
4863        if self.next_kind() != Some(SyntaxKind::LeftParen) {
4864            return None;
4865        }
4866
4867        let mut index = self.position + 2;
4868        let mut depth = 0usize;
4869        let mut comma_count = 0usize;
4870        let mut saw_argument = false;
4871        while let Some(token) = self.tokens.get(index) {
4872            match token.kind {
4873                kind if depth == 0 && recovery.contains(&kind) => return None,
4874                SyntaxKind::RightParen if depth == 0 => {
4875                    return Some(if saw_argument { comma_count + 1 } else { 0 });
4876                }
4877                SyntaxKind::Comma if depth == 0 => {
4878                    comma_count += 1;
4879                    saw_argument = false;
4880                }
4881                kind if kind.is_trivia() => {}
4882                SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen => {
4883                    depth += 1;
4884                    saw_argument = true;
4885                }
4886                SyntaxKind::RightBrace | SyntaxKind::RightBracket | SyntaxKind::RightParen => {
4887                    depth = depth.saturating_sub(1);
4888                    saw_argument = true;
4889                }
4890                _ => saw_argument = true,
4891            }
4892            index += 1;
4893        }
4894        None
4895    }
4896
4897    fn current_function_has_empty_top_level_argument_slot_before(
4898        &self,
4899        recovery: &[SyntaxKind],
4900    ) -> Option<bool> {
4901        if self.next_kind() != Some(SyntaxKind::LeftParen) {
4902            return None;
4903        }
4904
4905        let mut index = self.position + 2;
4906        let mut depth = 0usize;
4907        let mut expecting_argument = true;
4908        let mut saw_argument = false;
4909        while let Some(token) = self.tokens.get(index) {
4910            match token.kind {
4911                kind if depth == 0 && recovery.contains(&kind) => return None,
4912                SyntaxKind::RightParen if depth == 0 => {
4913                    return Some(expecting_argument && saw_argument);
4914                }
4915                SyntaxKind::Comma if depth == 0 => {
4916                    if expecting_argument {
4917                        return Some(true);
4918                    }
4919                    expecting_argument = true;
4920                }
4921                kind if kind.is_trivia() => {}
4922                SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen => {
4923                    depth += 1;
4924                    expecting_argument = false;
4925                    saw_argument = true;
4926                }
4927                SyntaxKind::RightBrace | SyntaxKind::RightBracket | SyntaxKind::RightParen => {
4928                    depth = depth.saturating_sub(1);
4929                    expecting_argument = false;
4930                    saw_argument = true;
4931                }
4932                _ => {
4933                    expecting_argument = false;
4934                    saw_argument = true;
4935                }
4936            }
4937            index += 1;
4938        }
4939        None
4940    }
4941
4942    fn current_function_first_argument_token_before(
4943        &self,
4944        recovery: &[SyntaxKind],
4945    ) -> Option<Token<'text>> {
4946        if self.next_kind() != Some(SyntaxKind::LeftParen) {
4947            return None;
4948        }
4949
4950        let mut index = self.position + 2;
4951        while let Some(token) = self.tokens.get(index).copied() {
4952            match token.kind {
4953                kind if recovery.contains(&kind) => return None,
4954                SyntaxKind::RightParen => return None,
4955                kind if kind.is_trivia() => {}
4956                _ => return Some(token),
4957            }
4958            index += 1;
4959        }
4960        None
4961    }
4962
4963    fn validate_function_argument_count(
4964        &mut self,
4965        function_name: &str,
4966        argument_count: usize,
4967        range: TextRange,
4968    ) {
4969        if function_argument_count_is_valid(function_name, argument_count) {
4970            return;
4971        }
4972        self.errors.push(ParseError {
4973            code: ParseErrorCode::ExpectedValue,
4974            range,
4975            message: "invalid function argument count",
4976        });
4977    }
4978
4979    fn validate_function_argument_slots(&mut self, function_name: &str, range: TextRange) {
4980        if !function_requires_filled_top_level_arguments(function_name) {
4981            return;
4982        }
4983        self.errors.push(ParseError {
4984            code: ParseErrorCode::ExpectedValue,
4985            range,
4986            message: "empty function argument",
4987        });
4988    }
4989
4990    fn validate_function_argument_head(
4991        &mut self,
4992        function_name: &str,
4993        argument_head: Option<Token<'text>>,
4994        range: TextRange,
4995    ) {
4996        let head_kind = argument_head.map(|token| token.kind);
4997        let valid = if function_name.eq_ignore_ascii_case("var") {
4998            matches!(head_kind, Some(SyntaxKind::CustomPropertyName))
4999                || head_kind.is_some_and(is_dynamic_function_argument_head)
5000        } else if function_name.eq_ignore_ascii_case("env") {
5001            matches!(
5002                head_kind,
5003                Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
5004            ) || head_kind.is_some_and(is_dynamic_function_argument_head)
5005        } else if function_name.eq_ignore_ascii_case("attr") {
5006            matches!(head_kind, Some(SyntaxKind::Ident))
5007                || head_kind.is_some_and(is_dynamic_function_argument_head)
5008        } else if function_name.eq_ignore_ascii_case("color-mix") {
5009            argument_head.is_some_and(|token| token.text.eq_ignore_ascii_case("in"))
5010                || head_kind.is_some_and(is_dynamic_function_argument_head)
5011        } else {
5012            true
5013        };
5014
5015        if valid {
5016            return;
5017        }
5018        self.errors.push(ParseError {
5019            code: ParseErrorCode::ExpectedValue,
5020            range,
5021            message: "invalid function argument head",
5022        });
5023    }
5024
5025    fn parse_bracketed_value(&mut self, recovery: &[SyntaxKind]) {
5026        let closed = self.current_bracketed_value_has_closing_bracket_before(recovery);
5027        self.builder.start_node(if closed {
5028            SyntaxKind::BracketedValue
5029        } else {
5030            SyntaxKind::BogusBracketedValue
5031        });
5032        self.token_current();
5033        let bracket_recovery = bracketed_value_recovery(recovery);
5034        self.parse_value_until(&bracket_recovery);
5035        if self.current_kind() == Some(SyntaxKind::RightBracket) {
5036            self.token_current();
5037        } else {
5038            self.error_at_current(
5039                ParseErrorCode::UnexpectedCharacter,
5040                "unterminated bracketed value",
5041            );
5042        }
5043        self.builder.finish_node();
5044    }
5045
5046    fn parse_parenthesized_expression(&mut self, recovery: &[SyntaxKind]) {
5047        self.builder.start_node(SyntaxKind::ParenthesizedExpression);
5048        self.token_current();
5049        let paren_recovery = function_argument_recovery(recovery);
5050        self.parse_value_until(&paren_recovery);
5051        if self.current_kind() == Some(SyntaxKind::RightParen) {
5052            self.token_current();
5053        }
5054        self.builder.finish_node();
5055    }
5056
5057    fn parse_at_rule(&mut self) {
5058        let spec = self.current_text().and_then(at_rule_spec);
5059        let at_rule_kind = if spec.is_none() && self.current_text() == Some("@") {
5060            SyntaxKind::BogusAtRule
5061        } else {
5062            SyntaxKind::AtRule
5063        };
5064        self.builder.start_node(at_rule_kind);
5065        if at_rule_kind == SyntaxKind::BogusAtRule {
5066            self.error_at_current(ParseErrorCode::UnexpectedCharacter, "expected at-rule name");
5067        }
5068        if let Some(spec) = spec {
5069            self.builder.start_node(spec.node_kind);
5070        }
5071
5072        if self.current_kind() == Some(SyntaxKind::AtKeyword) {
5073            self.token_current();
5074        }
5075        if let Some(spec) = spec {
5076            self.parse_at_rule_prelude(spec.node_kind);
5077        } else {
5078            self.consume_at_rule_prelude_tokens();
5079        }
5080
5081        while !self.at_end() {
5082            match self.current_kind() {
5083                Some(kind) if is_statement_end(kind) => {
5084                    self.token_current();
5085                    break;
5086                }
5087                Some(SyntaxKind::LeftBrace) => {
5088                    match spec
5089                        .map(|spec| spec.block_kind)
5090                        .unwrap_or(AtRuleBlockKind::Raw)
5091                    {
5092                        AtRuleBlockKind::GroupRuleList => self.parse_group_at_rule_block(),
5093                        AtRuleBlockKind::DeclarationList => self.parse_declaration_block(),
5094                        AtRuleBlockKind::Keyframes => self.parse_keyframes_block(),
5095                        AtRuleBlockKind::Raw => self.consume_balanced_block(),
5096                    }
5097                    break;
5098                }
5099                Some(SyntaxKind::SassIndent) => {
5100                    self.parse_sass_indented_at_rule_block(
5101                        spec.map(|spec| spec.block_kind)
5102                            .unwrap_or(AtRuleBlockKind::Raw),
5103                    );
5104                    break;
5105                }
5106                Some(_) => self.token_current(),
5107                None => break,
5108            }
5109        }
5110
5111        if spec.is_some() {
5112            self.builder.finish_node();
5113        }
5114        self.builder.finish_node();
5115    }
5116
5117    fn parse_at_rule_prelude(&mut self, node_kind: SyntaxKind) {
5118        match node_kind {
5119            SyntaxKind::MediaRule => self.parse_media_query_list(),
5120            SyntaxKind::SupportsRule => self.parse_supports_rule_prelude(),
5121            SyntaxKind::ContainerRule => self.parse_container_rule_prelude(),
5122            SyntaxKind::ImportRule => self.parse_import_prelude(),
5123            SyntaxKind::CharsetRule => self.parse_charset_rule_prelude(),
5124            SyntaxKind::NamespaceRule => self.parse_namespace_rule_prelude(),
5125            SyntaxKind::KeyframesRule => self.parse_keyframes_rule_prelude(),
5126            SyntaxKind::PageRule => self.parse_page_rule_prelude(),
5127            SyntaxKind::FontFaceRule
5128            | SyntaxKind::StartingStyleRule
5129            | SyntaxKind::PageMarginRule
5130            | SyntaxKind::FontFeatureValuesStylisticRule
5131            | SyntaxKind::FontFeatureValuesStylesetRule
5132            | SyntaxKind::FontFeatureValuesCharacterVariantRule
5133            | SyntaxKind::FontFeatureValuesSwashRule
5134            | SyntaxKind::FontFeatureValuesOrnamentsRule
5135            | SyntaxKind::FontFeatureValuesAnnotationRule
5136            | SyntaxKind::FontFeatureValuesHistoricalFormsRule
5137            | SyntaxKind::ViewTransitionRule => {
5138                self.parse_empty_at_rule_prelude("unexpected at-rule prelude")
5139            }
5140            SyntaxKind::PropertyRule => self.parse_named_at_rule_prelude(
5141                at_rule_prelude_head_is_custom_property_name,
5142                "invalid @property name",
5143            ),
5144            SyntaxKind::FontPaletteValuesRule
5145            | SyntaxKind::ColorProfileRule
5146            | SyntaxKind::PositionTryRule => self.parse_named_at_rule_prelude(
5147                at_rule_prelude_head_is_custom_property_name,
5148                "invalid at-rule custom property name",
5149            ),
5150            SyntaxKind::CustomMediaRule => self.parse_custom_media_rule_prelude(),
5151            SyntaxKind::CounterStyleRule => self.parse_named_at_rule_prelude(
5152                at_rule_prelude_head_is_custom_ident,
5153                "invalid @counter-style name",
5154            ),
5155            SyntaxKind::FontFeatureValuesRule => self.parse_font_feature_values_prelude(),
5156            SyntaxKind::LayerRule => self.parse_layer_rule_prelude(),
5157            SyntaxKind::ScopeRule => self.parse_scope_rule_prelude(),
5158            _ => self.consume_at_rule_prelude_tokens(),
5159        }
5160    }
5161
5162    fn parse_media_query_list(&mut self) {
5163        self.builder.start_node(SyntaxKind::MediaQueryList);
5164        let mut saw_query = false;
5165        let mut expecting_query = true;
5166        while !self.at_end() {
5167            match self.current_kind() {
5168                Some(kind) if is_at_rule_prelude_boundary(kind) => break,
5169                Some(SyntaxKind::Comma) => {
5170                    if expecting_query {
5171                        self.error_at_current(
5172                            ParseErrorCode::ExpectedValue,
5173                            "invalid @media prelude",
5174                        );
5175                        self.builder.start_node(SyntaxKind::BogusMediaQuery);
5176                        self.token_current();
5177                        self.builder.finish_node();
5178                    } else {
5179                        self.token_current();
5180                        expecting_query = true;
5181                    }
5182                }
5183                Some(_) => {
5184                    let valid = self.current_media_query_is_valid();
5185                    if !valid {
5186                        self.error_at_current(
5187                            ParseErrorCode::ExpectedValue,
5188                            "invalid @media prelude",
5189                        );
5190                    }
5191                    self.parse_media_query(valid);
5192                    saw_query = true;
5193                    expecting_query = false;
5194                }
5195                None => break,
5196            }
5197        }
5198        if !saw_query || expecting_query {
5199            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @media prelude");
5200            self.builder.start_node(SyntaxKind::BogusMediaQuery);
5201            self.builder.finish_node();
5202        }
5203        self.builder.finish_node();
5204    }
5205
5206    fn parse_media_query(&mut self, valid: bool) {
5207        self.builder.start_node(if valid {
5208            SyntaxKind::MediaQuery
5209        } else {
5210            SyntaxKind::BogusMediaQuery
5211        });
5212        while !self.at_end() {
5213            match self.current_kind() {
5214                Some(kind) if is_at_rule_prelude_boundary(kind) || kind == SyntaxKind::Comma => {
5215                    break;
5216                }
5217                Some(SyntaxKind::LeftParen) => self.parse_balanced_parenthesized_prelude_until(
5218                    Some(SyntaxKind::MediaFeature),
5219                    &[
5220                        SyntaxKind::Comma,
5221                        SyntaxKind::LeftBrace,
5222                        SyntaxKind::Semicolon,
5223                    ],
5224                ),
5225                Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
5226                    kind,
5227                    &[
5228                        SyntaxKind::Comma,
5229                        SyntaxKind::LeftBrace,
5230                        SyntaxKind::Semicolon,
5231                    ],
5232                ),
5233                Some(_) => self.token_current(),
5234                None => break,
5235            }
5236        }
5237        self.builder.finish_node();
5238    }
5239
5240    fn current_media_query_is_valid(&self) -> bool {
5241        let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5242            return false;
5243        };
5244        if is_at_rule_prelude_boundary(first_kind) || first_kind == SyntaxKind::Comma {
5245            return false;
5246        }
5247        if !self.current_prelude_parentheses_are_balanced_until(&[
5248            SyntaxKind::Comma,
5249            SyntaxKind::LeftBrace,
5250            SyntaxKind::SassIndent,
5251            SyntaxKind::Semicolon,
5252            SyntaxKind::SassOptionalSemicolon,
5253        ]) {
5254            return false;
5255        }
5256        self.media_query_starts_at(first_index, first_kind)
5257    }
5258
5259    fn media_query_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5260        match kind {
5261            SyntaxKind::Ident | SyntaxKind::LeftParen => true,
5262            SyntaxKind::KeywordNot | SyntaxKind::KeywordOnly => self
5263                .non_trivia_token_from(index + 1)
5264                .is_some_and(|(_, next_kind)| {
5265                    matches!(next_kind, SyntaxKind::Ident | SyntaxKind::LeftParen)
5266                        || is_interpolation_start(next_kind)
5267                }),
5268            kind if is_interpolation_start(kind) => true,
5269            _ => false,
5270        }
5271    }
5272
5273    fn parse_charset_rule_prelude(&mut self) {
5274        if !self.charset_rule_prelude_is_valid() {
5275            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @charset prelude");
5276        }
5277        self.consume_at_rule_prelude_tokens();
5278    }
5279
5280    fn charset_rule_prelude_is_valid(&self) -> bool {
5281        let Some((source_index, SyntaxKind::String)) = self.non_trivia_token_from(self.position)
5282        else {
5283            return false;
5284        };
5285        self.non_trivia_token_from(source_index + 1)
5286            .is_none_or(|(_, kind)| is_at_rule_prelude_boundary(kind))
5287    }
5288
5289    fn parse_namespace_rule_prelude(&mut self) {
5290        if !self.namespace_rule_prelude_is_valid() {
5291            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @namespace prelude");
5292        }
5293        self.consume_at_rule_prelude_tokens();
5294    }
5295
5296    fn parse_custom_media_rule_prelude(&mut self) {
5297        self.eat_trivia();
5298        let valid = self.custom_media_rule_prelude_is_valid();
5299        if !valid {
5300            self.error_at_current(
5301                ParseErrorCode::ExpectedValue,
5302                "invalid @custom-media prelude",
5303            );
5304        }
5305        self.builder.start_node(if valid {
5306            SyntaxKind::AtRulePrelude
5307        } else {
5308            SyntaxKind::BogusAtRulePrelude
5309        });
5310        self.consume_at_rule_prelude_tokens_without_wrapping();
5311        self.builder.finish_node();
5312    }
5313
5314    fn custom_media_rule_prelude_is_valid(&self) -> bool {
5315        let Some((name_index, name_kind)) = self.non_trivia_token_from(self.position) else {
5316            return false;
5317        };
5318        if !self.current_prelude_parentheses_are_balanced_until(&[
5319            SyntaxKind::Semicolon,
5320            SyntaxKind::SassOptionalSemicolon,
5321        ]) {
5322            return false;
5323        }
5324        let tail = if name_kind == SyntaxKind::CustomPropertyName {
5325            self.non_trivia_token_from(name_index + 1)
5326        } else if is_interpolation_start(name_kind) {
5327            self.non_trivia_token_after_interpolation(name_index, name_kind)
5328        } else {
5329            return false;
5330        };
5331        let Some((tail_index, tail_kind)) = tail else {
5332            return false;
5333        };
5334        if is_at_rule_prelude_boundary(tail_kind) {
5335            return false;
5336        }
5337        self.media_query_starts_at(tail_index, tail_kind)
5338    }
5339
5340    fn namespace_rule_prelude_is_valid(&self) -> bool {
5341        let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5342            return false;
5343        };
5344
5345        if self.namespace_source_starts_at(first_index, first_kind) {
5346            return true;
5347        }
5348        if !matches!(
5349            first_kind,
5350            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
5351        ) {
5352            return false;
5353        }
5354        self.non_trivia_token_from(first_index + 1)
5355            .is_some_and(|(source_index, source_kind)| {
5356                self.namespace_source_starts_at(source_index, source_kind)
5357            })
5358    }
5359
5360    fn namespace_source_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5361        matches!(kind, SyntaxKind::String | SyntaxKind::Url)
5362            || is_interpolation_start(kind)
5363            || self.token_starts_url_function(index, kind)
5364    }
5365
5366    fn token_starts_url_function(&self, index: usize, kind: SyntaxKind) -> bool {
5367        kind == SyntaxKind::Ident
5368            && self
5369                .tokens
5370                .get(index)
5371                .is_some_and(|token| token.text.eq_ignore_ascii_case("url"))
5372            && self
5373                .non_trivia_token_from(index + 1)
5374                .is_some_and(|(_, next_kind)| next_kind == SyntaxKind::LeftParen)
5375    }
5376
5377    fn parse_keyframes_rule_prelude(&mut self) {
5378        if !self.keyframes_rule_prelude_is_valid() {
5379            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @keyframes name");
5380        }
5381        self.consume_at_rule_prelude_tokens();
5382    }
5383
5384    fn keyframes_rule_prelude_is_valid(&self) -> bool {
5385        let Some((name_index, name_kind)) = self.non_trivia_token_from(self.position) else {
5386            return false;
5387        };
5388        if is_interpolation_start(name_kind) {
5389            return true;
5390        }
5391        if !matches!(name_kind, SyntaxKind::Ident | SyntaxKind::String) {
5392            return false;
5393        }
5394        self.non_trivia_token_from(name_index + 1)
5395            .is_none_or(|(_, kind)| is_at_rule_prelude_boundary(kind))
5396    }
5397
5398    fn parse_empty_at_rule_prelude(&mut self, message: &'static str) {
5399        self.eat_trivia();
5400        if self
5401            .current_kind()
5402            .is_some_and(|kind| !is_at_rule_prelude_boundary(kind))
5403        {
5404            self.error_at_current(ParseErrorCode::ExpectedValue, message);
5405            self.consume_at_rule_prelude_tokens();
5406        }
5407    }
5408
5409    fn parse_font_feature_values_prelude(&mut self) {
5410        if !self.font_feature_values_prelude_is_valid() {
5411            self.error_at_current(
5412                ParseErrorCode::ExpectedValue,
5413                "invalid @font-feature-values family name",
5414            );
5415        }
5416        self.consume_at_rule_prelude_tokens();
5417    }
5418
5419    fn font_feature_values_prelude_is_valid(&self) -> bool {
5420        self.non_trivia_token_from(self.position)
5421            .is_some_and(|(_, kind)| {
5422                matches!(kind, SyntaxKind::Ident | SyntaxKind::String)
5423                    || is_interpolation_start(kind)
5424            })
5425    }
5426
5427    fn parse_layer_rule_prelude(&mut self) {
5428        self.eat_trivia();
5429        match self.current_kind() {
5430            Some(SyntaxKind::LeftBrace | SyntaxKind::SassIndent) => return,
5431            Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) | None => {
5432                self.empty_bogus_node(
5433                    SyntaxKind::BogusLayerName,
5434                    ParseErrorCode::ExpectedValue,
5435                    "invalid @layer prelude",
5436                );
5437                return;
5438            }
5439            Some(_) => {}
5440        }
5441
5442        let valid = self.layer_rule_prelude_is_valid();
5443        if !valid {
5444            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @layer prelude");
5445        }
5446        self.builder.start_node(if valid {
5447            SyntaxKind::LayerName
5448        } else {
5449            SyntaxKind::BogusLayerName
5450        });
5451        self.consume_at_rule_prelude_tokens_without_wrapping();
5452        self.builder.finish_node();
5453    }
5454
5455    fn layer_rule_prelude_is_valid(&self) -> bool {
5456        let mut saw_name = false;
5457        let mut expecting_segment = true;
5458        let mut index = self.position;
5459
5460        while let Some(token) = self.tokens.get(index) {
5461            if token.kind.is_trivia() {
5462                index += 1;
5463                continue;
5464            }
5465            if is_at_rule_prelude_boundary(token.kind) {
5466                return saw_name && !expecting_segment;
5467            }
5468            if is_interpolation_start(token.kind) {
5469                return true;
5470            }
5471            match token.kind {
5472                SyntaxKind::Ident if expecting_segment => {
5473                    saw_name = true;
5474                    expecting_segment = false;
5475                }
5476                SyntaxKind::Comma if saw_name && !expecting_segment => {
5477                    expecting_segment = true;
5478                }
5479                SyntaxKind::Dot if saw_name && !expecting_segment => {
5480                    expecting_segment = true;
5481                }
5482                _ => return false,
5483            }
5484            index += 1;
5485        }
5486
5487        saw_name && !expecting_segment
5488    }
5489
5490    fn parse_container_rule_prelude(&mut self) {
5491        self.eat_trivia();
5492        let valid = self.container_rule_prelude_is_valid();
5493        if !valid {
5494            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @container prelude");
5495        }
5496        self.builder.start_node(if valid {
5497            SyntaxKind::ContainerCondition
5498        } else {
5499            SyntaxKind::BogusContainerCondition
5500        });
5501        self.consume_at_rule_prelude_tokens_without_wrapping();
5502        self.builder.finish_node();
5503    }
5504
5505    fn container_rule_prelude_is_valid(&self) -> bool {
5506        let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5507            return false;
5508        };
5509        if is_at_rule_prelude_boundary(first_kind) {
5510            return false;
5511        }
5512        if !self.current_prelude_parentheses_are_balanced_until(&[
5513            SyntaxKind::LeftBrace,
5514            SyntaxKind::SassIndent,
5515            SyntaxKind::Semicolon,
5516            SyntaxKind::SassOptionalSemicolon,
5517        ]) {
5518            return false;
5519        }
5520        if self.container_condition_starts_at(first_index, first_kind) {
5521            return true;
5522        }
5523        if first_kind != SyntaxKind::Ident {
5524            return false;
5525        }
5526        self.non_trivia_token_from(first_index + 1).is_some_and(
5527            |(condition_index, condition_kind)| {
5528                self.container_condition_starts_at(condition_index, condition_kind)
5529            },
5530        )
5531    }
5532
5533    fn container_condition_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5534        if matches!(kind, SyntaxKind::LeftParen | SyntaxKind::KeywordNot)
5535            || is_interpolation_start(kind)
5536        {
5537            return true;
5538        }
5539        kind == SyntaxKind::Ident
5540            && self
5541                .non_trivia_token_from(index + 1)
5542                .is_some_and(|(_, next_kind)| next_kind == SyntaxKind::LeftParen)
5543    }
5544
5545    fn parse_supports_rule_prelude(&mut self) {
5546        self.eat_trivia();
5547        let valid = self.supports_rule_prelude_is_valid();
5548        if !valid {
5549            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @supports prelude");
5550        }
5551        self.builder.start_node(if valid {
5552            SyntaxKind::SupportsCondition
5553        } else {
5554            SyntaxKind::BogusSupportsCondition
5555        });
5556        self.consume_at_rule_prelude_tokens_without_wrapping();
5557        self.builder.finish_node();
5558    }
5559
5560    fn supports_rule_prelude_is_valid(&self) -> bool {
5561        let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5562            return false;
5563        };
5564        if is_at_rule_prelude_boundary(first_kind) {
5565            return false;
5566        }
5567        if !self.current_prelude_parentheses_are_balanced_until(&[
5568            SyntaxKind::LeftBrace,
5569            SyntaxKind::SassIndent,
5570            SyntaxKind::Semicolon,
5571            SyntaxKind::SassOptionalSemicolon,
5572        ]) {
5573            return false;
5574        }
5575        self.supports_condition_starts_at(first_index, first_kind)
5576    }
5577
5578    fn supports_condition_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5579        if kind == SyntaxKind::KeywordNot {
5580            return self
5581                .non_trivia_token_from(index + 1)
5582                .is_some_and(|(next_index, next_kind)| {
5583                    self.supports_condition_starts_at(next_index, next_kind)
5584                });
5585        }
5586        if kind == SyntaxKind::LeftParen || is_interpolation_start(kind) {
5587            return true;
5588        }
5589        kind == SyntaxKind::Ident
5590            && self
5591                .non_trivia_token_from(index + 1)
5592                .is_some_and(|(_, next_kind)| next_kind == SyntaxKind::LeftParen)
5593    }
5594
5595    fn parse_scope_rule_prelude(&mut self) {
5596        self.eat_trivia();
5597        let valid = self.scope_rule_prelude_is_valid();
5598        if !valid {
5599            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @scope prelude");
5600        }
5601        self.builder.start_node(if valid {
5602            SyntaxKind::ScopeRange
5603        } else {
5604            SyntaxKind::BogusScopeRange
5605        });
5606        self.consume_at_rule_prelude_tokens_without_wrapping();
5607        self.builder.finish_node();
5608    }
5609
5610    fn scope_rule_prelude_is_valid(&self) -> bool {
5611        let Some((start_index, start_kind)) = self.non_trivia_token_from(self.position) else {
5612            return false;
5613        };
5614        if is_at_rule_prelude_boundary(start_kind) {
5615            return false;
5616        }
5617        if !self.current_prelude_parentheses_are_balanced_until(&[
5618            SyntaxKind::LeftBrace,
5619            SyntaxKind::SassIndent,
5620            SyntaxKind::Semicolon,
5621            SyntaxKind::SassOptionalSemicolon,
5622        ]) {
5623            return false;
5624        }
5625        if is_interpolation_start(start_kind) {
5626            return true;
5627        }
5628        if start_kind != SyntaxKind::LeftParen {
5629            return false;
5630        }
5631
5632        let Some(start_close_index) = self.parenthesized_prelude_close_index(start_index) else {
5633            return false;
5634        };
5635        let Some((after_start_index, after_start_kind)) =
5636            self.non_trivia_token_from(start_close_index + 1)
5637        else {
5638            return true;
5639        };
5640        if is_at_rule_prelude_boundary(after_start_kind) {
5641            return true;
5642        }
5643        if after_start_kind != SyntaxKind::Ident
5644            || !self
5645                .tokens
5646                .get(after_start_index)
5647                .is_some_and(|token| token.text.eq_ignore_ascii_case("to"))
5648        {
5649            return false;
5650        }
5651
5652        let Some((end_index, end_kind)) = self.non_trivia_token_from(after_start_index + 1) else {
5653            return false;
5654        };
5655        if is_interpolation_start(end_kind) {
5656            return true;
5657        }
5658        if end_kind != SyntaxKind::LeftParen {
5659            return false;
5660        }
5661        let Some(end_close_index) = self.parenthesized_prelude_close_index(end_index) else {
5662            return false;
5663        };
5664        self.non_trivia_token_from(end_close_index + 1)
5665            .is_none_or(|(_, kind)| is_at_rule_prelude_boundary(kind))
5666    }
5667
5668    fn parenthesized_prelude_close_index(&self, open_index: usize) -> Option<usize> {
5669        let mut depth = 0usize;
5670        for (index, token) in self.tokens.iter().enumerate().skip(open_index) {
5671            match token.kind {
5672                SyntaxKind::LeftParen => depth += 1,
5673                SyntaxKind::RightParen => {
5674                    depth = depth.saturating_sub(1);
5675                    if depth == 0 {
5676                        return Some(index);
5677                    }
5678                }
5679                kind if depth == 0 && is_at_rule_prelude_boundary(kind) => return None,
5680                _ => {}
5681            }
5682        }
5683        None
5684    }
5685
5686    fn parse_page_rule_prelude(&mut self) {
5687        self.eat_trivia();
5688        if self.current_kind().is_none_or(is_at_rule_prelude_boundary) {
5689            return;
5690        }
5691        let valid = self.page_rule_prelude_is_valid();
5692        if !valid {
5693            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @page prelude");
5694        }
5695        self.builder.start_node(if valid {
5696            SyntaxKind::AtRulePrelude
5697        } else {
5698            SyntaxKind::BogusAtRulePrelude
5699        });
5700        self.consume_at_rule_prelude_tokens_without_wrapping();
5701        self.builder.finish_node();
5702    }
5703
5704    fn page_rule_prelude_is_valid(&self) -> bool {
5705        let mut expecting_selector = true;
5706        let mut expecting_pseudo_name = false;
5707        let mut saw_selector = false;
5708
5709        for token in self.tokens.iter().skip(self.position) {
5710            if token.kind.is_trivia() {
5711                continue;
5712            }
5713            if is_at_rule_prelude_boundary(token.kind) {
5714                return saw_selector && !expecting_selector && !expecting_pseudo_name;
5715            }
5716            if is_interpolation_start(token.kind) {
5717                return true;
5718            }
5719            if expecting_pseudo_name {
5720                if token.kind != SyntaxKind::Ident {
5721                    return false;
5722                }
5723                saw_selector = true;
5724                expecting_selector = false;
5725                expecting_pseudo_name = false;
5726                continue;
5727            }
5728            match token.kind {
5729                SyntaxKind::Ident if expecting_selector => {
5730                    saw_selector = true;
5731                    expecting_selector = false;
5732                }
5733                SyntaxKind::Colon => {
5734                    expecting_pseudo_name = true;
5735                }
5736                SyntaxKind::Comma if saw_selector && !expecting_selector => {
5737                    expecting_selector = true;
5738                }
5739                _ => return false,
5740            }
5741        }
5742
5743        saw_selector && !expecting_selector && !expecting_pseudo_name
5744    }
5745
5746    fn parse_import_prelude(&mut self) {
5747        self.eat_trivia();
5748        if self.dialect == StyleDialect::Less && self.current_kind() == Some(SyntaxKind::LeftParen)
5749        {
5750            self.builder.start_node(SyntaxKind::AtRulePrelude);
5751            self.parse_balanced_parenthesized_prelude(None);
5752            self.builder.finish_node();
5753            self.eat_trivia();
5754        }
5755        if !self.parse_import_source() {
5756            self.parse_bogus_import_prelude();
5757            return;
5758        }
5759        while !self.at_end() {
5760            match self.current_kind() {
5761                Some(kind) if is_at_rule_prelude_boundary(kind) => break,
5762                Some(kind) if kind.is_trivia() => self.token_current(),
5763                Some(SyntaxKind::Ident) if self.current_text() == Some("layer") => {
5764                    self.parse_import_layer_tail_node()
5765                }
5766                Some(SyntaxKind::Ident) if self.current_text() == Some("supports") => {
5767                    self.parse_import_supports_tail_node()
5768                }
5769                Some(_) => {
5770                    self.parse_media_query_list();
5771                    break;
5772                }
5773                None => break,
5774            }
5775        }
5776    }
5777
5778    fn parse_import_source(&mut self) -> bool {
5779        match self.current_kind() {
5780            Some(SyntaxKind::Url) => {
5781                self.builder.start_node(SyntaxKind::UrlValue);
5782                self.token_current();
5783                self.builder.finish_node();
5784                true
5785            }
5786            Some(SyntaxKind::Ident)
5787                if self
5788                    .current_text()
5789                    .is_some_and(|text| text.eq_ignore_ascii_case("url"))
5790                    && self.next_kind() == Some(SyntaxKind::LeftParen) =>
5791            {
5792                self.builder.start_node(SyntaxKind::UrlValue);
5793                self.parse_function_call(&[SyntaxKind::LeftBrace, SyntaxKind::Semicolon]);
5794                self.builder.finish_node();
5795                true
5796            }
5797            Some(SyntaxKind::String) => {
5798                self.token_current();
5799                true
5800            }
5801            Some(kind) if is_interpolation_start(kind) => {
5802                self.parse_interpolation(kind, &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon]);
5803                true
5804            }
5805            Some(_) | None => false,
5806        }
5807    }
5808
5809    fn parse_bogus_import_prelude(&mut self) {
5810        self.builder.start_node(SyntaxKind::BogusAtRulePrelude);
5811        self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @import source");
5812        self.consume_at_rule_prelude_tokens_without_wrapping();
5813        self.builder.finish_node();
5814    }
5815
5816    fn parse_named_at_rule_prelude(
5817        &mut self,
5818        valid_head: fn(SyntaxKind) -> bool,
5819        message: &'static str,
5820    ) {
5821        if self.current_kind().is_none_or(is_at_rule_prelude_boundary) {
5822            return;
5823        }
5824        let valid_name = self
5825            .non_trivia_token_from(self.position)
5826            .is_some_and(|(_, kind)| valid_head(kind));
5827        if !valid_name {
5828            self.error_at_current(ParseErrorCode::ExpectedValue, message);
5829        }
5830        self.consume_at_rule_prelude_tokens();
5831    }
5832
5833    fn parse_import_layer_tail_node(&mut self) {
5834        let valid = self.import_layer_tail_is_valid();
5835        if !valid {
5836            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @import layer tail");
5837        }
5838        self.builder.start_node(if valid {
5839            SyntaxKind::LayerName
5840        } else {
5841            SyntaxKind::BogusLayerName
5842        });
5843        self.token_current();
5844        if self.current_kind() == Some(SyntaxKind::LeftParen) {
5845            self.parse_balanced_parenthesized_prelude(None);
5846        }
5847        self.builder.finish_node();
5848    }
5849
5850    fn import_layer_tail_is_valid(&self) -> bool {
5851        let Some((open_index, next_kind)) = self.non_trivia_token_from(self.position + 1) else {
5852            return true;
5853        };
5854        if next_kind != SyntaxKind::LeftParen {
5855            return true;
5856        }
5857        let Some(close_index) = self.parenthesized_prelude_close_index(open_index) else {
5858            return false;
5859        };
5860        self.layer_name_is_valid_between(open_index + 1, close_index)
5861    }
5862
5863    fn layer_name_is_valid_between(&self, start: usize, end: usize) -> bool {
5864        let mut saw_name = false;
5865        let mut expecting_segment = true;
5866
5867        for token in self.tokens[start..end]
5868            .iter()
5869            .filter(|token| !token.kind.is_trivia())
5870        {
5871            if is_interpolation_start(token.kind) {
5872                return true;
5873            }
5874            match token.kind {
5875                SyntaxKind::Ident if expecting_segment => {
5876                    saw_name = true;
5877                    expecting_segment = false;
5878                }
5879                SyntaxKind::Dot if saw_name && !expecting_segment => {
5880                    expecting_segment = true;
5881                }
5882                _ => return false,
5883            }
5884        }
5885
5886        saw_name && !expecting_segment
5887    }
5888
5889    fn parse_import_supports_tail_node(&mut self) {
5890        let valid = self.import_supports_tail_is_valid();
5891        if !valid {
5892            self.error_at_current(
5893                ParseErrorCode::ExpectedValue,
5894                "invalid @import supports tail",
5895            );
5896        }
5897        self.builder.start_node(if valid {
5898            SyntaxKind::SupportsCondition
5899        } else {
5900            SyntaxKind::BogusSupportsCondition
5901        });
5902        self.token_current();
5903        if self.current_kind() == Some(SyntaxKind::LeftParen) {
5904            self.parse_balanced_parenthesized_prelude(None);
5905        }
5906        self.builder.finish_node();
5907    }
5908
5909    fn import_supports_tail_is_valid(&self) -> bool {
5910        let Some((open_index, SyntaxKind::LeftParen)) =
5911            self.non_trivia_token_from(self.position + 1)
5912        else {
5913            return false;
5914        };
5915        let Some(close_index) = self.parenthesized_prelude_close_index(open_index) else {
5916            return false;
5917        };
5918        self.non_trivia_token_from(open_index + 1)
5919            .is_some_and(|(inner_index, inner_kind)| {
5920                inner_index < close_index && inner_kind != SyntaxKind::RightParen
5921            })
5922    }
5923
5924    fn consume_at_rule_prelude_tokens(&mut self) {
5925        if self.current_kind().is_none_or(is_at_rule_prelude_boundary) {
5926            return;
5927        }
5928        self.builder
5929            .start_node(self.current_generic_at_rule_prelude_node_kind());
5930        self.consume_at_rule_prelude_tokens_without_wrapping();
5931        self.builder.finish_node();
5932    }
5933
5934    fn consume_at_rule_prelude_tokens_without_wrapping(&mut self) {
5935        while !self.at_end() {
5936            match self.current_kind() {
5937                Some(kind) if is_at_rule_prelude_boundary(kind) => break,
5938                Some(SyntaxKind::LeftParen) => self.parse_balanced_parenthesized_prelude(None),
5939                Some(kind) if is_interpolation_start(kind) => {
5940                    self.parse_interpolation(kind, &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon])
5941                }
5942                Some(_) => self.token_current(),
5943                None => break,
5944            }
5945        }
5946    }
5947
5948    fn parse_balanced_parenthesized_prelude(&mut self, node_kind: Option<SyntaxKind>) {
5949        self.parse_balanced_parenthesized_prelude_until(
5950            node_kind,
5951            &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon],
5952        );
5953    }
5954
5955    fn parse_balanced_parenthesized_prelude_until(
5956        &mut self,
5957        node_kind: Option<SyntaxKind>,
5958        recovery: &[SyntaxKind],
5959    ) {
5960        if let Some(kind) = node_kind {
5961            self.builder.start_node(kind);
5962        }
5963        let mut depth = 0usize;
5964        let mut closed = false;
5965        while !self.at_end() {
5966            match self.current_kind() {
5967                Some(SyntaxKind::LeftParen) => {
5968                    depth += 1;
5969                    self.token_current();
5970                }
5971                Some(SyntaxKind::RightParen) => {
5972                    self.token_current();
5973                    depth = depth.saturating_sub(1);
5974                    if depth == 0 {
5975                        closed = true;
5976                        break;
5977                    }
5978                }
5979                Some(kind) if recovery.contains(&kind) => break,
5980                Some(kind) if is_interpolation_start(kind) => {
5981                    self.parse_interpolation(kind, &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon])
5982                }
5983                Some(_) => self.token_current(),
5984                None => break,
5985            }
5986        }
5987        if node_kind.is_some() {
5988            self.builder.finish_node();
5989        }
5990        if !closed {
5991            self.error_at_current(
5992                ParseErrorCode::UnexpectedCharacter,
5993                "unterminated parenthesized prelude",
5994            );
5995        }
5996    }
5997
5998    fn parse_interpolation(&mut self, start_kind: SyntaxKind, recovery: &[SyntaxKind]) {
5999        let Some(end_kind) = interpolation_end_kind(start_kind) else {
6000            self.token_current();
6001            return;
6002        };
6003        let closed = self.find_before_recovery(end_kind, recovery);
6004        self.builder.start_node(if closed {
6005            SyntaxKind::Interpolation
6006        } else {
6007            SyntaxKind::BogusInterpolation
6008        });
6009        if self.current_kind() == Some(start_kind) {
6010            self.token_current();
6011        }
6012        while !self.at_end() {
6013            match self.current_kind() {
6014                Some(kind) if kind == end_kind => {
6015                    self.token_current();
6016                    break;
6017                }
6018                Some(kind) if !closed && recovery.contains(&kind) => break,
6019                Some(_) => self.token_current(),
6020                None => break,
6021            }
6022        }
6023        if !closed {
6024            self.error_at_current(
6025                ParseErrorCode::UnexpectedCharacter,
6026                "unterminated interpolation",
6027            );
6028        }
6029        self.builder.finish_node();
6030    }
6031
6032    fn parse_group_at_rule_block(&mut self) {
6033        self.token_current();
6034        self.builder.start_node(SyntaxKind::RuleList);
6035        self.parse_rule_list_items();
6036        self.builder.finish_node();
6037        if self.current_kind() == Some(SyntaxKind::RightBrace) {
6038            self.token_current();
6039        }
6040    }
6041
6042    fn parse_rule_list_items(&mut self) {
6043        while !self.at_end() {
6044            self.eat_trivia();
6045            match self.current_kind() {
6046                Some(SyntaxKind::RightBrace | SyntaxKind::SassDedent) | None => break,
6047                Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) => {
6048                    self.token_current()
6049                }
6050                Some(SyntaxKind::AtKeyword) if self.current_is_css_module_value_rule() => {
6051                    self.parse_css_module_value_rule()
6052                }
6053                Some(SyntaxKind::AtKeyword) if self.current_dialect_at_rule_spec().is_some() => {
6054                    self.parse_dialect_at_rule()
6055                }
6056                Some(SyntaxKind::AtKeyword) => self.parse_at_rule(),
6057                Some(_) => self.parse_rule(),
6058            }
6059        }
6060    }
6061
6062    fn parse_declaration_block(&mut self) {
6063        self.token_current();
6064        self.builder
6065            .start_node(if self.previous_left_brace_has_match() {
6066                SyntaxKind::DeclarationList
6067            } else {
6068                SyntaxKind::BogusDeclarationList
6069            });
6070        self.parse_declaration_list();
6071        self.builder.finish_node();
6072        if self.current_kind() == Some(SyntaxKind::RightBrace) {
6073            self.token_current();
6074        } else {
6075            self.missing_token_bogus_trivia(
6076                ParseErrorCode::UnexpectedCharacter,
6077                "unterminated declaration block",
6078            );
6079        }
6080    }
6081
6082    fn parse_sass_indented_at_rule_block(&mut self, block_kind: AtRuleBlockKind) {
6083        self.builder.start_node(SyntaxKind::SassIndentedBlock);
6084        if self.current_kind() == Some(SyntaxKind::SassIndent) {
6085            self.token_current();
6086        }
6087        match block_kind {
6088            AtRuleBlockKind::GroupRuleList => {
6089                self.builder.start_node(SyntaxKind::RuleList);
6090                self.parse_rule_list_items();
6091                self.builder.finish_node();
6092            }
6093            AtRuleBlockKind::DeclarationList | AtRuleBlockKind::Keyframes => {
6094                self.builder.start_node(SyntaxKind::DeclarationList);
6095                self.parse_declaration_list();
6096                self.builder.finish_node();
6097            }
6098            AtRuleBlockKind::Raw => self.consume_sass_indented_raw_body(),
6099        }
6100        if self.current_kind() == Some(SyntaxKind::SassDedent) {
6101            self.token_current();
6102        } else {
6103            self.error_at_current(
6104                ParseErrorCode::UnexpectedCharacter,
6105                "unterminated Sass indented at-rule block",
6106            );
6107        }
6108        self.builder.finish_node();
6109    }
6110
6111    fn consume_sass_indented_raw_body(&mut self) {
6112        let mut depth = 0usize;
6113        while !self.at_end() {
6114            match self.current_kind() {
6115                Some(SyntaxKind::SassIndent) => {
6116                    depth += 1;
6117                    self.token_current();
6118                }
6119                Some(SyntaxKind::SassDedent) if depth == 0 => break,
6120                Some(SyntaxKind::SassDedent) => {
6121                    depth = depth.saturating_sub(1);
6122                    self.token_current();
6123                }
6124                Some(_) => self.token_current(),
6125                None => break,
6126            }
6127        }
6128    }
6129
6130    fn parse_keyframes_block(&mut self) {
6131        self.token_current();
6132        while !self.at_end() {
6133            self.eat_trivia();
6134            match self.current_kind() {
6135                Some(SyntaxKind::RightBrace) | None => break,
6136                Some(_) => self.parse_keyframe_block(),
6137            }
6138        }
6139        if self.current_kind() == Some(SyntaxKind::RightBrace) {
6140            self.token_current();
6141        }
6142    }
6143
6144    fn parse_keyframe_block(&mut self) {
6145        let has_block = self.find_before_recovery(SyntaxKind::LeftBrace, &[SyntaxKind::RightBrace]);
6146        self.builder.start_node(if has_block {
6147            SyntaxKind::KeyframeBlock
6148        } else {
6149            SyntaxKind::BogusKeyframeBlock
6150        });
6151        if has_block && !self.keyframe_selector_list_is_valid() {
6152            self.error_at_current(ParseErrorCode::ExpectedValue, "invalid keyframe selector");
6153        }
6154        while !self.at_end() {
6155            match self.current_kind() {
6156                Some(SyntaxKind::LeftBrace) => {
6157                    self.parse_declaration_block();
6158                    break;
6159                }
6160                Some(SyntaxKind::RightBrace) | None => break,
6161                Some(_) => self.token_current(),
6162            }
6163        }
6164        if !has_block {
6165            self.error_at_current(
6166                ParseErrorCode::UnexpectedCharacter,
6167                "expected keyframe declaration block",
6168            );
6169        }
6170        self.builder.finish_node();
6171    }
6172
6173    fn keyframe_selector_list_is_valid(&self) -> bool {
6174        let mut index = self.position;
6175        let mut saw_selector = false;
6176        let mut expect_selector = true;
6177        loop {
6178            let Some((token_index, kind)) = self.non_trivia_token_from(index) else {
6179                return false;
6180            };
6181            if kind == SyntaxKind::LeftBrace {
6182                return saw_selector && !expect_selector;
6183            }
6184            if expect_selector {
6185                if is_interpolation_start(kind) {
6186                    return true;
6187                }
6188                if !keyframe_selector_token_is_valid(self.tokens[token_index]) {
6189                    return false;
6190                }
6191                saw_selector = true;
6192                expect_selector = false;
6193                index = token_index + 1;
6194                continue;
6195            }
6196            if kind != SyntaxKind::Comma {
6197                return false;
6198            }
6199            expect_selector = true;
6200            index = token_index + 1;
6201        }
6202    }
6203
6204    fn consume_balanced_block(&mut self) {
6205        let mut depth = 0usize;
6206        while !self.at_end() {
6207            match self.current_kind() {
6208                Some(SyntaxKind::LeftBrace) => {
6209                    depth += 1;
6210                    self.token_current();
6211                }
6212                Some(SyntaxKind::RightBrace) => {
6213                    self.token_current();
6214                    depth = depth.saturating_sub(1);
6215                    if depth == 0 {
6216                        break;
6217                    }
6218                }
6219                Some(_) => self.token_current(),
6220                None => break,
6221            }
6222        }
6223    }
6224
6225    fn eat_trivia(&mut self) {
6226        while matches!(self.current_kind(), Some(kind) if kind.is_trivia()) {
6227            self.token_current();
6228        }
6229    }
6230
6231    fn consume_until_recovery(&mut self, recovery: &[SyntaxKind]) {
6232        let should_wrap = self
6233            .current_kind()
6234            .is_some_and(|kind| !recovery.contains(&kind));
6235        if should_wrap {
6236            self.builder.start_node(SyntaxKind::BogusRecovery);
6237        }
6238        while !self.at_end() {
6239            match self.current_kind() {
6240                Some(kind) if recovery.contains(&kind) => break,
6241                Some(_) => self.token_current(),
6242                None => break,
6243            }
6244        }
6245        if should_wrap {
6246            self.builder.finish_node();
6247        }
6248    }
6249
6250    fn find_before_recovery(&self, target: SyntaxKind, recovery: &[SyntaxKind]) -> bool {
6251        let mut index = self.position;
6252        while let Some(token) = self.tokens.get(index) {
6253            if token.kind == target {
6254                return true;
6255            }
6256            if recovery.contains(&token.kind) {
6257                return false;
6258            }
6259            index += 1;
6260        }
6261        false
6262    }
6263
6264    fn find_rule_block_open_before_recovery(&self, recovery: &[SyntaxKind]) -> bool {
6265        let mut index = self.position;
6266        while let Some(token) = self.tokens.get(index) {
6267            if token.kind == SyntaxKind::LeftBrace
6268                || (self.dialect == StyleDialect::Sass && token.kind == SyntaxKind::SassIndent)
6269            {
6270                return true;
6271            }
6272            if recovery.contains(&token.kind) {
6273                return false;
6274            }
6275            index += 1;
6276        }
6277        false
6278    }
6279
6280    fn find_text_before_recovery(&self, target: &str, recovery: &[SyntaxKind]) -> bool {
6281        let mut index = self.position;
6282        while let Some(token) = self.tokens.get(index) {
6283            if token.text == target {
6284                return true;
6285            }
6286            if recovery.contains(&token.kind) {
6287                return false;
6288            }
6289            index += 1;
6290        }
6291        false
6292    }
6293
6294    fn current_function_has_closing_paren_before(&self, recovery: &[SyntaxKind]) -> bool {
6295        let Some(open_index) = self.position.checked_add(1) else {
6296            return false;
6297        };
6298        if self
6299            .tokens
6300            .get(open_index)
6301            .is_none_or(|token| token.kind != SyntaxKind::LeftParen)
6302        {
6303            return false;
6304        }
6305
6306        let mut depth = 0usize;
6307        for token in self.tokens.iter().skip(open_index) {
6308            match token.kind {
6309                SyntaxKind::LeftParen => depth += 1,
6310                SyntaxKind::RightParen => {
6311                    depth = depth.saturating_sub(1);
6312                    if depth == 0 {
6313                        return true;
6314                    }
6315                }
6316                kind if depth == 1 && recovery.contains(&kind) => return false,
6317                _ => {}
6318            }
6319        }
6320        false
6321    }
6322
6323    fn current_split_important_annotation(&self) -> bool {
6324        self.current_text() == Some("!")
6325            && self
6326                .non_trivia_token_from(self.position + 1)
6327                .is_some_and(|(index, kind)| {
6328                    matches!(kind, SyntaxKind::Ident | SyntaxKind::KeywordImportant)
6329                        && self
6330                            .tokens
6331                            .get(index)
6332                            .is_some_and(|token| token.text.eq_ignore_ascii_case("important"))
6333                })
6334    }
6335
6336    fn current_scss_variable_flag_annotation(&self) -> bool {
6337        matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass)
6338            && self.current_text() == Some("!")
6339            && self
6340                .non_trivia_token_from(self.position + 1)
6341                .is_some_and(|(index, kind)| {
6342                    kind == SyntaxKind::Ident
6343                        && self.tokens.get(index).is_some_and(|token| {
6344                            token.text.eq_ignore_ascii_case("default")
6345                                || token.text.eq_ignore_ascii_case("global")
6346                        })
6347                })
6348    }
6349
6350    fn current_bracketed_value_has_closing_bracket_before(&self, recovery: &[SyntaxKind]) -> bool {
6351        let mut depth = 0usize;
6352        for token in self.tokens.iter().skip(self.position) {
6353            match token.kind {
6354                SyntaxKind::LeftBracket => depth += 1,
6355                SyntaxKind::RightBracket => {
6356                    depth = depth.saturating_sub(1);
6357                    if depth == 0 {
6358                        return true;
6359                    }
6360                }
6361                kind if depth == 1 && recovery.contains(&kind) => return false,
6362                _ => {}
6363            }
6364        }
6365        false
6366    }
6367
6368    fn current_simple_block_has_matching_close(&self, recovery: &[SyntaxKind]) -> bool {
6369        let Some(open_kind) = self.current_kind() else {
6370            return false;
6371        };
6372        if matching_simple_block_close(open_kind).is_none() {
6373            return false;
6374        }
6375
6376        let mut expected_closes = Vec::new();
6377        for token in self.tokens.iter().skip(self.position) {
6378            if let Some(close_kind) = matching_simple_block_close(token.kind) {
6379                expected_closes.push(close_kind);
6380                continue;
6381            }
6382
6383            if expected_closes.last().copied() == Some(token.kind) {
6384                expected_closes.pop();
6385                if expected_closes.is_empty() {
6386                    return true;
6387                }
6388                continue;
6389            }
6390
6391            if expected_closes.len() == 1 && recovery.contains(&token.kind) {
6392                return false;
6393            }
6394        }
6395        false
6396    }
6397
6398    fn current_dialect_at_rule_node_kind(&self, spec: AtRuleSpec) -> SyntaxKind {
6399        if !self.find_rule_block_open_before_recovery(&[
6400            SyntaxKind::Semicolon,
6401            SyntaxKind::SassOptionalSemicolon,
6402            SyntaxKind::RightBrace,
6403            SyntaxKind::SassDedent,
6404        ]) {
6405            return match spec.node_kind {
6406                SyntaxKind::ScssMixinDeclaration => SyntaxKind::BogusScssMixin,
6407                SyntaxKind::ScssFunctionDeclaration => SyntaxKind::BogusScssFunction,
6408                SyntaxKind::ScssControlIf
6409                | SyntaxKind::ScssControlElse
6410                | SyntaxKind::ScssControlEach
6411                | SyntaxKind::ScssControlFor
6412                | SyntaxKind::ScssControlWhile => SyntaxKind::BogusScssControl,
6413                _ => spec.node_kind,
6414            };
6415        }
6416        spec.node_kind
6417    }
6418
6419    fn current_less_guard_has_condition_before(&self, recovery: &[SyntaxKind]) -> bool {
6420        let mut index = self.position + 1;
6421        while let Some(token) = self.tokens.get(index) {
6422            if recovery.contains(&token.kind) {
6423                return false;
6424            }
6425            if token.kind == SyntaxKind::LeftParen {
6426                return true;
6427            }
6428            index += 1;
6429        }
6430        false
6431    }
6432
6433    fn current_scss_module_config_has_balanced_parens(&self) -> bool {
6434        let Some((_, SyntaxKind::LeftParen)) = self.non_trivia_token_from(self.position + 1) else {
6435            return false;
6436        };
6437        self.current_prelude_parentheses_are_balanced_until(&[
6438            SyntaxKind::Semicolon,
6439            SyntaxKind::SassOptionalSemicolon,
6440            SyntaxKind::LeftBrace,
6441            SyntaxKind::SassIndent,
6442        ])
6443    }
6444
6445    fn current_value_has_top_level_comma_before(&self, recovery: &[SyntaxKind]) -> bool {
6446        let mut paren_depth = 0usize;
6447        let mut bracket_depth = 0usize;
6448        for token in self.tokens.iter().skip(self.position) {
6449            match token.kind {
6450                kind if paren_depth == 0 && bracket_depth == 0 && recovery.contains(&kind) => {
6451                    return false;
6452                }
6453                SyntaxKind::LeftParen => paren_depth += 1,
6454                SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
6455                SyntaxKind::LeftBracket => bracket_depth += 1,
6456                SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
6457                SyntaxKind::Comma if paren_depth == 0 && bracket_depth == 0 => return true,
6458                _ => {}
6459            }
6460        }
6461        false
6462    }
6463
6464    fn current_value_list_is_bogus(&self, recovery: &[SyntaxKind]) -> bool {
6465        let mut paren_depth = 0usize;
6466        let mut bracket_depth = 0usize;
6467        let mut expecting_item = true;
6468        for token in self.tokens.iter().skip(self.position) {
6469            if token.kind.is_trivia() {
6470                continue;
6471            }
6472            match token.kind {
6473                kind if paren_depth == 0 && bracket_depth == 0 && recovery.contains(&kind) => {
6474                    return expecting_item;
6475                }
6476                SyntaxKind::LeftParen => {
6477                    paren_depth += 1;
6478                    expecting_item = false;
6479                }
6480                SyntaxKind::RightParen => {
6481                    paren_depth = paren_depth.saturating_sub(1);
6482                    expecting_item = false;
6483                }
6484                SyntaxKind::LeftBracket => {
6485                    bracket_depth += 1;
6486                    expecting_item = false;
6487                }
6488                SyntaxKind::RightBracket => {
6489                    bracket_depth = bracket_depth.saturating_sub(1);
6490                    expecting_item = false;
6491                }
6492                SyntaxKind::Comma if paren_depth == 0 && bracket_depth == 0 => {
6493                    if expecting_item {
6494                        return true;
6495                    }
6496                    expecting_item = true;
6497                }
6498                _ => expecting_item = false,
6499            }
6500        }
6501        expecting_item
6502    }
6503
6504    fn current_starts_missing_semicolon_declaration(&self, recovery: &[SyntaxKind]) -> bool {
6505        match self.current_kind() {
6506            Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {}
6507            _ => return false,
6508        }
6509
6510        let mut index = self.position + 1;
6511        while let Some(token) = self.tokens.get(index) {
6512            if token.kind.is_trivia() {
6513                index += 1;
6514                continue;
6515            }
6516            if recovery.contains(&token.kind) {
6517                return false;
6518            }
6519            return token.kind == SyntaxKind::Colon;
6520        }
6521        false
6522    }
6523
6524    fn current_selector_item_is_bogus(&self, recovery: &[SyntaxKind]) -> bool {
6525        self.selector_item_is_bogus_from(self.position, recovery)
6526    }
6527
6528    fn selector_item_is_bogus_from(&self, start: usize, recovery: &[SyntaxKind]) -> bool {
6529        let mut paren_depth = 0usize;
6530        let mut bracket_depth = 0usize;
6531        let mut saw_selector_token = false;
6532
6533        for token in self.tokens.iter().skip(start) {
6534            if token.kind.is_trivia() {
6535                continue;
6536            }
6537            if paren_depth == 0
6538                && bracket_depth == 0
6539                && (token.kind == SyntaxKind::Comma
6540                    || is_selector_boundary_until(token.kind, recovery))
6541            {
6542                break;
6543            }
6544
6545            match token.kind {
6546                SyntaxKind::LeftParen => paren_depth += 1,
6547                SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
6548                SyntaxKind::LeftBracket => bracket_depth += 1,
6549                SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
6550                _ => {}
6551            }
6552
6553            if !selector_item_token_is_recoverable(token.kind) {
6554                return true;
6555            }
6556            saw_selector_token = true;
6557        }
6558
6559        !saw_selector_token
6560    }
6561
6562    fn selector_list_contains_bogus_item_until(&self, recovery: &[SyntaxKind]) -> bool {
6563        let mut index = self.position;
6564        while let Some(token) = self.tokens.get(index) {
6565            if token.kind.is_trivia() || token.kind == SyntaxKind::Comma {
6566                index += 1;
6567                continue;
6568            }
6569            if is_selector_boundary_until(token.kind, recovery) {
6570                return false;
6571            }
6572            if self.selector_item_is_bogus_from(index, recovery) {
6573                return true;
6574            }
6575
6576            let mut paren_depth = 0usize;
6577            let mut bracket_depth = 0usize;
6578            while let Some(token) = self.tokens.get(index) {
6579                if paren_depth == 0
6580                    && bracket_depth == 0
6581                    && (token.kind == SyntaxKind::Comma
6582                        || is_selector_boundary_until(token.kind, recovery))
6583                {
6584                    break;
6585                }
6586                match token.kind {
6587                    SyntaxKind::LeftParen => paren_depth += 1,
6588                    SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
6589                    SyntaxKind::LeftBracket => bracket_depth += 1,
6590                    SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
6591                    _ => {}
6592                }
6593                index += 1;
6594            }
6595        }
6596        false
6597    }
6598
6599    fn current_generic_at_rule_prelude_node_kind(&self) -> SyntaxKind {
6600        if self.current_prelude_parentheses_are_balanced_until(&[
6601            SyntaxKind::LeftBrace,
6602            SyntaxKind::Semicolon,
6603        ]) {
6604            SyntaxKind::AtRulePrelude
6605        } else {
6606            SyntaxKind::BogusAtRulePrelude
6607        }
6608    }
6609
6610    fn current_prelude_parentheses_are_balanced_until(&self, recovery: &[SyntaxKind]) -> bool {
6611        let mut depth = 0usize;
6612        for token in self.tokens.iter().skip(self.position) {
6613            match token.kind {
6614                kind if depth == 0 && recovery.contains(&kind) => return true,
6615                SyntaxKind::LeftParen => depth += 1,
6616                SyntaxKind::RightParen => {
6617                    if depth == 0 {
6618                        return false;
6619                    }
6620                    depth -= 1;
6621                }
6622                _ => {}
6623            }
6624        }
6625        depth == 0
6626    }
6627
6628    fn previous_left_brace_has_match(&self) -> bool {
6629        let Some(open_index) = self.position.checked_sub(1) else {
6630            return false;
6631        };
6632        let Some(open) = self.tokens.get(open_index) else {
6633            return false;
6634        };
6635        if open.kind != SyntaxKind::LeftBrace {
6636            return false;
6637        }
6638
6639        let mut depth = 0usize;
6640        for token in self.tokens.iter().skip(open_index) {
6641            match token.kind {
6642                SyntaxKind::LeftBrace => depth += 1,
6643                SyntaxKind::RightBrace => {
6644                    depth = depth.saturating_sub(1);
6645                    if depth == 0 {
6646                        return true;
6647                    }
6648                }
6649                _ => {}
6650            }
6651        }
6652        false
6653    }
6654
6655    fn current_starts_nested_rule(&self) -> bool {
6656        matches!(
6657            self.current_kind(),
6658            Some(
6659                SyntaxKind::Dot
6660                    | SyntaxKind::Hash
6661                    | SyntaxKind::Ampersand
6662                    | SyntaxKind::Colon
6663                    | SyntaxKind::DoubleColon
6664                    | SyntaxKind::LeftBracket
6665            )
6666        ) && self.find_rule_block_open_before_recovery(&[
6667            SyntaxKind::Colon,
6668            SyntaxKind::Semicolon,
6669            SyntaxKind::SassOptionalSemicolon,
6670            SyntaxKind::RightBrace,
6671            SyntaxKind::SassDedent,
6672        ])
6673    }
6674
6675    fn current_starts_scss_nested_property(&self) -> bool {
6676        if !matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass) {
6677            return false;
6678        }
6679        if !matches!(
6680            self.current_kind(),
6681            Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
6682        ) {
6683            return false;
6684        }
6685
6686        let mut saw_colon = false;
6687        for token in self.tokens.iter().skip(self.position) {
6688            match token.kind {
6689                SyntaxKind::Colon => saw_colon = true,
6690                SyntaxKind::LeftBrace if saw_colon => return true,
6691                SyntaxKind::SassIndent if saw_colon && self.dialect == StyleDialect::Sass => {
6692                    return true;
6693                }
6694                SyntaxKind::Semicolon
6695                | SyntaxKind::SassOptionalSemicolon
6696                | SyntaxKind::RightBrace
6697                | SyntaxKind::SassDedent => return false,
6698                _ => {}
6699            }
6700        }
6701        false
6702    }
6703
6704    fn current_starts_less_mixin_declaration(&self) -> bool {
6705        self.dialect == StyleDialect::Less
6706            && self.current_starts_less_callable_signature()
6707            && self.find_before_recovery(
6708                SyntaxKind::LeftBrace,
6709                &[SyntaxKind::Semicolon, SyntaxKind::RightBrace],
6710            )
6711    }
6712
6713    fn current_starts_less_mixin_call(&self) -> bool {
6714        self.dialect == StyleDialect::Less
6715            && self.current_starts_less_callable_signature()
6716            && !self.find_before_recovery(
6717                SyntaxKind::LeftBrace,
6718                &[SyntaxKind::Semicolon, SyntaxKind::RightBrace],
6719            )
6720    }
6721
6722    fn current_starts_less_callable_signature(&self) -> bool {
6723        match self.current_kind() {
6724            Some(SyntaxKind::Dot) => {
6725                let Some((index, SyntaxKind::Ident | SyntaxKind::CustomPropertyName)) =
6726                    self.non_trivia_token_from(self.position + 1)
6727                else {
6728                    return false;
6729                };
6730                self.non_trivia_token_from(index + 1)
6731                    .is_some_and(|(_, kind)| kind == SyntaxKind::LeftParen)
6732            }
6733            Some(SyntaxKind::Hash) => self
6734                .non_trivia_token_from(self.position + 1)
6735                .is_some_and(|(_, kind)| kind == SyntaxKind::LeftParen),
6736            _ => false,
6737        }
6738    }
6739
6740    fn current_starts_less_extend_rule(&self) -> bool {
6741        self.dialect == StyleDialect::Less
6742            && self.current_kind() == Some(SyntaxKind::Colon)
6743            && self
6744                .non_trivia_token_from(self.position + 1)
6745                .is_some_and(|(index, kind)| {
6746                    kind == SyntaxKind::Ident
6747                        && self
6748                            .tokens
6749                            .get(index)
6750                            .is_some_and(|token| token.text == "extend")
6751                })
6752    }
6753
6754    fn current_starts_less_namespace_access(&self) -> bool {
6755        self.dialect == StyleDialect::Less
6756            && matches!(
6757                self.current_kind(),
6758                Some(SyntaxKind::Dot | SyntaxKind::Hash)
6759            )
6760            && self.find_before_recovery(
6761                SyntaxKind::GreaterThan,
6762                &[
6763                    SyntaxKind::Semicolon,
6764                    SyntaxKind::LeftBrace,
6765                    SyntaxKind::RightBrace,
6766                ],
6767            )
6768            && self.find_before_recovery(
6769                SyntaxKind::LeftParen,
6770                &[
6771                    SyntaxKind::Semicolon,
6772                    SyntaxKind::LeftBrace,
6773                    SyntaxKind::RightBrace,
6774                ],
6775            )
6776    }
6777
6778    fn current_left_brace_has_match(&self) -> bool {
6779        let mut depth = 0usize;
6780        for token in self.tokens.iter().skip(self.position) {
6781            match token.kind {
6782                SyntaxKind::LeftBrace => depth += 1,
6783                SyntaxKind::RightBrace => {
6784                    depth = depth.saturating_sub(1);
6785                    if depth == 0 {
6786                        return true;
6787                    }
6788                }
6789                _ => {}
6790            }
6791        }
6792        false
6793    }
6794
6795    fn token_current(&mut self) {
6796        if let Some(token) = self.tokens.get(self.position).copied() {
6797            self.builder.token(token.kind, token.text);
6798            self.position += 1;
6799        }
6800    }
6801
6802    fn empty_bogus_node(&mut self, kind: SyntaxKind, code: ParseErrorCode, message: &'static str) {
6803        self.builder.start_node(kind);
6804        self.builder.finish_node();
6805        self.error_at_current(code, message);
6806    }
6807
6808    fn missing_token_bogus_trivia(&mut self, code: ParseErrorCode, message: &'static str) {
6809        self.builder.start_node(SyntaxKind::BogusTrivia);
6810        self.builder.finish_node();
6811        self.error_at_current(code, message);
6812    }
6813
6814    fn error_at_current(&mut self, code: ParseErrorCode, message: &'static str) {
6815        self.errors.push(ParseError {
6816            code,
6817            range: self.current_range(),
6818            message,
6819        });
6820    }
6821
6822    fn current_kind(&self) -> Option<SyntaxKind> {
6823        self.tokens.get(self.position).map(|token| token.kind)
6824    }
6825
6826    fn current_range(&self) -> TextRange {
6827        if let Some(token) = self.tokens.get(self.position) {
6828            return token.range;
6829        }
6830        let end = self
6831            .tokens
6832            .last()
6833            .map(|token| token.range.end())
6834            .unwrap_or_else(|| TextSize::from(0));
6835        TextRange::new(end, end)
6836    }
6837
6838    fn current_text(&self) -> Option<&'text str> {
6839        self.tokens.get(self.position).map(|token| token.text)
6840    }
6841
6842    fn current_dialect_at_rule_spec(&self) -> Option<AtRuleSpec> {
6843        let text = self.current_text()?;
6844        match self.dialect {
6845            StyleDialect::Scss | StyleDialect::Sass => scss_at_rule_spec(text),
6846            StyleDialect::Css | StyleDialect::Less => None,
6847        }
6848    }
6849
6850    fn current_is_css_module_value_rule(&self) -> bool {
6851        self.current_text() == Some("@value")
6852    }
6853
6854    fn next_kind(&self) -> Option<SyntaxKind> {
6855        self.tokens.get(self.position + 1).map(|token| token.kind)
6856    }
6857
6858    fn next_non_trivia_kind(&self) -> Option<SyntaxKind> {
6859        let mut index = self.position + 1;
6860        while let Some(token) = self.tokens.get(index) {
6861            if !token.kind.is_trivia() {
6862                return Some(token.kind);
6863            }
6864            index += 1;
6865        }
6866        None
6867    }
6868
6869    fn non_trivia_token_from(&self, mut index: usize) -> Option<(usize, SyntaxKind)> {
6870        while let Some(token) = self.tokens.get(index) {
6871            if !token.kind.is_trivia() {
6872                return Some((index, token.kind));
6873            }
6874            index += 1;
6875        }
6876        None
6877    }
6878
6879    fn non_trivia_token_after_interpolation(
6880        &self,
6881        mut index: usize,
6882        start_kind: SyntaxKind,
6883    ) -> Option<(usize, SyntaxKind)> {
6884        let end_kind = interpolation_end_kind(start_kind)?;
6885        index += 1;
6886        while let Some(token) = self.tokens.get(index) {
6887            if token.kind == end_kind {
6888                return self.non_trivia_token_from(index + 1);
6889            }
6890            if is_at_rule_prelude_boundary(token.kind) {
6891                return None;
6892            }
6893            index += 1;
6894        }
6895        None
6896    }
6897
6898    fn current_starts_namespace_qualified_selector(&self, kind: SyntaxKind) -> bool {
6899        match kind {
6900            SyntaxKind::Ident | SyntaxKind::Star => {
6901                self.next_kind() == Some(SyntaxKind::Pipe)
6902                    && self
6903                        .tokens
6904                        .get(self.position + 2)
6905                        .is_some_and(|token| namespace_selector_target_can_start(token.kind))
6906            }
6907            SyntaxKind::Pipe => self
6908                .tokens
6909                .get(self.position + 1)
6910                .is_some_and(|token| namespace_selector_target_can_start(token.kind)),
6911            _ => false,
6912        }
6913    }
6914
6915    fn namespace_qualified_selector_target_kind(&self) -> Option<SyntaxKind> {
6916        let target_index = if self.current_kind() == Some(SyntaxKind::Pipe) {
6917            self.position + 1
6918        } else {
6919            self.position + 2
6920        };
6921        self.tokens.get(target_index).map(|token| token.kind)
6922    }
6923
6924    fn at_end(&self) -> bool {
6925        self.position >= self.tokens.len()
6926    }
6927}
6928
6929impl<'text, 'extension, E> Tokenizer<'text, 'extension, E>
6930where
6931    E: DialectExtension,
6932{
6933    fn new(text: &'text str, extension: &'extension E) -> Self {
6934        Self {
6935            text,
6936            extension,
6937            offset: 0,
6938            scss_interpolation_depth: 0,
6939            less_interpolation_depth: 0,
6940            sass_indent_stack: vec![0],
6941            tokens: Vec::new(),
6942            errors: Vec::new(),
6943        }
6944    }
6945
6946    fn tokenize(&mut self) {
6947        while let Some(current) = self.current_char() {
6948            let start = self.offset;
6949            match current {
6950                '\u{feff}' if start == 0 => self.bump_current(),
6951                '\r' | '\n' if self.extension.dialect() == StyleDialect::Sass => {
6952                    self.consume_sass_indented_newline(start)
6953                }
6954                char if char.is_whitespace() => {
6955                    self.consume_while(SyntaxKind::Whitespace, |c| c.is_whitespace())
6956                }
6957                '/' if self.starts_with("/*") => self.consume_block_comment(),
6958                '/' if self.starts_with("//") && self.extension.dialect() != StyleDialect::Css => {
6959                    self.consume_line_comment()
6960                }
6961                '#' if self.starts_with("#{") && self.supports_scss_interpolation() => {
6962                    self.consume_scss_interpolation_start(start)
6963                }
6964                '@' if self.starts_with("@{") && self.supports_less_interpolation() => {
6965                    self.consume_less_interpolation_start(start)
6966                }
6967                '!' if self.starts_with_ascii_keyword("!important") => {
6968                    self.consume_static(SyntaxKind::Important, start, "!important".len())
6969                }
6970                '<' if self.starts_with("<!--") => {
6971                    self.consume_static(SyntaxKind::Cdo, start, "<!--".len())
6972                }
6973                '-' if self.starts_with("-->") => {
6974                    self.consume_static(SyntaxKind::Cdc, start, "-->".len())
6975                }
6976                '"' | '\'' => self.consume_string(current),
6977                'u' | 'U' if self.starts_unicode_range() => self.consume_unicode_range(),
6978                '0'..='9' => self.consume_number(),
6979                '$' if matches!(
6980                    self.extension.dialect(),
6981                    StyleDialect::Scss | StyleDialect::Sass
6982                ) =>
6983                {
6984                    self.consume_prefixed_name(SyntaxKind::ScssVariable)
6985                }
6986                '@' if self.extension.dialect() == StyleDialect::Less => {
6987                    self.consume_less_at_name()
6988                }
6989                '@' => self.consume_at_keyword(),
6990                '!' => self.consume_static(SyntaxKind::Delim, start, 1),
6991                '.' if self.current_starts_number() => self.consume_number(),
6992                '.' => self.consume_static(SyntaxKind::Dot, start, 1),
6993                ',' => self.consume_static(SyntaxKind::Comma, start, 1),
6994                ':' if self.starts_with("::") => {
6995                    self.consume_static(SyntaxKind::DoubleColon, start, 2)
6996                }
6997                ':' => self.consume_static(SyntaxKind::Colon, start, 1),
6998                ';' => self.consume_static(SyntaxKind::Semicolon, start, 1),
6999                '{' => self.consume_static(SyntaxKind::LeftBrace, start, 1),
7000                '}' if self.scss_interpolation_depth > 0 => {
7001                    self.consume_scss_interpolation_end(start)
7002                }
7003                '}' if self.less_interpolation_depth > 0 => {
7004                    self.consume_less_interpolation_end(start)
7005                }
7006                '}' => self.consume_static(SyntaxKind::RightBrace, start, 1),
7007                '(' => self.consume_static(SyntaxKind::LeftParen, start, 1),
7008                ')' => self.consume_static(SyntaxKind::RightParen, start, 1),
7009                '[' => self.consume_static(SyntaxKind::LeftBracket, start, 1),
7010                ']' => self.consume_static(SyntaxKind::RightBracket, start, 1),
7011                '+' if self.starts_with("+=") => {
7012                    self.consume_static(SyntaxKind::PlusEquals, start, 2)
7013                }
7014                '+' if self.current_starts_number() => self.consume_number(),
7015                '+' => self.consume_static(SyntaxKind::Plus, start, 1),
7016                '-' if self.starts_with("-=") => {
7017                    self.consume_static(SyntaxKind::MinusEquals, start, 2)
7018                }
7019                '-' if self.current_starts_number() => self.consume_number(),
7020                '-' if self.current_starts_ident_sequence() => self.consume_ident_like(),
7021                '-' => self.consume_static(SyntaxKind::Minus, start, 1),
7022                '*' if self.starts_with("*=") => {
7023                    self.consume_static(SyntaxKind::SubstringMatch, start, 2)
7024                }
7025                '*' => self.consume_static(SyntaxKind::Star, start, 1),
7026                '/' if self.starts_with("/=") => {
7027                    self.consume_static(SyntaxKind::SlashEquals, start, 2)
7028                }
7029                '/' => self.consume_static(SyntaxKind::Slash, start, 1),
7030                '%' if self.starts_scss_placeholder() => {
7031                    self.consume_prefixed_name(SyntaxKind::ScssPlaceholder)
7032                }
7033                '%' => self.consume_static(SyntaxKind::Percent, start, 1),
7034                '=' if self.starts_with("=>") => self.consume_static(SyntaxKind::Arrow, start, 2),
7035                '=' => self.consume_static(SyntaxKind::Equals, start, 1),
7036                '~' if self.starts_less_escaped_string() => self.consume_less_escaped_string(start),
7037                '~' if self.starts_with("~=") => {
7038                    self.consume_static(SyntaxKind::IncludesMatch, start, 2)
7039                }
7040                '~' => self.consume_static(SyntaxKind::Tilde, start, 1),
7041                '|' if self.starts_with("|=") => {
7042                    self.consume_static(SyntaxKind::DashMatch, start, 2)
7043                }
7044                '|' if self.starts_with("||") => {
7045                    self.consume_static(SyntaxKind::ColumnCombinator, start, 2)
7046                }
7047                '|' => self.consume_static(SyntaxKind::Pipe, start, 1),
7048                '^' if self.starts_with("^=") => {
7049                    self.consume_static(SyntaxKind::PrefixMatch, start, 2)
7050                }
7051                '^' => self.consume_static(SyntaxKind::Caret, start, 1),
7052                '$' if self.starts_with("$=") => {
7053                    self.consume_static(SyntaxKind::SuffixMatch, start, 2)
7054                }
7055                '$' if self.starts_less_property_variable() => {
7056                    self.consume_prefixed_name(SyntaxKind::LessPropertyVariableToken)
7057                }
7058                '&' if self.starts_with("&&") => {
7059                    self.consume_static(SyntaxKind::DoubleAmpersand, start, 2)
7060                }
7061                '&' => self.consume_static(SyntaxKind::Ampersand, start, 1),
7062                '>' => self.consume_static(SyntaxKind::GreaterThan, start, 1),
7063                '<' => self.consume_static(SyntaxKind::LessThan, start, 1),
7064                '#' if self.current_hash_starts_name() => self.consume_name_like(SyntaxKind::Hash),
7065                '#' => self.consume_static(SyntaxKind::Delim, start, 1),
7066                '\\' if self.current_starts_valid_escape() => {
7067                    self.consume_name_like(SyntaxKind::Ident)
7068                }
7069                char if is_name_start(char) => self.consume_ident_like(),
7070                char => self.consume_unexpected(char),
7071            }
7072        }
7073        self.consume_pending_sass_dedents();
7074    }
7075
7076    fn consume_static(&mut self, kind: SyntaxKind, start: usize, byte_len: usize) {
7077        self.offset += byte_len;
7078        self.push(kind, start, self.offset);
7079    }
7080
7081    fn consume_while(&mut self, kind: SyntaxKind, predicate: impl Fn(char) -> bool) {
7082        let start = self.offset;
7083        while let Some(char) = self.current_char() {
7084            if !predicate(char) {
7085                break;
7086            }
7087            self.bump_char(char);
7088        }
7089        self.push(kind, start, self.offset);
7090    }
7091
7092    fn consume_block_comment(&mut self) {
7093        let start = self.offset;
7094        self.offset += 2;
7095        while self.offset < self.text.len() {
7096            if self.starts_with("*/") {
7097                self.offset += 2;
7098                self.push(SyntaxKind::BlockComment, start, self.offset);
7099                return;
7100            }
7101            match self.current_char() {
7102                Some(char) => self.bump_char(char),
7103                None => break,
7104            }
7105        }
7106        self.push(SyntaxKind::BlockComment, start, self.offset);
7107        self.error(
7108            ParseErrorCode::UnterminatedBlockComment,
7109            start,
7110            self.offset,
7111            "unterminated block comment",
7112        );
7113    }
7114
7115    fn consume_line_comment(&mut self) {
7116        let start = self.offset;
7117        while let Some(char) = self.current_char() {
7118            if char == '\n' {
7119                break;
7120            }
7121            if char == '\r' {
7122                break;
7123            }
7124            self.bump_char(char);
7125        }
7126        self.push(SyntaxKind::LineComment, start, self.offset);
7127    }
7128
7129    fn consume_sass_indented_newline(&mut self, start: usize) {
7130        self.consume_line_break();
7131        let indent = self.consume_sass_line_indent();
7132        let line_start = self.offset;
7133        let current_indent = self.sass_indent_stack.last().copied().unwrap_or(0);
7134
7135        if indent > current_indent {
7136            self.push(SyntaxKind::SassIndentedNewline, start, line_start);
7137            self.sass_indent_stack.push(indent);
7138            self.push(SyntaxKind::SassIndent, line_start, line_start);
7139            return;
7140        }
7141
7142        if self.previous_significant_sass_token_can_end_statement() {
7143            self.push(SyntaxKind::SassOptionalSemicolon, start, start);
7144        }
7145        self.push(SyntaxKind::SassIndentedNewline, start, line_start);
7146
7147        while self.sass_indent_stack.len() > 1
7148            && self
7149                .sass_indent_stack
7150                .last()
7151                .is_some_and(|current| indent < *current)
7152        {
7153            self.sass_indent_stack.pop();
7154            self.push(SyntaxKind::SassDedent, line_start, line_start);
7155        }
7156
7157        if self
7158            .sass_indent_stack
7159            .last()
7160            .is_some_and(|current| indent != *current)
7161        {
7162            self.error(
7163                ParseErrorCode::UnexpectedCharacter,
7164                line_start,
7165                line_start,
7166                "inconsistent Sass indentation",
7167            );
7168        }
7169    }
7170
7171    fn consume_line_break(&mut self) {
7172        if self.starts_with("\r\n") {
7173            self.offset += "\r\n".len();
7174            return;
7175        }
7176        if let Some(char @ ('\r' | '\n')) = self.current_char() {
7177            self.bump_char(char);
7178        }
7179    }
7180
7181    fn consume_sass_line_indent(&mut self) -> usize {
7182        let mut indent = 0usize;
7183        while let Some(char) = self.current_char() {
7184            match char {
7185                ' ' => {
7186                    indent += 1;
7187                    self.bump_char(char);
7188                }
7189                '\t' => {
7190                    indent += 4;
7191                    self.bump_char(char);
7192                }
7193                _ => break,
7194            }
7195        }
7196        indent
7197    }
7198
7199    fn consume_pending_sass_dedents(&mut self) {
7200        if self.extension.dialect() != StyleDialect::Sass {
7201            return;
7202        }
7203        while self.sass_indent_stack.len() > 1 {
7204            self.sass_indent_stack.pop();
7205            self.push(SyntaxKind::SassDedent, self.offset, self.offset);
7206        }
7207    }
7208
7209    fn previous_significant_sass_token_can_end_statement(&self) -> bool {
7210        self.tokens
7211            .iter()
7212            .rev()
7213            .find(|token| !token.kind.is_trivia())
7214            .is_some_and(|token| sass_token_can_end_statement(token.kind))
7215    }
7216
7217    fn consume_scss_interpolation_start(&mut self, start: usize) {
7218        self.offset += "#{".len();
7219        self.scss_interpolation_depth += 1;
7220        self.push(SyntaxKind::ScssInterpolationStart, start, self.offset);
7221    }
7222
7223    fn consume_scss_interpolation_end(&mut self, start: usize) {
7224        self.offset += '}'.len_utf8();
7225        self.scss_interpolation_depth = self.scss_interpolation_depth.saturating_sub(1);
7226        self.push(SyntaxKind::ScssInterpolationEnd, start, self.offset);
7227    }
7228
7229    fn consume_less_interpolation_start(&mut self, start: usize) {
7230        self.offset += "@{".len();
7231        self.less_interpolation_depth += 1;
7232        self.push(SyntaxKind::LessInterpolationStart, start, self.offset);
7233    }
7234
7235    fn consume_less_interpolation_end(&mut self, start: usize) {
7236        self.offset += '}'.len_utf8();
7237        self.less_interpolation_depth = self.less_interpolation_depth.saturating_sub(1);
7238        self.push(SyntaxKind::LessInterpolationEnd, start, self.offset);
7239    }
7240
7241    fn consume_string(&mut self, quote: char) {
7242        let start = self.offset;
7243        self.bump_char(quote);
7244        while let Some(char) = self.current_char() {
7245            self.bump_char(char);
7246            if matches!(char, '\n' | '\r' | '\u{000c}') {
7247                self.push(SyntaxKind::BadString, start, self.offset);
7248                self.error(
7249                    ParseErrorCode::UnterminatedString,
7250                    start,
7251                    self.offset,
7252                    "unterminated string",
7253                );
7254                return;
7255            }
7256            if char == quote {
7257                self.push(SyntaxKind::String, start, self.offset);
7258                return;
7259            }
7260            if char == '\\'
7261                && let Some(escaped) = self.current_char()
7262            {
7263                self.bump_char(escaped);
7264            }
7265        }
7266        self.push(SyntaxKind::BadString, start, self.offset);
7267        self.error(
7268            ParseErrorCode::UnterminatedString,
7269            start,
7270            self.offset,
7271            "unterminated string",
7272        );
7273    }
7274
7275    fn consume_less_escaped_string(&mut self, start: usize) {
7276        self.offset += '~'.len_utf8();
7277        let Some(quote @ ('"' | '\'')) = self.current_char() else {
7278            self.push(SyntaxKind::Tilde, start, self.offset);
7279            return;
7280        };
7281        self.bump_char(quote);
7282        while let Some(char) = self.current_char() {
7283            self.bump_char(char);
7284            if matches!(char, '\n' | '\r' | '\u{000c}') {
7285                self.push(SyntaxKind::BadString, start, self.offset);
7286                self.error(
7287                    ParseErrorCode::UnterminatedString,
7288                    start,
7289                    self.offset,
7290                    "unterminated Less escaped string",
7291                );
7292                return;
7293            }
7294            if char == quote {
7295                self.push(SyntaxKind::LessEscapedString, start, self.offset);
7296                return;
7297            }
7298            if char == '\\'
7299                && let Some(escaped) = self.current_char()
7300            {
7301                self.bump_char(escaped);
7302            }
7303        }
7304        self.push(SyntaxKind::BadString, start, self.offset);
7305        self.error(
7306            ParseErrorCode::UnterminatedString,
7307            start,
7308            self.offset,
7309            "unterminated Less escaped string",
7310        );
7311    }
7312
7313    fn consume_number(&mut self) {
7314        let start = self.offset;
7315        if matches!(self.current_char(), Some('+' | '-')) {
7316            self.bump_current();
7317        }
7318        self.consume_digits();
7319        if self.current_char() == Some('.') && self.char_after_current_is_ascii_digit() {
7320            self.bump_current();
7321            self.consume_digits();
7322        }
7323        if self.current_starts_number_exponent() {
7324            self.bump_current();
7325            if matches!(self.current_char(), Some('+' | '-')) {
7326                self.bump_current();
7327            }
7328            self.consume_digits();
7329        }
7330        if self.current_char() == Some('%') {
7331            self.offset += 1;
7332            self.push(SyntaxKind::Percentage, start, self.offset);
7333            return;
7334        }
7335        if self.current_starts_ident_sequence() {
7336            self.consume_name_continue_sequence();
7337            self.push(SyntaxKind::Dimension, start, self.offset);
7338            return;
7339        }
7340        self.push(SyntaxKind::Number, start, self.offset);
7341    }
7342
7343    fn consume_unicode_range(&mut self) {
7344        let start = self.offset;
7345        self.bump_current();
7346        self.offset += '+'.len_utf8();
7347        self.consume_unicode_range_codepoints(true);
7348        if self.current_char() == Some('-') && self.next_char_is_hex_digit() {
7349            self.bump_current();
7350            self.consume_unicode_range_codepoints(false);
7351        }
7352        self.push(SyntaxKind::UnicodeRange, start, self.offset);
7353    }
7354
7355    fn consume_unicode_range_codepoints(&mut self, allow_question_mark: bool) {
7356        let mut consumed = 0usize;
7357        while consumed < 6 {
7358            match self.current_char() {
7359                Some(char) if char.is_ascii_hexdigit() => {
7360                    self.bump_char(char);
7361                    consumed += 1;
7362                }
7363                Some('?') if allow_question_mark => {
7364                    self.bump_current();
7365                    consumed += 1;
7366                }
7367                _ => break,
7368            }
7369        }
7370    }
7371
7372    fn consume_digits(&mut self) {
7373        while matches!(self.current_char(), Some('0'..='9')) {
7374            self.offset += 1;
7375        }
7376    }
7377
7378    fn consume_prefixed_name(&mut self, preferred_kind: SyntaxKind) {
7379        let start = self.offset;
7380        self.bump_current();
7381        while matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7382            self.bump_current();
7383        }
7384        let text = &self.text[start..self.offset];
7385        let kind = self
7386            .extension
7387            .classify_variable_token(text)
7388            .unwrap_or(preferred_kind);
7389        self.push(kind, start, self.offset);
7390    }
7391
7392    fn consume_less_at_name(&mut self) {
7393        let start = self.offset;
7394        self.bump_current();
7395        while matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7396            self.bump_current();
7397        }
7398        let text = &self.text[start..self.offset];
7399        let kind = if is_css_at_rule_name(text) {
7400            SyntaxKind::AtKeyword
7401        } else {
7402            self.extension
7403                .classify_variable_token(text)
7404                .unwrap_or(SyntaxKind::LessVariable)
7405        };
7406        self.push(kind, start, self.offset);
7407    }
7408
7409    fn consume_at_keyword(&mut self) {
7410        let start = self.offset;
7411        self.bump_current();
7412        while matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7413            self.bump_current();
7414        }
7415        self.push(SyntaxKind::AtKeyword, start, self.offset);
7416    }
7417
7418    fn consume_name_like(&mut self, kind: SyntaxKind) {
7419        let start = self.offset;
7420        self.consume_name_start();
7421        self.consume_name_continue_sequence();
7422        self.push(kind, start, self.offset);
7423    }
7424
7425    fn consume_ident_like(&mut self) {
7426        let start = self.offset;
7427        self.consume_name_continue_sequence();
7428        let ident = &self.text[start..self.offset];
7429        if ident.eq_ignore_ascii_case("url")
7430            && self.current_char() == Some('(')
7431            && !self.url_starts_with_quoted_argument()
7432        {
7433            self.consume_url_token(start);
7434            return;
7435        }
7436        let kind = if is_custom_property_name_text(ident) {
7437            SyntaxKind::CustomPropertyName
7438        } else {
7439            SyntaxKind::Ident
7440        };
7441        self.push(kind, start, self.offset);
7442    }
7443
7444    fn consume_name_start(&mut self) {
7445        if self.current_starts_valid_escape() {
7446            self.consume_name_escape();
7447        } else {
7448            self.bump_current();
7449        }
7450    }
7451
7452    fn consume_name_continue_sequence(&mut self) {
7453        loop {
7454            if self.current_starts_valid_escape() {
7455                self.consume_name_escape();
7456            } else if matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7457                self.bump_current();
7458            } else {
7459                break;
7460            }
7461        }
7462    }
7463
7464    fn consume_name_escape(&mut self) {
7465        self.bump_current();
7466        let mut hex_digits = 0usize;
7467        while hex_digits < 6
7468            && matches!(self.current_char(), Some(char) if char.is_ascii_hexdigit())
7469        {
7470            self.bump_current();
7471            hex_digits += 1;
7472        }
7473        if hex_digits > 0 {
7474            if matches!(self.current_char(), Some(char) if char.is_whitespace()) {
7475                self.bump_current();
7476            }
7477        } else if self.current_char().is_some() {
7478            self.bump_current();
7479        }
7480    }
7481
7482    fn consume_url_token(&mut self, start: usize) {
7483        self.bump_current();
7484        while matches!(self.current_char(), Some(char) if char.is_whitespace()) {
7485            self.bump_current();
7486        }
7487        while let Some(char) = self.current_char() {
7488            match char {
7489                ')' => {
7490                    self.bump_current();
7491                    self.push(SyntaxKind::Url, start, self.offset);
7492                    return;
7493                }
7494                char if char.is_whitespace() => {
7495                    self.bump_current();
7496                    while matches!(self.current_char(), Some(char) if char.is_whitespace()) {
7497                        self.bump_current();
7498                    }
7499                    if self.current_char() == Some(')') {
7500                        self.bump_current();
7501                        self.push(SyntaxKind::Url, start, self.offset);
7502                        return;
7503                    }
7504                    self.consume_bad_url(start);
7505                    return;
7506                }
7507                '"' | '\'' | '(' => {
7508                    self.consume_bad_url(start);
7509                    return;
7510                }
7511                '\\' if self.current_starts_valid_escape() => {
7512                    self.consume_name_escape();
7513                }
7514                '\\' => {
7515                    self.consume_bad_url(start);
7516                    return;
7517                }
7518                char if is_non_printable_code_point(char) => {
7519                    self.consume_bad_url(start);
7520                    return;
7521                }
7522                _ => self.bump_current(),
7523            }
7524        }
7525        self.push(SyntaxKind::BadUrl, start, self.offset);
7526        self.error(
7527            ParseErrorCode::UnexpectedCharacter,
7528            start,
7529            self.offset,
7530            "unterminated url token",
7531        );
7532    }
7533
7534    fn consume_bad_url(&mut self, start: usize) {
7535        while let Some(char) = self.current_char() {
7536            if char == ')' {
7537                self.bump_current();
7538                break;
7539            }
7540            if self.current_starts_valid_escape() {
7541                self.consume_name_escape();
7542            } else {
7543                self.bump_current();
7544            }
7545        }
7546        self.push(SyntaxKind::BadUrl, start, self.offset);
7547        self.error(
7548            ParseErrorCode::UnexpectedCharacter,
7549            start,
7550            self.offset,
7551            "bad url token",
7552        );
7553    }
7554
7555    fn url_starts_with_quoted_argument(&self) -> bool {
7556        let Some(mut rest) = self.text.get(self.offset + '('.len_utf8()..) else {
7557            return false;
7558        };
7559        rest = rest.trim_start_matches(char::is_whitespace);
7560        matches!(rest.chars().next(), Some('"' | '\''))
7561    }
7562
7563    fn starts_less_property_variable(&self) -> bool {
7564        self.extension.dialect() == StyleDialect::Less
7565            && self.text[self.offset + '$'.len_utf8()..]
7566                .chars()
7567                .next()
7568                .is_some_and(is_name_start)
7569    }
7570
7571    fn starts_scss_placeholder(&self) -> bool {
7572        matches!(
7573            self.extension.dialect(),
7574            StyleDialect::Scss | StyleDialect::Sass
7575        ) && self.text[self.offset + '%'.len_utf8()..]
7576            .chars()
7577            .next()
7578            .is_some_and(is_name_start)
7579    }
7580
7581    fn current_hash_starts_name(&self) -> bool {
7582        if self.current_char() != Some('#') {
7583            return false;
7584        }
7585        let next_offset = self.offset + '#'.len_utf8();
7586        self.text[next_offset..]
7587            .chars()
7588            .next()
7589            .is_some_and(is_name_continue)
7590            || self.escape_starts_at(next_offset)
7591    }
7592
7593    fn consume_unexpected(&mut self, char: char) {
7594        let start = self.offset;
7595        self.bump_char(char);
7596        self.push(SyntaxKind::Delim, start, self.offset);
7597        self.error(
7598            ParseErrorCode::UnexpectedCharacter,
7599            start,
7600            self.offset,
7601            "unexpected character",
7602        );
7603    }
7604
7605    fn push(&mut self, kind: SyntaxKind, start: usize, end: usize) {
7606        self.tokens.push(Token {
7607            kind,
7608            text: &self.text[start..end],
7609            range: text_range(start, end),
7610        });
7611    }
7612
7613    fn error(&mut self, code: ParseErrorCode, start: usize, end: usize, message: &'static str) {
7614        self.errors.push(ParseError {
7615            code,
7616            range: text_range(start, end),
7617            message,
7618        });
7619    }
7620
7621    fn starts_with(&self, pattern: &str) -> bool {
7622        self.text[self.offset..].starts_with(pattern)
7623    }
7624
7625    fn current_starts_valid_escape(&self) -> bool {
7626        self.escape_starts_at(self.offset)
7627    }
7628
7629    fn current_starts_number(&self) -> bool {
7630        self.starts_number_at(self.offset)
7631    }
7632
7633    fn current_starts_number_exponent(&self) -> bool {
7634        let Some('e' | 'E') = self.current_char() else {
7635            return false;
7636        };
7637        let exponent_offset = self.offset + 'e'.len_utf8();
7638        self.char_at(exponent_offset)
7639            .is_some_and(|char| char.is_ascii_digit())
7640            || (matches!(self.char_at(exponent_offset), Some('+' | '-'))
7641                && self.char_after_offset_is_ascii_digit(exponent_offset))
7642    }
7643
7644    fn starts_number_at(&self, offset: usize) -> bool {
7645        let Some(first) = self.char_at(offset) else {
7646            return false;
7647        };
7648        let second_offset = offset + first.len_utf8();
7649        match first {
7650            '+' | '-' => {
7651                self.char_at(second_offset)
7652                    .is_some_and(|char| char.is_ascii_digit())
7653                    || (self.char_at(second_offset) == Some('.')
7654                        && self.char_after_offset_is_ascii_digit(second_offset))
7655            }
7656            '.' => self.char_after_offset_is_ascii_digit(offset),
7657            char => char.is_ascii_digit(),
7658        }
7659    }
7660
7661    fn current_starts_ident_sequence(&self) -> bool {
7662        self.starts_ident_sequence_at(self.offset)
7663    }
7664
7665    fn starts_ident_sequence_at(&self, offset: usize) -> bool {
7666        let Some(first) = self.char_at(offset) else {
7667            return false;
7668        };
7669        let second_offset = offset + first.len_utf8();
7670        match first {
7671            '-' => {
7672                self.char_at(second_offset)
7673                    .is_some_and(|char| char == '-' || is_name_start(char))
7674                    || self.escape_starts_at(second_offset)
7675            }
7676            '\\' => self.escape_starts_at(offset),
7677            char => is_name_start(char),
7678        }
7679    }
7680
7681    fn escape_starts_at(&self, offset: usize) -> bool {
7682        if !self
7683            .text
7684            .get(offset..)
7685            .is_some_and(|remaining| remaining.starts_with('\\'))
7686        {
7687            return false;
7688        }
7689        self.text[offset + '\\'.len_utf8()..]
7690            .chars()
7691            .next()
7692            .is_some_and(|char| !matches!(char, '\n' | '\r' | '\u{000c}'))
7693    }
7694
7695    fn char_at(&self, offset: usize) -> Option<char> {
7696        self.text.get(offset..)?.chars().next()
7697    }
7698
7699    fn char_after_current_is_ascii_digit(&self) -> bool {
7700        self.char_after_offset_is_ascii_digit(self.offset)
7701    }
7702
7703    fn char_after_offset_is_ascii_digit(&self, offset: usize) -> bool {
7704        let Some(char) = self.char_at(offset) else {
7705            return false;
7706        };
7707        self.char_at(offset + char.len_utf8())
7708            .is_some_and(|char| char.is_ascii_digit())
7709    }
7710
7711    fn starts_with_ascii_keyword(&self, keyword: &str) -> bool {
7712        let remaining = &self.text[self.offset..];
7713        let Some(prefix) = remaining.get(..keyword.len()) else {
7714            return false;
7715        };
7716        if !prefix.eq_ignore_ascii_case(keyword) {
7717            return false;
7718        }
7719        remaining[keyword.len()..]
7720            .chars()
7721            .next()
7722            .is_none_or(|char| !is_name_continue(char))
7723    }
7724
7725    fn supports_scss_interpolation(&self) -> bool {
7726        matches!(
7727            self.extension.dialect(),
7728            StyleDialect::Scss | StyleDialect::Sass
7729        )
7730    }
7731
7732    fn supports_less_interpolation(&self) -> bool {
7733        self.extension.dialect() == StyleDialect::Less
7734    }
7735
7736    fn starts_less_escaped_string(&self) -> bool {
7737        self.extension.dialect() == StyleDialect::Less
7738            && (self.starts_with("~\"") || self.starts_with("~'"))
7739    }
7740
7741    fn starts_unicode_range(&self) -> bool {
7742        let mut chars = self.text[self.offset..].chars();
7743        matches!(chars.next(), Some('u' | 'U'))
7744            && chars.next() == Some('+')
7745            && chars
7746                .next()
7747                .is_some_and(|char| char.is_ascii_hexdigit() || char == '?')
7748    }
7749
7750    fn current_char(&self) -> Option<char> {
7751        self.text[self.offset..].chars().next()
7752    }
7753
7754    fn next_char_is_hex_digit(&self) -> bool {
7755        let offset = self.offset + '-'.len_utf8();
7756        self.text
7757            .get(offset..)
7758            .and_then(|tail| tail.chars().next())
7759            .is_some_and(|char| char.is_ascii_hexdigit())
7760    }
7761
7762    fn bump_current(&mut self) {
7763        if let Some(char) = self.current_char() {
7764            self.bump_char(char);
7765        }
7766    }
7767
7768    fn bump_char(&mut self, char: char) {
7769        self.offset += char.len_utf8();
7770    }
7771}
7772
7773fn public_token_text(text: &str) -> String {
7774    text.chars()
7775        .map(css_syntax_preprocessed_char)
7776        .collect::<String>()
7777}
7778
7779fn css_syntax_preprocessed_char(char: char) -> char {
7780    if char == '\0' { '\u{fffd}' } else { char }
7781}
7782
7783fn is_name_start(char: char) -> bool {
7784    let char = css_syntax_preprocessed_char(char);
7785    char == '_' || char == '-' || char.is_alphabetic() || !char.is_ascii()
7786}
7787
7788fn is_name_continue(char: char) -> bool {
7789    is_name_start(char) || char.is_ascii_digit()
7790}
7791
7792fn is_non_printable_code_point(char: char) -> bool {
7793    let char = css_syntax_preprocessed_char(char);
7794    matches!(char, '\u{0000}'..='\u{0008}' | '\u{000b}' | '\u{000e}'..='\u{001f}' | '\u{007f}')
7795}
7796
7797fn is_custom_property_name_text(text: &str) -> bool {
7798    let Some(rest) = text.strip_prefix("--") else {
7799        return false;
7800    };
7801    let Some(first) = rest.chars().next() else {
7802        return false;
7803    };
7804    first == '-' || is_name_start(first) || starts_valid_escape_text(rest)
7805}
7806
7807fn starts_valid_escape_text(text: &str) -> bool {
7808    text.starts_with('\\')
7809        && text['\\'.len_utf8()..]
7810            .chars()
7811            .next()
7812            .is_some_and(|char| !matches!(char, '\n' | '\r' | '\u{000c}'))
7813}
7814
7815fn is_css_at_rule_name(text: &str) -> bool {
7816    matches_ignore_ascii_case(
7817        text,
7818        &[
7819            "@charset",
7820            "@container",
7821            "@font-face",
7822            "@font-feature-values",
7823            "@font-palette-values",
7824            "@import",
7825            "@keyframes",
7826            "@layer",
7827            "@media",
7828            "@namespace",
7829            "@page",
7830            "@property",
7831            "@scope",
7832            "@starting-style",
7833            "@supports",
7834            "@counter-style",
7835            "@custom-media",
7836            "@color-profile",
7837            "@nest",
7838            "@position-try",
7839            "@view-transition",
7840            "@stylistic",
7841            "@styleset",
7842            "@character-variant",
7843            "@swash",
7844            "@ornaments",
7845            "@annotation",
7846            "@historical-forms",
7847            "@when",
7848            "@else",
7849        ],
7850    )
7851}
7852
7853fn is_interpolation_start(kind: SyntaxKind) -> bool {
7854    matches!(
7855        kind,
7856        SyntaxKind::ScssInterpolationStart | SyntaxKind::LessInterpolationStart
7857    )
7858}
7859
7860fn is_component_value_atom_start(kind: SyntaxKind) -> bool {
7861    matches!(
7862        kind,
7863        SyntaxKind::Ident
7864            | SyntaxKind::CustomPropertyName
7865            | SyntaxKind::Number
7866            | SyntaxKind::Percentage
7867            | SyntaxKind::Dimension
7868            | SyntaxKind::String
7869            | SyntaxKind::LessEscapedString
7870            | SyntaxKind::UnicodeRange
7871            | SyntaxKind::Hash
7872            | SyntaxKind::Url
7873            | SyntaxKind::BadUrl
7874            | SyntaxKind::BadString
7875            | SyntaxKind::Important
7876            | SyntaxKind::ScssVariable
7877            | SyntaxKind::LessVariable
7878            | SyntaxKind::LessPropertyVariableToken
7879            | SyntaxKind::ScssInterpolationStart
7880            | SyntaxKind::LessInterpolationStart
7881    )
7882}
7883
7884fn interpolation_end_kind(start_kind: SyntaxKind) -> Option<SyntaxKind> {
7885    match start_kind {
7886        SyntaxKind::ScssInterpolationStart => Some(SyntaxKind::ScssInterpolationEnd),
7887        SyntaxKind::LessInterpolationStart => Some(SyntaxKind::LessInterpolationEnd),
7888        _ => None,
7889    }
7890}
7891
7892#[derive(Debug, Clone, PartialEq, Eq)]
7893struct SelectorBranch {
7894    name: String,
7895    range: TextRange,
7896    bare_suffix_base: bool,
7897}
7898
7899fn collect_selector_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedSelectorFact> {
7900    let mut selectors = Vec::new();
7901    let mut seen = BTreeSet::new();
7902    collect_selector_facts_in_range(
7903        tokens,
7904        0,
7905        tokens.len(),
7906        &[],
7907        None,
7908        &mut seen,
7909        &mut selectors,
7910    );
7911    selectors
7912}
7913
7914fn collect_selector_facts_in_range(
7915    tokens: &[Token<'_>],
7916    start: usize,
7917    end: usize,
7918    parent_branches: &[SelectorBranch],
7919    css_module_scope: Option<&'static str>,
7920    seen: &mut BTreeSet<(ParsedSelectorFactKind, String, u32, u32)>,
7921    selectors: &mut Vec<ParsedSelectorFact>,
7922) {
7923    let mut index = start;
7924    while index < end {
7925        index = skip_trivia_tokens(tokens, index, end);
7926        if index >= end {
7927            break;
7928        }
7929
7930        if tokens[index].kind == SyntaxKind::AtKeyword {
7931            let block = find_block_after_header(tokens, index, end);
7932            if let Some((open, close)) = block {
7933                if tokens[index].text == "@nest" {
7934                    if css_module_scope == Some("global") {
7935                        collect_selector_facts_in_range(
7936                            tokens,
7937                            open + 1,
7938                            close,
7939                            &[],
7940                            css_module_scope,
7941                            seen,
7942                            selectors,
7943                        );
7944                    } else {
7945                        let branches =
7946                            resolve_selector_header(tokens, index + 1, open, parent_branches);
7947                        push_class_selector_facts_from_header(
7948                            selectors,
7949                            seen,
7950                            tokens,
7951                            index + 1,
7952                            open,
7953                        );
7954                        for branch in &branches {
7955                            push_selector_fact(
7956                                selectors,
7957                                seen,
7958                                ParsedSelectorFactKind::Class,
7959                                branch.name.clone(),
7960                                branch.range,
7961                            );
7962                        }
7963                        collect_selector_facts_in_range(
7964                            tokens,
7965                            open + 1,
7966                            close,
7967                            &branches,
7968                            css_module_scope,
7969                            seen,
7970                            selectors,
7971                        );
7972                    }
7973                } else if style_wrapper_at_rule(tokens[index].text) {
7974                    collect_selector_facts_in_range(
7975                        tokens,
7976                        open + 1,
7977                        close,
7978                        parent_branches,
7979                        css_module_scope,
7980                        seen,
7981                        selectors,
7982                    );
7983                }
7984                index = close + 1;
7985            } else {
7986                index = skip_statement(tokens, index, end);
7987            }
7988            continue;
7989        }
7990
7991        let Some((open, close)) = find_block_after_header(tokens, index, end) else {
7992            index = skip_statement(tokens, index, end);
7993            continue;
7994        };
7995
7996        let effective_scope = css_module_scope
7997            .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
7998        if effective_scope == Some("global") {
7999            collect_selector_facts_in_range(
8000                tokens,
8001                open + 1,
8002                close,
8003                &[],
8004                effective_scope,
8005                seen,
8006                selectors,
8007            );
8008        } else {
8009            let branches = resolve_selector_header(tokens, index, open, parent_branches);
8010            push_class_selector_facts_from_header(selectors, seen, tokens, index, open);
8011            for branch in &branches {
8012                push_selector_fact(
8013                    selectors,
8014                    seen,
8015                    ParsedSelectorFactKind::Class,
8016                    branch.name.clone(),
8017                    branch.range,
8018                );
8019            }
8020            for id in collect_id_selector_facts_from_header(tokens, index, open)
8021                .into_iter()
8022                .chain(collect_local_function_id_selector_facts_from_header(
8023                    tokens, index, open,
8024                ))
8025            {
8026                push_selector_fact(selectors, seen, ParsedSelectorFactKind::Id, id.0, id.1);
8027            }
8028            for placeholder in collect_placeholder_selector_facts_from_header(tokens, index, open) {
8029                push_selector_fact(
8030                    selectors,
8031                    seen,
8032                    ParsedSelectorFactKind::Placeholder,
8033                    placeholder.0,
8034                    placeholder.1,
8035                );
8036            }
8037
8038            collect_selector_facts_in_range(
8039                tokens,
8040                open + 1,
8041                close,
8042                &branches,
8043                effective_scope,
8044                seen,
8045                selectors,
8046            );
8047        }
8048        index = close + 1;
8049    }
8050}
8051
8052fn push_class_selector_facts_from_header(
8053    selectors: &mut Vec<ParsedSelectorFact>,
8054    seen: &mut BTreeSet<(ParsedSelectorFactKind, String, u32, u32)>,
8055    tokens: &[Token<'_>],
8056    start: usize,
8057    end: usize,
8058) {
8059    for (name, range) in collect_class_selector_names_from_header(tokens, start, end) {
8060        push_selector_fact(selectors, seen, ParsedSelectorFactKind::Class, name, range);
8061    }
8062}
8063
8064fn push_selector_fact(
8065    selectors: &mut Vec<ParsedSelectorFact>,
8066    seen: &mut BTreeSet<(ParsedSelectorFactKind, String, u32, u32)>,
8067    kind: ParsedSelectorFactKind,
8068    name: String,
8069    range: TextRange,
8070) {
8071    if seen.insert((
8072        kind,
8073        name.clone(),
8074        u32::from(range.start()),
8075        u32::from(range.end()),
8076    )) {
8077        selectors.push(ParsedSelectorFact { kind, name, range });
8078    }
8079}
8080
8081fn resolve_selector_header(
8082    tokens: &[Token<'_>],
8083    start: usize,
8084    end: usize,
8085    parent_branches: &[SelectorBranch],
8086) -> Vec<SelectorBranch> {
8087    split_selector_groups(tokens, start, end)
8088        .into_iter()
8089        .flat_map(|(group_start, group_end)| {
8090            resolve_selector_group(tokens, group_start, group_end, parent_branches)
8091        })
8092        .collect()
8093}
8094
8095fn resolve_selector_group(
8096    tokens: &[Token<'_>],
8097    start: usize,
8098    end: usize,
8099    parent_branches: &[SelectorBranch],
8100) -> Vec<SelectorBranch> {
8101    if let Some(mut local_names) = collect_local_function_selector_names(tokens, start, end) {
8102        local_names.extend(collect_class_selector_names_from_header(tokens, start, end));
8103        let bare_suffix_base = parent_branches.is_empty() && local_names.len() == 1;
8104        return local_names
8105            .into_iter()
8106            .map(|(name, range)| SelectorBranch {
8107                name,
8108                range,
8109                bare_suffix_base,
8110            })
8111            .collect();
8112    }
8113
8114    let (tail_start, tail_end) = selector_group_tail_range(tokens, start, end);
8115    let tail_start = skip_trivia_tokens(tokens, tail_start, tail_end);
8116
8117    if let Some((suffix, range)) = ampersand_suffix_selector(tokens, tail_start, tail_end) {
8118        let bases: Vec<&SelectorBranch> = if parent_branches.is_empty() {
8119            Vec::new()
8120        } else {
8121            parent_branches
8122                .iter()
8123                .filter(|parent| parent.bare_suffix_base)
8124                .collect()
8125        };
8126        return bases
8127            .into_iter()
8128            .map(|parent| SelectorBranch {
8129                name: format!("{}{}", parent.name, suffix),
8130                range,
8131                bare_suffix_base: parent.bare_suffix_base,
8132            })
8133            .collect();
8134    }
8135
8136    let class_names = collect_class_selector_names_from_header(tokens, tail_start, tail_end);
8137    if class_names.is_empty() {
8138        return Vec::new();
8139    }
8140
8141    let bare_suffix_base = parent_branches.is_empty()
8142        && class_names.len() == 1
8143        && is_bare_class_selector_group(tokens, tail_start, tail_end);
8144    class_names
8145        .into_iter()
8146        .map(|(name, range)| SelectorBranch {
8147            name,
8148            range,
8149            bare_suffix_base,
8150        })
8151        .collect()
8152}
8153
8154fn is_bare_class_selector_group(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
8155    let dot_index = skip_trivia_tokens(tokens, start, end);
8156    if tokens.get(dot_index).map(|token| token.kind) != Some(SyntaxKind::Dot) {
8157        return false;
8158    }
8159    let name_index = skip_trivia_tokens(tokens, dot_index + 1, end);
8160    if !tokens.get(name_index).is_some_and(|token| {
8161        matches!(
8162            token.kind,
8163            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
8164        )
8165    }) {
8166        return false;
8167    }
8168    skip_trivia_tokens(tokens, name_index + 1, end) >= end
8169}
8170
8171fn split_selector_groups(tokens: &[Token<'_>], start: usize, end: usize) -> Vec<(usize, usize)> {
8172    let mut groups = Vec::new();
8173    let mut group_start = start;
8174    let mut paren_depth = 0usize;
8175    let mut bracket_depth = 0usize;
8176    let mut index = start;
8177    while index < end {
8178        match tokens[index].kind {
8179            SyntaxKind::LeftParen => paren_depth += 1,
8180            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8181            SyntaxKind::LeftBracket => bracket_depth += 1,
8182            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8183            SyntaxKind::Comma if paren_depth == 0 && bracket_depth == 0 => {
8184                groups.push((group_start, index));
8185                group_start = index + 1;
8186            }
8187            _ => {}
8188        }
8189        index += 1;
8190    }
8191    groups.push((group_start, end));
8192    groups
8193}
8194
8195fn selector_group_tail_range(tokens: &[Token<'_>], start: usize, end: usize) -> (usize, usize) {
8196    let mut paren_depth = 0usize;
8197    let mut bracket_depth = 0usize;
8198    let mut tail_start = start;
8199    let mut index = start;
8200    while index < end {
8201        match tokens[index].kind {
8202            SyntaxKind::LeftParen => paren_depth += 1,
8203            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8204            SyntaxKind::LeftBracket => bracket_depth += 1,
8205            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8206            kind if paren_depth == 0 && bracket_depth == 0 && is_selector_combinator_kind(kind) => {
8207                tail_start = index + 1;
8208            }
8209            SyntaxKind::Whitespace if paren_depth == 0 && bracket_depth == 0 => {
8210                let previous = previous_non_trivia_token(tokens, start, index);
8211                let next = next_non_trivia_token_until(tokens, index + 1, end);
8212                if previous.is_some_and(|token| selector_component_can_end(token.kind))
8213                    && next.is_some_and(|token| selector_component_can_start(token.kind))
8214                {
8215                    tail_start = index + 1;
8216                }
8217            }
8218            _ => {}
8219        }
8220        index += 1;
8221    }
8222    (tail_start, end)
8223}
8224
8225fn ampersand_suffix_selector(
8226    tokens: &[Token<'_>],
8227    start: usize,
8228    end: usize,
8229) -> Option<(String, TextRange)> {
8230    let ampersand_index = skip_trivia_tokens(tokens, start, end);
8231    if tokens.get(ampersand_index)?.kind != SyntaxKind::Ampersand {
8232        return None;
8233    }
8234    let suffix = next_non_trivia_token_until(tokens, ampersand_index + 1, end)?;
8235    if matches!(
8236        suffix.kind,
8237        SyntaxKind::Ident | SyntaxKind::CustomPropertyName
8238    ) {
8239        return Some((suffix.text.to_string(), suffix.range));
8240    }
8241    None
8242}
8243
8244fn collect_class_selector_names_from_header(
8245    tokens: &[Token<'_>],
8246    start: usize,
8247    end: usize,
8248) -> Vec<(String, TextRange)> {
8249    let mut names = Vec::new();
8250    let mut index = start;
8251    let mut paren_depth = 0usize;
8252    let mut bracket_depth = 0usize;
8253    while index < end {
8254        match tokens[index].kind {
8255            SyntaxKind::LeftParen => paren_depth += 1,
8256            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8257            SyntaxKind::LeftBracket => bracket_depth += 1,
8258            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8259            _ => {}
8260        }
8261        if paren_depth == 0
8262            && bracket_depth == 0
8263            && tokens[index].kind == SyntaxKind::Dot
8264            && let Some(name) = next_non_trivia_token_until(tokens, index + 1, end)
8265            && matches!(
8266                name.kind,
8267                SyntaxKind::Ident | SyntaxKind::CustomPropertyName
8268            )
8269        {
8270            names.push((name.text.to_string(), name.range));
8271        }
8272        index += 1;
8273    }
8274    names
8275}
8276
8277fn collect_local_function_selector_names(
8278    tokens: &[Token<'_>],
8279    start: usize,
8280    end: usize,
8281) -> Option<Vec<(String, TextRange)>> {
8282    let colon_index = skip_trivia_tokens(tokens, start, end);
8283    if tokens.get(colon_index)?.kind != SyntaxKind::Colon {
8284        return None;
8285    }
8286    let ident = next_non_trivia_token_until(tokens, colon_index + 1, end)?;
8287    if ident.kind != SyntaxKind::Ident || ident.text != "local" {
8288        return None;
8289    }
8290    let open_index = skip_trivia_tokens(tokens, colon_index + 2, end);
8291    if tokens.get(open_index)?.kind != SyntaxKind::LeftParen {
8292        return None;
8293    }
8294    Some(collect_class_selector_names_from_header(
8295        tokens,
8296        open_index + 1,
8297        end.saturating_sub(1),
8298    ))
8299}
8300
8301fn collect_local_function_id_selector_facts_from_header(
8302    tokens: &[Token<'_>],
8303    start: usize,
8304    end: usize,
8305) -> Vec<(String, TextRange)> {
8306    let mut ids = Vec::new();
8307    let mut index = start;
8308    while index < end {
8309        if tokens[index].kind == SyntaxKind::Colon
8310            && let Some(scope) = next_non_trivia_token_until(tokens, index + 1, end)
8311            && scope.kind == SyntaxKind::Ident
8312            && scope.text == "local"
8313            && let Some(open) = next_non_trivia_token_after_range(tokens, scope.range, end)
8314            && open.kind == SyntaxKind::LeftParen
8315            && let Some(close) = matching_right_paren_from_range(tokens, open.range, end)
8316        {
8317            ids.extend(collect_id_selector_facts_from_header(
8318                tokens,
8319                token_index_by_range(tokens, open.range).map_or(index + 1, |value| value + 1),
8320                close,
8321            ));
8322            index = close.saturating_add(1);
8323            continue;
8324        }
8325        index += 1;
8326    }
8327    ids
8328}
8329
8330fn css_module_block_scope_marker_in_header(
8331    tokens: &[Token<'_>],
8332    start: usize,
8333    end: usize,
8334) -> Option<&'static str> {
8335    if next_non_trivia_token_until(tokens, start, end)
8336        .is_some_and(|token| token.kind == SyntaxKind::AtKeyword)
8337    {
8338        return None;
8339    }
8340
8341    css_module_scope_marker_after_colon(tokens, start, end)
8342        .filter(|_| !css_module_scope_marker_is_function(tokens, start, end))
8343}
8344
8345fn css_module_header_is_global_only(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
8346    if next_non_trivia_token_until(tokens, start, end)
8347        .is_some_and(|token| token.kind == SyntaxKind::AtKeyword)
8348    {
8349        return false;
8350    }
8351    css_module_header_contains_scope(tokens, start, end, "global")
8352        && collect_class_selector_names_from_header(tokens, start, end).is_empty()
8353        && collect_local_function_selector_names(tokens, start, end)
8354            .map(|names| names.is_empty())
8355            .unwrap_or(true)
8356}
8357
8358fn css_module_header_contains_scope(
8359    tokens: &[Token<'_>],
8360    start: usize,
8361    end: usize,
8362    expected_scope: &str,
8363) -> bool {
8364    let mut index = start;
8365    while index < end {
8366        if tokens[index].kind == SyntaxKind::Colon
8367            && let Some(scope) = next_non_trivia_token_until(tokens, index + 1, end)
8368            && scope.kind == SyntaxKind::Ident
8369            && scope.text == expected_scope
8370        {
8371            return true;
8372        }
8373        index += 1;
8374    }
8375    false
8376}
8377
8378fn css_module_scope_marker_after_colon(
8379    tokens: &[Token<'_>],
8380    start: usize,
8381    end: usize,
8382) -> Option<&'static str> {
8383    let colon = skip_trivia_tokens(tokens, start, end);
8384    if tokens.get(colon)?.kind != SyntaxKind::Colon {
8385        return None;
8386    }
8387    let scope = next_non_trivia_token_until(tokens, colon + 1, end)?;
8388    if scope.kind != SyntaxKind::Ident {
8389        return None;
8390    }
8391    match scope.text {
8392        "global" => Some("global"),
8393        "local" => Some("local"),
8394        _ => None,
8395    }
8396}
8397
8398fn css_module_scope_marker_is_function(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
8399    let colon = skip_trivia_tokens(tokens, start, end);
8400    let mut index = colon + 1;
8401    let Some(scope) = next_non_trivia_token_until(tokens, index, end) else {
8402        return false;
8403    };
8404    while index < end {
8405        if tokens[index].range == scope.range {
8406            break;
8407        }
8408        index += 1;
8409    }
8410    let Some(next) = next_non_trivia_token_until(tokens, index + 1, end) else {
8411        return false;
8412    };
8413    scope.kind == SyntaxKind::Ident && next.kind == SyntaxKind::LeftParen
8414}
8415
8416fn collect_id_selector_facts_from_header(
8417    tokens: &[Token<'_>],
8418    start: usize,
8419    end: usize,
8420) -> Vec<(String, TextRange)> {
8421    let mut names = Vec::new();
8422    let mut index = start;
8423    let mut paren_depth = 0usize;
8424    let mut bracket_depth = 0usize;
8425    while index < end {
8426        match tokens[index].kind {
8427            SyntaxKind::LeftParen => paren_depth += 1,
8428            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8429            SyntaxKind::LeftBracket => bracket_depth += 1,
8430            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8431            _ => {}
8432        }
8433        let token = tokens[index];
8434        if paren_depth == 0 && bracket_depth == 0 && token.kind == SyntaxKind::Hash {
8435            names.push((token.text.trim_start_matches('#').to_string(), token.range));
8436        }
8437        index += 1;
8438    }
8439    names
8440}
8441
8442fn collect_placeholder_selector_facts_from_header(
8443    tokens: &[Token<'_>],
8444    start: usize,
8445    end: usize,
8446) -> Vec<(String, TextRange)> {
8447    let mut names = Vec::new();
8448    let mut index = start;
8449    let mut paren_depth = 0usize;
8450    let mut bracket_depth = 0usize;
8451    while index < end {
8452        match tokens[index].kind {
8453            SyntaxKind::LeftParen => paren_depth += 1,
8454            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8455            SyntaxKind::LeftBracket => bracket_depth += 1,
8456            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8457            _ => {}
8458        }
8459        let token = tokens[index];
8460        if paren_depth == 0 && bracket_depth == 0 && token.kind == SyntaxKind::ScssPlaceholder {
8461            names.push((token.text.trim_start_matches('%').to_string(), token.range));
8462        }
8463        index += 1;
8464    }
8465    names
8466}
8467
8468fn collect_variable_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedVariableFact> {
8469    let mut variables = Vec::new();
8470    for (index, token) in tokens.iter().enumerate() {
8471        let kind = match token.kind {
8472            SyntaxKind::ScssVariable => {
8473                if scss_variable_token_is_declaration(tokens, index) {
8474                    ParsedVariableFactKind::ScssDeclaration
8475                } else {
8476                    ParsedVariableFactKind::ScssReference
8477                }
8478            }
8479            SyntaxKind::LessVariable => {
8480                if next_non_trivia_token(tokens, index + 1)
8481                    .is_some_and(|candidate| candidate.kind == SyntaxKind::Colon)
8482                {
8483                    ParsedVariableFactKind::LessDeclaration
8484                } else {
8485                    ParsedVariableFactKind::LessReference
8486                }
8487            }
8488            SyntaxKind::CustomPropertyName => {
8489                if previous_non_trivia_token(tokens, 0, index).is_some_and(|candidate| {
8490                    matches!(candidate.kind, SyntaxKind::Ampersand | SyntaxKind::Dot)
8491                }) {
8492                    continue;
8493                }
8494                if let Some(at_rule_name) = containing_at_rule_header_name(tokens, index) {
8495                    if at_rule_name == "@property" {
8496                        ParsedVariableFactKind::CustomPropertyDeclaration
8497                    } else {
8498                        continue;
8499                    }
8500                } else if next_non_trivia_token(tokens, index + 1)
8501                    .is_some_and(|candidate| candidate.kind == SyntaxKind::Colon)
8502                {
8503                    ParsedVariableFactKind::CustomPropertyDeclaration
8504                } else {
8505                    ParsedVariableFactKind::CustomPropertyReference
8506                }
8507            }
8508            _ => continue,
8509        };
8510        let has_fallback = kind == ParsedVariableFactKind::CustomPropertyReference
8511            && custom_property_reference_has_var_fallback(tokens, index);
8512        variables.push(ParsedVariableFact {
8513            kind,
8514            name: token.text.to_string(),
8515            range: token.range,
8516            has_fallback,
8517        });
8518    }
8519    variables
8520}
8521
8522/// Detect a `var(--x, fallback)` fallback for the `CustomPropertyName` at `index`.
8523///
8524/// True iff the reference is the first argument of an enclosing `var(` call *and* a
8525/// top-level comma follows it before that call's closing paren. Scoped per-`var()`: in
8526/// `var(--a, var(--b))` only `--a` carries a fallback; the nested `--b` (no fallback of
8527/// its own) is unaffected and stays a live `missingCustomProperty` candidate.
8528fn custom_property_reference_has_var_fallback(tokens: &[Token<'_>], index: usize) -> bool {
8529    // The reference must be the leading argument of a `var(` call: its immediate
8530    // non-trivia predecessor is `(`, preceded by an identifier `var`.
8531    let Some(open_index) = previous_non_trivia_token_index(tokens, index, 0) else {
8532        return false;
8533    };
8534    if tokens[open_index].kind != SyntaxKind::LeftParen {
8535        return false;
8536    }
8537    let Some(callee_index) = previous_non_trivia_token_index(tokens, open_index, 0) else {
8538        return false;
8539    };
8540    if tokens[callee_index].kind != SyntaxKind::Ident
8541        || !tokens[callee_index].text.eq_ignore_ascii_case("var")
8542    {
8543        return false;
8544    }
8545    // Scan forward at this call's paren depth for a top-level comma before its close.
8546    let mut depth = 0usize;
8547    let mut cursor = open_index;
8548    while cursor < tokens.len() {
8549        match tokens[cursor].kind {
8550            SyntaxKind::LeftParen => depth += 1,
8551            SyntaxKind::RightParen => {
8552                depth = depth.saturating_sub(1);
8553                if depth == 0 {
8554                    return false;
8555                }
8556            }
8557            SyntaxKind::Comma if depth == 1 => return true,
8558            _ => {}
8559        }
8560        cursor += 1;
8561    }
8562    false
8563}
8564
8565fn scss_variable_token_is_declaration(tokens: &[Token<'_>], index: usize) -> bool {
8566    if scss_loop_variable_token_is_binding(tokens, index) {
8567        return true;
8568    }
8569    next_non_trivia_token(tokens, index + 1).is_some_and(|candidate| {
8570        candidate.kind == SyntaxKind::Colon
8571            || (matches!(candidate.kind, SyntaxKind::Comma | SyntaxKind::RightParen)
8572                && containing_at_rule_header_name(tokens, index).is_some_and(|name| {
8573                    name.eq_ignore_ascii_case("@mixin") || name.eq_ignore_ascii_case("@function")
8574                }))
8575    })
8576}
8577
8578/// Positional guard for `@each` / `@for` loop bindings.
8579///
8580/// In `@each $k, $v in $map` the `$k`/`$v` are *bindings* (declarations), while
8581/// the iterable `$map` after `in` is a *reference*. In `@for $i from $start
8582/// through $end` the `$i` is a binding, while `$start`/`$end` after `from` are
8583/// references. A `$var` is a binding iff it sits in the loop header *before* the
8584/// top-level separator keyword (`in` for `@each`, `from` for `@for`). `@while` /
8585/// `@if` headers introduce no bindings and stay reference-only.
8586fn scss_loop_variable_token_is_binding(tokens: &[Token<'_>], index: usize) -> bool {
8587    let Some(header_index) = containing_at_rule_header_index(tokens, index) else {
8588        return false;
8589    };
8590    let separator = match () {
8591        _ if tokens[header_index].text.eq_ignore_ascii_case("@each") => "in",
8592        _ if tokens[header_index].text.eq_ignore_ascii_case("@for") => "from",
8593        _ => return false,
8594    };
8595    // Scan the header from just after the at-keyword up to (but excluding) the
8596    // variable token. If the top-level separator keyword has already appeared,
8597    // the variable is part of the iterable/bounds expression -> reference.
8598    let mut paren_depth = 0usize;
8599    for token in &tokens[header_index + 1..index] {
8600        match token.kind {
8601            SyntaxKind::LeftParen => paren_depth += 1,
8602            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8603            SyntaxKind::Ident if paren_depth == 0 && token.text.eq_ignore_ascii_case(separator) => {
8604                return false;
8605            }
8606            _ => {}
8607        }
8608    }
8609    true
8610}
8611
8612/// Like [`containing_at_rule_header_name`] but returns the index of the
8613/// enclosing `@`-keyword token rather than its text.
8614fn containing_at_rule_header_index(tokens: &[Token<'_>], index: usize) -> Option<usize> {
8615    let mut current = index;
8616    while current > 0 {
8617        current -= 1;
8618        let token = tokens.get(current)?;
8619        if token.kind.is_trivia() {
8620            continue;
8621        }
8622        if matches!(
8623            token.kind,
8624            SyntaxKind::Semicolon
8625                | SyntaxKind::SassOptionalSemicolon
8626                | SyntaxKind::LeftBrace
8627                | SyntaxKind::RightBrace
8628                | SyntaxKind::SassIndent
8629                | SyntaxKind::SassDedent
8630        ) {
8631            return None;
8632        }
8633        if token.kind == SyntaxKind::AtKeyword {
8634            return Some(current);
8635        }
8636    }
8637    None
8638}
8639
8640fn collect_sass_symbol_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedSassSymbolFact> {
8641    let declared_functions = collect_sass_callable_declaration_names(tokens, "@function");
8642    let mut symbols = Vec::new();
8643
8644    for (index, token) in tokens.iter().enumerate() {
8645        match token.kind {
8646            SyntaxKind::ScssVariable => {
8647                let kind = if scss_variable_token_is_declaration(tokens, index) {
8648                    ParsedSassSymbolFactKind::VariableDeclaration
8649                } else {
8650                    ParsedSassSymbolFactKind::VariableReference
8651                };
8652                let namespace = (!scss_variable_token_is_declaration(tokens, index))
8653                    .then(|| sass_member_namespace_before(tokens, index))
8654                    .flatten();
8655                symbols.push(ParsedSassSymbolFact {
8656                    kind,
8657                    symbol_kind: "variable",
8658                    name: token.text.trim_start_matches('$').to_string(),
8659                    role: match kind {
8660                        ParsedSassSymbolFactKind::VariableDeclaration => "declaration",
8661                        _ => "reference",
8662                    },
8663                    namespace,
8664                    range: sass_symbol_variable_range(token, kind),
8665                });
8666            }
8667            SyntaxKind::AtKeyword if token.text.eq_ignore_ascii_case("@mixin") => {
8668                if let Some(name) = sass_callable_name_after_at_rule(tokens, index) {
8669                    symbols.push(ParsedSassSymbolFact {
8670                        kind: ParsedSassSymbolFactKind::MixinDeclaration,
8671                        symbol_kind: "mixin",
8672                        name: name.text.to_string(),
8673                        role: "declaration",
8674                        namespace: None,
8675                        range: name.range,
8676                    });
8677                }
8678            }
8679            SyntaxKind::AtKeyword if token.text.eq_ignore_ascii_case("@include") => {
8680                if let Some((name, namespace)) = sass_include_name_after_at_rule(tokens, index) {
8681                    symbols.push(ParsedSassSymbolFact {
8682                        kind: ParsedSassSymbolFactKind::MixinInclude,
8683                        symbol_kind: "mixin",
8684                        name: name.text.to_string(),
8685                        role: "include",
8686                        namespace,
8687                        range: name.range,
8688                    });
8689                }
8690            }
8691            SyntaxKind::AtKeyword if token.text.eq_ignore_ascii_case("@function") => {
8692                if let Some(name) = sass_callable_name_after_at_rule(tokens, index) {
8693                    symbols.push(ParsedSassSymbolFact {
8694                        kind: ParsedSassSymbolFactKind::FunctionDeclaration,
8695                        symbol_kind: "function",
8696                        name: name.text.to_string(),
8697                        role: "declaration",
8698                        namespace: None,
8699                        range: name.range,
8700                    });
8701                }
8702            }
8703            SyntaxKind::Ident
8704                if (declared_functions.contains(token.text)
8705                    || sass_member_namespace_before(tokens, index).is_some())
8706                    && next_non_trivia_token(tokens, index + 1)
8707                        .is_some_and(|candidate| candidate.kind == SyntaxKind::LeftParen)
8708                    && !containing_at_rule_header_name(tokens, index)
8709                        .is_some_and(|name| name.eq_ignore_ascii_case("@include"))
8710                    && previous_non_trivia_token(tokens, 0, index).is_none_or(|candidate| {
8711                        !matches!(candidate.kind, SyntaxKind::AtKeyword)
8712                    }) =>
8713            {
8714                symbols.push(ParsedSassSymbolFact {
8715                    kind: ParsedSassSymbolFactKind::FunctionCall,
8716                    symbol_kind: "function",
8717                    name: token.text.to_string(),
8718                    role: "call",
8719                    namespace: sass_member_namespace_before(tokens, index),
8720                    range: token.range,
8721                });
8722            }
8723            _ => {}
8724        }
8725    }
8726
8727    symbols
8728}
8729
8730fn sass_symbol_variable_range(token: &Token<'_>, kind: ParsedSassSymbolFactKind) -> TextRange {
8731    if kind == ParsedSassSymbolFactKind::VariableDeclaration && token.text.starts_with('$') {
8732        let start = u32::from(token.range.start());
8733        let end = u32::from(token.range.end());
8734        if start < end {
8735            return TextRange::new(TextSize::from(start + 1), TextSize::from(end));
8736        }
8737    }
8738    token.range
8739}
8740
8741fn collect_sass_callable_declaration_names(
8742    tokens: &[Token<'_>],
8743    at_keyword: &str,
8744) -> BTreeSet<String> {
8745    tokens
8746        .iter()
8747        .enumerate()
8748        .filter_map(|(index, token)| {
8749            (token.kind == SyntaxKind::AtKeyword && token.text.eq_ignore_ascii_case(at_keyword))
8750                .then(|| sass_callable_name_after_at_rule(tokens, index))
8751                .flatten()
8752                .map(|name| name.text.to_string())
8753        })
8754        .collect()
8755}
8756
8757fn sass_callable_name_after_at_rule<'text>(
8758    tokens: &[Token<'text>],
8759    at_rule_index: usize,
8760) -> Option<Token<'text>> {
8761    let statement_end = css_module_value_statement_end(tokens, at_rule_index + 1);
8762    let name_index = next_non_trivia_token_index_until(tokens, at_rule_index + 1, statement_end)?;
8763    let name = tokens[name_index];
8764    if name.kind != SyntaxKind::Ident {
8765        return None;
8766    }
8767    if next_non_trivia_token_index_until(tokens, name_index + 1, statement_end)
8768        .is_some_and(|next| tokens[next].kind == SyntaxKind::Dot)
8769    {
8770        return None;
8771    }
8772    Some(name)
8773}
8774
8775fn sass_include_name_after_at_rule<'text>(
8776    tokens: &[Token<'text>],
8777    at_rule_index: usize,
8778) -> Option<(Token<'text>, Option<String>)> {
8779    let statement_end = css_module_value_statement_end(tokens, at_rule_index + 1);
8780    let first_index = next_non_trivia_token_index_until(tokens, at_rule_index + 1, statement_end)?;
8781    let first = tokens[first_index];
8782    if first.kind != SyntaxKind::Ident {
8783        return None;
8784    }
8785    let Some(dot_index) = next_non_trivia_token_index_until(tokens, first_index + 1, statement_end)
8786    else {
8787        return Some((first, None));
8788    };
8789    if tokens[dot_index].kind != SyntaxKind::Dot {
8790        return Some((first, None));
8791    }
8792    let member_index = next_non_trivia_token_index_until(tokens, dot_index + 1, statement_end)?;
8793    let member = tokens[member_index];
8794    (member.kind == SyntaxKind::Ident).then(|| (member, Some(first.text.to_string())))
8795}
8796
8797fn sass_member_namespace_before(tokens: &[Token<'_>], member_index: usize) -> Option<String> {
8798    let dot_index = previous_non_trivia_token_index(tokens, member_index, 0)?;
8799    if tokens[dot_index].kind != SyntaxKind::Dot {
8800        return None;
8801    }
8802    let namespace = tokens[previous_non_trivia_token_index(tokens, dot_index, 0)?];
8803    (namespace.kind == SyntaxKind::Ident).then(|| namespace.text.to_string())
8804}
8805
8806fn collect_sass_include_facts_from_tokens(
8807    source: &str,
8808    tokens: &[Token<'_>],
8809) -> Vec<ParsedSassIncludeFact> {
8810    let mut includes = Vec::new();
8811    for (index, token) in tokens.iter().enumerate() {
8812        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@include") {
8813            continue;
8814        }
8815        let statement_end = css_module_value_statement_end(tokens, index + 1);
8816        let Some((name, namespace)) = sass_include_name_after_at_rule(tokens, index) else {
8817            continue;
8818        };
8819        let header_end = previous_non_trivia_token_index(tokens, statement_end, index + 1)
8820            .map(|previous| tokens[previous].range.end())
8821            .unwrap_or(name.range.end());
8822        let params = source
8823            .get(u32::from(name.range.end()) as usize..u32::from(header_end) as usize)
8824            .unwrap_or_default()
8825            .trim()
8826            .to_string();
8827        includes.push(ParsedSassIncludeFact {
8828            name: name.text.to_string(),
8829            namespace,
8830            params,
8831            range: TextRange::new(token.range.start(), header_end),
8832        });
8833    }
8834    includes
8835}
8836
8837fn collect_sass_module_edge_facts_from_tokens(
8838    tokens: &[Token<'_>],
8839) -> Vec<ParsedSassModuleEdgeFact> {
8840    let mut edges = Vec::new();
8841    let mut seen = BTreeSet::new();
8842
8843    for (index, token) in tokens.iter().enumerate() {
8844        if token.kind != SyntaxKind::AtKeyword {
8845            continue;
8846        }
8847        let Some(kind) = sass_module_edge_kind(token.text) else {
8848            continue;
8849        };
8850        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
8851        let end = css_module_value_statement_end(tokens, start);
8852        if kind == ParsedSassModuleEdgeFactKind::Import {
8853            collect_sass_import_module_edges(tokens, start, end, &mut edges, &mut seen);
8854            continue;
8855        }
8856        let Some(source_index) = next_non_trivia_token_index_until(tokens, start, end) else {
8857            continue;
8858        };
8859        let source = tokens[source_index];
8860        if !matches!(source.kind, SyntaxKind::String | SyntaxKind::Url) {
8861            continue;
8862        }
8863        let source_name = css_module_value_source_name(source);
8864        let (namespace_kind, namespace) = if kind == ParsedSassModuleEdgeFactKind::Use {
8865            sass_module_use_namespace(tokens, source_name.as_str(), source_index + 1, end)
8866        } else {
8867            (None, None)
8868        };
8869        let (visibility_filter_kind, visibility_filter_names) =
8870            if kind == ParsedSassModuleEdgeFactKind::Forward {
8871                sass_module_forward_visibility_filter(tokens, source_index + 1, end)
8872            } else {
8873                (None, Vec::new())
8874            };
8875        let forward_prefix = if kind == ParsedSassModuleEdgeFactKind::Forward {
8876            sass_module_forward_prefix(tokens, source_index + 1, end)
8877        } else {
8878            None
8879        };
8880        push_sass_module_edge_fact(
8881            &mut edges,
8882            &mut seen,
8883            ParsedSassModuleEdgeFact {
8884                kind,
8885                source: source_name,
8886                namespace_kind,
8887                namespace,
8888                forward_prefix,
8889                visibility_filter_kind,
8890                visibility_filter_names,
8891                media_qualified: false,
8892                range: source.range,
8893            },
8894        );
8895    }
8896
8897    edges
8898}
8899
8900fn sass_module_edge_kind(text: &str) -> Option<ParsedSassModuleEdgeFactKind> {
8901    match text {
8902        text if text.eq_ignore_ascii_case("@use") => Some(ParsedSassModuleEdgeFactKind::Use),
8903        text if text.eq_ignore_ascii_case("@forward") => {
8904            Some(ParsedSassModuleEdgeFactKind::Forward)
8905        }
8906        text if text.eq_ignore_ascii_case("@import") => Some(ParsedSassModuleEdgeFactKind::Import),
8907        _ => None,
8908    }
8909}
8910
8911/// RFC-0007-E1 (#45): capture the target of each `@extend` rule. For each `@extend` keyword, the
8912/// statement runs to the next `;`/`}`/indent boundary (`css_module_value_statement_end`). Within
8913/// it we capture the FIRST simple target — a `%placeholder` (one `ScssPlaceholder` token) or a
8914/// `.class` (`Dot` + `Ident`) — and record whether the statement carries the `!optional` flag
8915/// (`!` `optional`, anywhere in the statement). A compound target (`.a.b`) records only its first
8916/// simple selector; dart-sass rejects compound `@extend` targets outright, so the first-simple
8917/// capture is sufficient for the missing-target check and never over-reports. Interpolated targets
8918/// (`#{...}`) produce no simple token here and are skipped (not statically checkable).
8919fn collect_extend_target_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedExtendTargetFact> {
8920    let mut targets = Vec::new();
8921
8922    for (index, token) in tokens.iter().enumerate() {
8923        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@extend") {
8924            continue;
8925        }
8926        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
8927        let end = css_module_value_statement_end(tokens, start);
8928
8929        // `!optional` may appear after the target; scan the whole statement for it first.
8930        let optional = extend_statement_has_optional_flag(tokens, start, end);
8931
8932        // First simple target within the statement.
8933        let mut cursor = start;
8934        let mut captured: Option<ParsedExtendTargetFact> = None;
8935        while cursor < end {
8936            let current = tokens[cursor];
8937            if current.kind == SyntaxKind::ScssPlaceholder {
8938                captured = Some(ParsedExtendTargetFact {
8939                    kind: ParsedExtendTargetFactKind::Placeholder,
8940                    name: current.text.trim_start_matches('%').to_string(),
8941                    optional,
8942                    range: current.range,
8943                });
8944                break;
8945            }
8946            if current.kind == SyntaxKind::Dot
8947                && let Some(name_index) = next_non_trivia_token_index_until(tokens, cursor + 1, end)
8948                && tokens[name_index].kind == SyntaxKind::Ident
8949            {
8950                let name_token = tokens[name_index];
8951                let range = TextRange::new(current.range.start(), name_token.range.end());
8952                captured = Some(ParsedExtendTargetFact {
8953                    kind: ParsedExtendTargetFactKind::Class,
8954                    name: name_token.text.to_string(),
8955                    optional,
8956                    range,
8957                });
8958                break;
8959            }
8960            cursor += 1;
8961        }
8962
8963        if let Some(target) = captured {
8964            targets.push(target);
8965        }
8966    }
8967
8968    targets
8969}
8970
8971/// Detect a trailing `!optional` flag in an `@extend` statement span. The flag tokenizes as a
8972/// `Delim "!"` followed by an `Ident "optional"` (case-insensitive), matching the `!important`
8973/// tokenization shape observed for value flags.
8974fn extend_statement_has_optional_flag(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
8975    let mut index = start;
8976    while index < end {
8977        if tokens[index].kind == SyntaxKind::Delim
8978            && tokens[index].text == "!"
8979            && let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
8980            && tokens[next_index].kind == SyntaxKind::Ident
8981            && tokens[next_index].text.eq_ignore_ascii_case("optional")
8982        {
8983            return true;
8984        }
8985        index += 1;
8986    }
8987    false
8988}
8989
8990fn collect_sass_import_module_edges(
8991    tokens: &[Token<'_>],
8992    start: usize,
8993    end: usize,
8994    edges: &mut Vec<ParsedSassModuleEdgeFact>,
8995    seen: &mut BTreeSet<(ParsedSassModuleEdgeFactKind, String, u32, u32)>,
8996) {
8997    for index in start..end {
8998        let token = tokens[index];
8999        if !matches!(token.kind, SyntaxKind::String | SyntaxKind::Url) {
9000            continue;
9001        }
9002        // RFC-0007-D1 (#44): a trailing media qualifier (`@import "foo" screen`,
9003        // `@import "foo" (min-width: 100px)`) keeps the import as plain CSS. Classify
9004        // per comma-peer target: the next significant token after this target marks a
9005        // media qualifier iff it is present and is NOT the `,` comma-peer separator.
9006        // (`@import "a", "b" screen` qualifies only `"b"`.)
9007        let media_qualified = next_non_trivia_token_index_until(tokens, index + 1, end)
9008            .is_some_and(|next| tokens[next].kind != SyntaxKind::Comma);
9009        push_sass_module_edge_fact(
9010            edges,
9011            seen,
9012            ParsedSassModuleEdgeFact {
9013                kind: ParsedSassModuleEdgeFactKind::Import,
9014                source: css_module_value_source_name(token),
9015                namespace_kind: None,
9016                namespace: None,
9017                forward_prefix: None,
9018                visibility_filter_kind: None,
9019                visibility_filter_names: Vec::new(),
9020                media_qualified,
9021                range: token.range,
9022            },
9023        );
9024    }
9025}
9026
9027fn sass_module_use_namespace(
9028    tokens: &[Token<'_>],
9029    source: &str,
9030    start: usize,
9031    end: usize,
9032) -> (Option<&'static str>, Option<String>) {
9033    let Some(as_index) = top_level_token_text_index(tokens, start, end, "as") else {
9034        return (
9035            Some("default"),
9036            sass_module_default_namespace(source).map(str::to_string),
9037        );
9038    };
9039    let Some(namespace_index) = next_non_trivia_token_index_until(tokens, as_index + 1, end) else {
9040        return (Some("invalid"), None);
9041    };
9042    let namespace = tokens[namespace_index];
9043    match namespace.kind {
9044        SyntaxKind::Star => (Some("wildcard"), None),
9045        SyntaxKind::Ident => (Some("alias"), Some(namespace.text.to_string())),
9046        _ => (Some("invalid"), None),
9047    }
9048}
9049
9050fn sass_module_forward_prefix(tokens: &[Token<'_>], start: usize, end: usize) -> Option<String> {
9051    let as_index = top_level_token_text_index(tokens, start, end, "as")?;
9052    let prefix_index = next_non_trivia_token_index_until(tokens, as_index + 1, end)?;
9053    let prefix = tokens[prefix_index].text.trim();
9054    if prefix.is_empty() {
9055        return None;
9056    }
9057    Some(prefix.to_string())
9058}
9059
9060fn sass_module_forward_visibility_filter(
9061    tokens: &[Token<'_>],
9062    start: usize,
9063    end: usize,
9064) -> (Option<&'static str>, Vec<String>) {
9065    let show_index = top_level_token_text_index(tokens, start, end, "show");
9066    let hide_index = top_level_token_text_index(tokens, start, end, "hide");
9067    let (filter_kind, filter_index) = match (show_index, hide_index) {
9068        (Some(show_index), Some(hide_index)) if show_index <= hide_index => ("show", show_index),
9069        (Some(_), Some(hide_index)) => ("hide", hide_index),
9070        (Some(show_index), None) => ("show", show_index),
9071        (None, Some(hide_index)) => ("hide", hide_index),
9072        (None, None) => return (None, Vec::new()),
9073    };
9074    let clause_end =
9075        top_level_token_text_index(tokens, filter_index + 1, end, "with").unwrap_or(end);
9076    (
9077        Some(filter_kind),
9078        sass_module_visibility_filter_names(tokens, filter_index + 1, clause_end),
9079    )
9080}
9081
9082fn sass_module_visibility_filter_names(
9083    tokens: &[Token<'_>],
9084    start: usize,
9085    end: usize,
9086) -> Vec<String> {
9087    let mut names = BTreeSet::new();
9088    for token in &tokens[start..end] {
9089        match token.kind {
9090            SyntaxKind::Ident | SyntaxKind::ScssVariable => {
9091                if matches_ignore_ascii_case(token.text, &["show", "hide", "with", "as"]) {
9092                    continue;
9093                }
9094                let name = token.text.trim_start_matches('$');
9095                if !name.is_empty() {
9096                    names.insert(name.to_string());
9097                }
9098            }
9099            _ => {}
9100        }
9101    }
9102    names.into_iter().collect()
9103}
9104
9105fn sass_module_default_namespace(source: &str) -> Option<&str> {
9106    let basename = source
9107        .rsplit(['/', '\\', ':'])
9108        .next()
9109        .unwrap_or(source)
9110        .trim_start_matches('_');
9111    let namespace = basename.split('.').next().unwrap_or(basename);
9112    (!namespace.is_empty()).then_some(namespace)
9113}
9114
9115fn push_sass_module_edge_fact(
9116    edges: &mut Vec<ParsedSassModuleEdgeFact>,
9117    seen: &mut BTreeSet<(ParsedSassModuleEdgeFactKind, String, u32, u32)>,
9118    edge: ParsedSassModuleEdgeFact,
9119) {
9120    let start: u32 = edge.range.start().into();
9121    let end: u32 = edge.range.end().into();
9122    if seen.insert((edge.kind, edge.source.clone(), start, end)) {
9123        edges.push(edge);
9124    }
9125}
9126
9127fn collect_css_module_value_facts_from_tokens(
9128    tokens: &[Token<'_>],
9129) -> Vec<ParsedCssModuleValueFact> {
9130    let mut values = Vec::new();
9131    let mut seen = BTreeSet::new();
9132    let value_path_aliases = collect_css_module_value_path_aliases_from_tokens(tokens);
9133    for (index, token) in tokens.iter().enumerate() {
9134        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9135            continue;
9136        }
9137
9138        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9139        let end = css_module_value_statement_end(tokens, start);
9140        let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9141        let from_index = top_level_token_text_index(tokens, start, end, "from");
9142
9143        if let Some(from_index) = from_index
9144            && match colon_index {
9145                Some(colon_index) => from_index < colon_index,
9146                None => true,
9147            }
9148        {
9149            collect_css_module_value_import_facts(
9150                tokens,
9151                start,
9152                from_index,
9153                end,
9154                &value_path_aliases,
9155                &mut values,
9156                &mut seen,
9157            );
9158            continue;
9159        }
9160
9161        if let Some(colon_index) = colon_index {
9162            if css_module_value_path_alias_from_tokens(tokens, start, colon_index, end).is_some() {
9163                continue;
9164            }
9165            collect_css_module_value_definition_facts(
9166                tokens,
9167                start,
9168                colon_index,
9169                &mut values,
9170                &mut seen,
9171            );
9172            collect_css_module_value_reference_facts(
9173                tokens,
9174                colon_index + 1,
9175                end,
9176                &mut values,
9177                &mut seen,
9178            );
9179        } else {
9180            collect_css_module_value_definition_facts(tokens, start, end, &mut values, &mut seen);
9181        }
9182    }
9183    let local_value_names = values
9184        .iter()
9185        .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
9186        .map(|value| value.name.clone())
9187        .collect::<BTreeSet<_>>();
9188    collect_css_module_value_declaration_reference_facts(
9189        tokens,
9190        0,
9191        tokens.len(),
9192        &local_value_names,
9193        &mut values,
9194        &mut seen,
9195    );
9196    values
9197}
9198
9199fn collect_css_module_value_path_aliases_from_tokens(
9200    tokens: &[Token<'_>],
9201) -> BTreeMap<String, String> {
9202    let mut aliases = BTreeMap::new();
9203    for (index, token) in tokens.iter().enumerate() {
9204        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9205            continue;
9206        }
9207
9208        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9209        let end = css_module_value_statement_end(tokens, start);
9210        let Some(colon_index) = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon)
9211        else {
9212            continue;
9213        };
9214        if top_level_token_text_index(tokens, start, end, "from").is_some() {
9215            continue;
9216        }
9217        if let Some((name, target)) =
9218            css_module_value_path_alias_from_tokens(tokens, start, colon_index, end)
9219        {
9220            aliases.insert(name, target);
9221        }
9222    }
9223    aliases
9224}
9225
9226fn css_module_value_path_alias_from_tokens(
9227    tokens: &[Token<'_>],
9228    start: usize,
9229    colon_index: usize,
9230    end: usize,
9231) -> Option<(String, String)> {
9232    let name_index = next_non_trivia_token_index_until(tokens, start, colon_index)?;
9233    let name_token = tokens[name_index];
9234    if !css_module_value_name_token_can_define(name_token) {
9235        return None;
9236    }
9237    let source_index = next_non_trivia_token_index_until(tokens, colon_index + 1, end)?;
9238    let source_token = tokens[source_index];
9239    if !matches!(source_token.kind, SyntaxKind::String | SyntaxKind::Url) {
9240        return None;
9241    }
9242    let source = css_module_value_source_name(source_token);
9243    css_module_value_source_looks_like_style_request(&source)
9244        .then(|| (name_token.text.to_string(), source))
9245}
9246
9247fn css_module_value_statement_end(tokens: &[Token<'_>], start: usize) -> usize {
9248    let mut index = start;
9249    let mut paren_depth = 0usize;
9250    let mut bracket_depth = 0usize;
9251    while index < tokens.len() {
9252        match tokens[index].kind {
9253            SyntaxKind::LeftParen => paren_depth += 1,
9254            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9255            SyntaxKind::LeftBracket => bracket_depth += 1,
9256            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9257            SyntaxKind::Semicolon
9258            | SyntaxKind::SassOptionalSemicolon
9259            | SyntaxKind::LeftBrace
9260            | SyntaxKind::RightBrace
9261            | SyntaxKind::SassIndent
9262            | SyntaxKind::SassDedent
9263                if paren_depth == 0 && bracket_depth == 0 =>
9264            {
9265                return index;
9266            }
9267            _ => {}
9268        }
9269        index += 1;
9270    }
9271    index
9272}
9273
9274fn collect_css_module_value_import_facts(
9275    tokens: &[Token<'_>],
9276    start: usize,
9277    from_index: usize,
9278    end: usize,
9279    value_path_aliases: &BTreeMap<String, String>,
9280    values: &mut Vec<ParsedCssModuleValueFact>,
9281    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9282) {
9283    collect_css_module_value_import_names(tokens, start, from_index, values, seen);
9284    if let Some((source_name, source_range)) =
9285        css_module_value_import_edge_source(tokens, from_index + 1, end, value_path_aliases)
9286    {
9287        push_css_module_value_fact(
9288            values,
9289            seen,
9290            ParsedCssModuleValueFactKind::ImportSource,
9291            source_name,
9292            source_range,
9293        );
9294    }
9295}
9296
9297fn collect_css_module_value_import_edge_facts_from_tokens(
9298    tokens: &[Token<'_>],
9299) -> Vec<ParsedCssModuleValueImportEdgeFact> {
9300    let mut edges = Vec::new();
9301    let value_path_aliases = collect_css_module_value_path_aliases_from_tokens(tokens);
9302    for (index, token) in tokens.iter().enumerate() {
9303        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9304            continue;
9305        }
9306
9307        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9308        let end = css_module_value_statement_end(tokens, start);
9309        let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9310        let from_index = top_level_token_text_index(tokens, start, end, "from");
9311        let Some(from_index) = from_index else {
9312            continue;
9313        };
9314        if colon_index.is_some_and(|colon_index| from_index > colon_index) {
9315            continue;
9316        }
9317        let Some((import_source, _source_range)) =
9318            css_module_value_import_edge_source(tokens, from_index + 1, end, &value_path_aliases)
9319        else {
9320            continue;
9321        };
9322
9323        collect_css_module_value_import_edges(tokens, start, from_index, import_source, &mut edges);
9324    }
9325    edges
9326}
9327
9328fn collect_css_module_value_definition_edge_facts_from_tokens(
9329    tokens: &[Token<'_>],
9330) -> Vec<ParsedCssModuleValueDefinitionEdgeFact> {
9331    let mut edges = Vec::new();
9332    for (index, token) in tokens.iter().enumerate() {
9333        if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9334            continue;
9335        }
9336
9337        let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9338        let end = css_module_value_statement_end(tokens, start);
9339        let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9340        let from_index = top_level_token_text_index(tokens, start, end, "from");
9341        let Some(colon_index) = colon_index else {
9342            continue;
9343        };
9344        if from_index.is_some_and(|from_index| from_index < colon_index) {
9345            continue;
9346        }
9347
9348        let definition_names = collect_css_module_value_definition_edge_names(
9349            tokens,
9350            start,
9351            colon_index,
9352            |tokens, index| css_module_value_name_token_can_define(tokens[index]),
9353        );
9354        let reference_names = collect_css_module_value_definition_edge_names(
9355            tokens,
9356            colon_index + 1,
9357            end,
9358            css_module_value_reference_token_can_be_name,
9359        );
9360        if reference_names.is_empty() {
9361            continue;
9362        }
9363        let range_end = end
9364            .checked_sub(1)
9365            .and_then(|end| tokens.get(end))
9366            .map(|token| token.range.end())
9367            .unwrap_or_else(|| tokens[index].range.end());
9368
9369        for definition_name in definition_names {
9370            edges.push(ParsedCssModuleValueDefinitionEdgeFact {
9371                definition_name,
9372                reference_names: reference_names.clone(),
9373                range: TextRange::new(tokens[index].range.start(), range_end),
9374            });
9375        }
9376    }
9377    edges
9378}
9379
9380fn collect_css_module_value_definition_edge_names(
9381    tokens: &[Token<'_>],
9382    start: usize,
9383    end: usize,
9384    predicate: impl Fn(&[Token<'_>], usize) -> bool,
9385) -> Vec<String> {
9386    let mut names = Vec::new();
9387    let mut index = start;
9388    while index < end {
9389        if predicate(tokens, index) && !names.iter().any(|name| name == tokens[index].text) {
9390            names.push(tokens[index].text.to_string());
9391        }
9392        index += 1;
9393    }
9394    names
9395}
9396
9397fn css_module_value_import_edge_source(
9398    tokens: &[Token<'_>],
9399    start: usize,
9400    end: usize,
9401    value_path_aliases: &BTreeMap<String, String>,
9402) -> Option<(String, TextRange)> {
9403    let source_index = next_non_trivia_token_index_until(tokens, start, end)?;
9404    let token = tokens[source_index];
9405    if matches!(token.kind, SyntaxKind::String | SyntaxKind::Url) {
9406        return Some((css_module_value_source_name(token), token.range));
9407    }
9408    if css_module_value_name_token_can_define(token) {
9409        return css_module_value_source_alias_target(token.text, token.range, value_path_aliases);
9410    }
9411    None
9412}
9413
9414fn css_module_value_source_alias_target(
9415    name: &str,
9416    range: TextRange,
9417    value_path_aliases: &BTreeMap<String, String>,
9418) -> Option<(String, TextRange)> {
9419    value_path_aliases
9420        .get(name)
9421        .map(|source| (source.clone(), range))
9422}
9423
9424fn collect_css_module_value_import_edges(
9425    tokens: &[Token<'_>],
9426    start: usize,
9427    end: usize,
9428    import_source: String,
9429    edges: &mut Vec<ParsedCssModuleValueImportEdgeFact>,
9430) {
9431    let mut index = start;
9432    while index < end {
9433        let token = tokens[index];
9434        if !css_module_value_name_token_can_define(token) {
9435            index += 1;
9436            continue;
9437        }
9438        if previous_non_trivia_token_index(tokens, index, start)
9439            .is_some_and(|previous| tokens[previous].text == "as")
9440        {
9441            index += 1;
9442            continue;
9443        }
9444        let remote_name = token.text.to_string();
9445        let mut local_name = remote_name.clone();
9446        let mut local_range = token.range;
9447        if let Some(as_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
9448            && tokens[as_index].text == "as"
9449            && let Some(local_index) = next_non_trivia_token_index_until(tokens, as_index + 1, end)
9450            && css_module_value_name_token_can_define(tokens[local_index])
9451        {
9452            local_name = tokens[local_index].text.to_string();
9453            local_range = tokens[local_index].range;
9454            index = local_index + 1;
9455        } else {
9456            index += 1;
9457        }
9458        edges.push(ParsedCssModuleValueImportEdgeFact {
9459            remote_name,
9460            local_name,
9461            import_source: import_source.clone(),
9462            local_range,
9463            remote_range: token.range,
9464            range: token.range,
9465        });
9466    }
9467}
9468
9469fn collect_css_module_value_import_names(
9470    tokens: &[Token<'_>],
9471    start: usize,
9472    end: usize,
9473    values: &mut Vec<ParsedCssModuleValueFact>,
9474    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9475) {
9476    let mut index = start;
9477    while index < end {
9478        let token = tokens[index];
9479        if css_module_value_name_token_can_define(token) {
9480            let previous = previous_non_trivia_token_index(tokens, index, start);
9481            let next = next_non_trivia_token_index_until(tokens, index + 1, end);
9482            let kind = if previous.is_some_and(|previous| tokens[previous].text == "as") {
9483                Some(ParsedCssModuleValueFactKind::Definition)
9484            } else if next.is_some_and(|next| tokens[next].text == "as") {
9485                Some(ParsedCssModuleValueFactKind::Reference)
9486            } else {
9487                Some(ParsedCssModuleValueFactKind::Definition)
9488            };
9489            if let Some(kind) = kind {
9490                push_css_module_value_fact(values, seen, kind, token.text.to_string(), token.range);
9491            }
9492        }
9493        index += 1;
9494    }
9495}
9496
9497fn collect_css_module_value_definition_facts(
9498    tokens: &[Token<'_>],
9499    start: usize,
9500    end: usize,
9501    values: &mut Vec<ParsedCssModuleValueFact>,
9502    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9503) {
9504    let mut index = start;
9505    while index < end {
9506        let token = tokens[index];
9507        if css_module_value_name_token_can_define(token) {
9508            push_css_module_value_fact(
9509                values,
9510                seen,
9511                ParsedCssModuleValueFactKind::Definition,
9512                token.text.to_string(),
9513                token.range,
9514            );
9515        }
9516        index += 1;
9517    }
9518}
9519
9520fn collect_css_module_value_reference_facts(
9521    tokens: &[Token<'_>],
9522    start: usize,
9523    end: usize,
9524    values: &mut Vec<ParsedCssModuleValueFact>,
9525    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9526) {
9527    let mut index = start;
9528    let mut paren_depth = 0usize;
9529    let mut bracket_depth = 0usize;
9530    while index < end {
9531        match tokens[index].kind {
9532            SyntaxKind::LeftParen => paren_depth += 1,
9533            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9534            SyntaxKind::LeftBracket => bracket_depth += 1,
9535            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9536            _ => {}
9537        }
9538        if paren_depth == 0
9539            && bracket_depth == 0
9540            && css_module_value_reference_token_can_be_name(tokens, index)
9541        {
9542            push_css_module_value_fact(
9543                values,
9544                seen,
9545                ParsedCssModuleValueFactKind::Reference,
9546                tokens[index].text.to_string(),
9547                tokens[index].range,
9548            );
9549        }
9550        index += 1;
9551    }
9552}
9553
9554fn collect_css_module_value_declaration_reference_facts(
9555    tokens: &[Token<'_>],
9556    start: usize,
9557    end: usize,
9558    local_value_names: &BTreeSet<String>,
9559    values: &mut Vec<ParsedCssModuleValueFact>,
9560    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9561) {
9562    if local_value_names.is_empty() {
9563        return;
9564    }
9565
9566    let mut index = start;
9567    while index < end {
9568        index = skip_trivia_tokens(tokens, index, end);
9569        if index >= end {
9570            break;
9571        }
9572
9573        if tokens[index].kind == SyntaxKind::AtKeyword {
9574            let block = find_block_after_header(tokens, index, end);
9575            if let Some((open, close)) = block {
9576                if style_wrapper_at_rule(tokens[index].text) {
9577                    collect_css_module_value_declaration_reference_facts(
9578                        tokens,
9579                        open + 1,
9580                        close,
9581                        local_value_names,
9582                        values,
9583                        seen,
9584                    );
9585                }
9586                index = close + 1;
9587            } else {
9588                index = skip_statement(tokens, index, end);
9589            }
9590            continue;
9591        }
9592
9593        let statement_end = css_module_value_statement_end(tokens, index);
9594        if statement_end < end && tokens[statement_end].kind == SyntaxKind::LeftBrace {
9595            if let Some(close) = matching_right_brace(tokens, statement_end, end) {
9596                collect_css_module_value_declaration_reference_facts(
9597                    tokens,
9598                    statement_end + 1,
9599                    close,
9600                    local_value_names,
9601                    values,
9602                    seen,
9603                );
9604                index = close + 1;
9605            } else {
9606                index = statement_end + 1;
9607            }
9608            continue;
9609        }
9610
9611        if let Some(colon_index) = declaration_colon_index(tokens, index, statement_end.min(end)) {
9612            collect_known_css_module_value_reference_facts(
9613                tokens,
9614                colon_index + 1,
9615                statement_end.min(end),
9616                local_value_names,
9617                values,
9618                seen,
9619            );
9620        }
9621
9622        if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
9623            break;
9624        }
9625        index = statement_end + 1;
9626    }
9627}
9628
9629fn declaration_colon_index(tokens: &[Token<'_>], start: usize, end: usize) -> Option<usize> {
9630    let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon)?;
9631    let property_index = previous_non_trivia_token_index(tokens, colon_index, start)?;
9632    if !matches!(
9633        tokens[property_index].kind,
9634        SyntaxKind::Ident
9635            | SyntaxKind::CustomPropertyName
9636            | SyntaxKind::ScssVariable
9637            | SyntaxKind::LessVariable
9638            | SyntaxKind::LessPropertyVariableToken
9639    ) {
9640        return None;
9641    }
9642    let value_index = next_non_trivia_token_index_until(tokens, colon_index + 1, end)?;
9643    if matches!(
9644        tokens[value_index].kind,
9645        SyntaxKind::LeftBrace | SyntaxKind::LeftParen | SyntaxKind::LeftBracket
9646    ) {
9647        return None;
9648    }
9649    Some(colon_index)
9650}
9651
9652fn collect_known_css_module_value_reference_facts(
9653    tokens: &[Token<'_>],
9654    start: usize,
9655    end: usize,
9656    local_value_names: &BTreeSet<String>,
9657    values: &mut Vec<ParsedCssModuleValueFact>,
9658    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9659) {
9660    let mut index = start;
9661    let mut paren_depth = 0usize;
9662    let mut bracket_depth = 0usize;
9663    while index < end {
9664        match tokens[index].kind {
9665            SyntaxKind::LeftParen => paren_depth += 1,
9666            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9667            SyntaxKind::LeftBracket => bracket_depth += 1,
9668            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9669            _ => {}
9670        }
9671        if paren_depth == 0
9672            && bracket_depth == 0
9673            && css_module_value_reference_token_can_be_name(tokens, index)
9674            && local_value_names.contains(tokens[index].text)
9675        {
9676            push_css_module_value_fact(
9677                values,
9678                seen,
9679                ParsedCssModuleValueFactKind::Reference,
9680                tokens[index].text.to_string(),
9681                tokens[index].range,
9682            );
9683        }
9684        index += 1;
9685    }
9686}
9687
9688fn push_css_module_value_fact(
9689    values: &mut Vec<ParsedCssModuleValueFact>,
9690    seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9691    kind: ParsedCssModuleValueFactKind,
9692    name: String,
9693    range: TextRange,
9694) {
9695    if seen.insert((
9696        kind,
9697        name.clone(),
9698        u32::from(range.start()),
9699        u32::from(range.end()),
9700    )) {
9701        values.push(ParsedCssModuleValueFact { kind, name, range });
9702    }
9703}
9704
9705fn top_level_token_kind_index(
9706    tokens: &[Token<'_>],
9707    start: usize,
9708    end: usize,
9709    expected: SyntaxKind,
9710) -> Option<usize> {
9711    let mut index = start;
9712    let mut paren_depth = 0usize;
9713    let mut bracket_depth = 0usize;
9714    while index < end {
9715        match tokens[index].kind {
9716            SyntaxKind::LeftParen => paren_depth += 1,
9717            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9718            SyntaxKind::LeftBracket => bracket_depth += 1,
9719            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9720            kind if kind == expected && paren_depth == 0 && bracket_depth == 0 => {
9721                return Some(index);
9722            }
9723            _ => {}
9724        }
9725        index += 1;
9726    }
9727    None
9728}
9729
9730fn top_level_token_text_index(
9731    tokens: &[Token<'_>],
9732    start: usize,
9733    end: usize,
9734    expected: &str,
9735) -> Option<usize> {
9736    let mut index = start;
9737    let mut paren_depth = 0usize;
9738    let mut bracket_depth = 0usize;
9739    while index < end {
9740        match tokens[index].kind {
9741            SyntaxKind::LeftParen => paren_depth += 1,
9742            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9743            SyntaxKind::LeftBracket => bracket_depth += 1,
9744            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9745            SyntaxKind::Ident
9746                if paren_depth == 0
9747                    && bracket_depth == 0
9748                    && tokens[index].text.eq_ignore_ascii_case(expected) =>
9749            {
9750                return Some(index);
9751            }
9752            _ => {}
9753        }
9754        index += 1;
9755    }
9756    None
9757}
9758
9759fn previous_non_trivia_token_index(
9760    tokens: &[Token<'_>],
9761    mut index: usize,
9762    start: usize,
9763) -> Option<usize> {
9764    while index > start {
9765        index -= 1;
9766        if !tokens[index].kind.is_trivia() {
9767            return Some(index);
9768        }
9769    }
9770    None
9771}
9772
9773fn css_module_value_name_token_can_define(token: Token<'_>) -> bool {
9774    matches!(
9775        token.kind,
9776        SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9777    ) && !matches!(token.text, "as" | "from")
9778}
9779
9780fn css_module_value_reference_token_can_be_name(tokens: &[Token<'_>], index: usize) -> bool {
9781    let token = tokens[index];
9782    if !matches!(
9783        token.kind,
9784        SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9785    ) {
9786        return false;
9787    }
9788    if let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9789        && tokens[next_index].kind == SyntaxKind::LeftParen
9790    {
9791        return false;
9792    }
9793    !css_module_value_literal_ident_is_not_reference(token.text)
9794}
9795
9796fn css_module_value_literal_ident_is_not_reference(name: &str) -> bool {
9797    matches!(
9798        name.to_ascii_lowercase().as_str(),
9799        "initial"
9800            | "inherit"
9801            | "unset"
9802            | "revert"
9803            | "revert-layer"
9804            | "none"
9805            | "auto"
9806            | "normal"
9807            | "transparent"
9808            | "currentcolor"
9809            | "black"
9810            | "white"
9811            | "red"
9812            | "green"
9813            | "blue"
9814            | "yellow"
9815            | "magenta"
9816            | "cyan"
9817            | "solid"
9818            | "dashed"
9819            | "block"
9820            | "inline"
9821            | "flex"
9822            | "grid"
9823    )
9824}
9825
9826fn css_module_value_source_name(token: Token<'_>) -> String {
9827    token
9828        .text
9829        .trim_matches(|character| character == '"' || character == '\'')
9830        .to_string()
9831}
9832
9833fn css_module_value_source_looks_like_style_request(source: &str) -> bool {
9834    let lower = source.to_ascii_lowercase();
9835    (lower.starts_with('/') || lower.starts_with("./") || lower.starts_with("../"))
9836        && (lower.ends_with(".css")
9837            || lower.ends_with(".scss")
9838            || lower.ends_with(".sass")
9839            || lower.ends_with(".less"))
9840}
9841
9842fn collect_css_module_composes_facts_from_tokens(
9843    tokens: &[Token<'_>],
9844) -> Vec<ParsedCssModuleComposesFact> {
9845    let mut composes = Vec::new();
9846    let mut seen = BTreeSet::new();
9847    for (index, token) in tokens.iter().enumerate() {
9848        if token.kind != SyntaxKind::Ident || !token.text.eq_ignore_ascii_case("composes") {
9849            continue;
9850        }
9851        let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9852        else {
9853            continue;
9854        };
9855        if tokens[colon_index].kind != SyntaxKind::Colon {
9856            continue;
9857        }
9858
9859        let start = colon_index + 1;
9860        let end = css_module_value_statement_end(tokens, start);
9861        let from_index = top_level_token_text_index(tokens, start, end, "from");
9862        let target_end = from_index.unwrap_or(end);
9863        collect_css_module_composes_targets(tokens, start, target_end, &mut composes, &mut seen);
9864        if let Some(from_index) = from_index {
9865            collect_css_module_composes_import_source(
9866                tokens,
9867                from_index + 1,
9868                end,
9869                &mut composes,
9870                &mut seen,
9871            );
9872        }
9873    }
9874    composes
9875}
9876
9877fn collect_css_module_composes_edge_facts_from_tokens(
9878    tokens: &[Token<'_>],
9879) -> Vec<ParsedCssModuleComposesEdgeFact> {
9880    let mut edges = Vec::new();
9881    collect_css_module_composes_edge_facts_in_range(tokens, 0, tokens.len(), &[], None, &mut edges);
9882    edges
9883}
9884
9885fn collect_css_module_composes_edge_facts_in_range(
9886    tokens: &[Token<'_>],
9887    start: usize,
9888    end: usize,
9889    parent_branches: &[SelectorBranch],
9890    css_module_scope: Option<&'static str>,
9891    edges: &mut Vec<ParsedCssModuleComposesEdgeFact>,
9892) {
9893    let mut index = start;
9894    while index < end {
9895        index = skip_trivia_tokens(tokens, index, end);
9896        if index >= end {
9897            break;
9898        }
9899
9900        if tokens[index].kind == SyntaxKind::AtKeyword {
9901            let block = find_block_after_header(tokens, index, end);
9902            if let Some((open, close)) = block {
9903                if tokens[index].text == "@nest" {
9904                    if css_module_scope == Some("global") {
9905                        collect_css_module_composes_edge_facts_in_range(
9906                            tokens,
9907                            open + 1,
9908                            close,
9909                            &[],
9910                            css_module_scope,
9911                            edges,
9912                        );
9913                    } else {
9914                        let branches =
9915                            resolve_selector_header(tokens, index + 1, open, parent_branches);
9916                        collect_immediate_css_module_composes_edge_facts(
9917                            tokens,
9918                            open + 1,
9919                            close,
9920                            &branches,
9921                            edges,
9922                        );
9923                        collect_css_module_composes_edge_facts_in_range(
9924                            tokens,
9925                            open + 1,
9926                            close,
9927                            &branches,
9928                            css_module_scope,
9929                            edges,
9930                        );
9931                    }
9932                } else if style_wrapper_at_rule(tokens[index].text) {
9933                    collect_css_module_composes_edge_facts_in_range(
9934                        tokens,
9935                        open + 1,
9936                        close,
9937                        parent_branches,
9938                        css_module_scope,
9939                        edges,
9940                    );
9941                }
9942                index = close + 1;
9943            } else {
9944                index = skip_statement(tokens, index, end);
9945            }
9946            continue;
9947        }
9948
9949        let Some((open, close)) = find_block_after_header(tokens, index, end) else {
9950            index = skip_statement(tokens, index, end);
9951            continue;
9952        };
9953
9954        let effective_scope = css_module_scope
9955            .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
9956        if effective_scope == Some("global") {
9957            collect_css_module_composes_edge_facts_in_range(
9958                tokens,
9959                open + 1,
9960                close,
9961                &[],
9962                effective_scope,
9963                edges,
9964            );
9965        } else {
9966            let branches = resolve_selector_header(tokens, index, open, parent_branches);
9967            collect_immediate_css_module_composes_edge_facts(
9968                tokens,
9969                open + 1,
9970                close,
9971                &branches,
9972                edges,
9973            );
9974            collect_css_module_composes_edge_facts_in_range(
9975                tokens,
9976                open + 1,
9977                close,
9978                &branches,
9979                effective_scope,
9980                edges,
9981            );
9982        }
9983        index = close + 1;
9984    }
9985}
9986
9987fn collect_immediate_css_module_composes_edge_facts(
9988    tokens: &[Token<'_>],
9989    start: usize,
9990    end: usize,
9991    owner_branches: &[SelectorBranch],
9992    edges: &mut Vec<ParsedCssModuleComposesEdgeFact>,
9993) {
9994    let owner_selector_names = sorted_selector_branch_names(owner_branches);
9995    let mut index = start;
9996    let mut block_depth = 0usize;
9997    while index < end {
9998        match tokens[index].kind {
9999            SyntaxKind::LeftBrace | SyntaxKind::SassIndent => {
10000                block_depth += 1;
10001                index += 1;
10002                continue;
10003            }
10004            SyntaxKind::RightBrace | SyntaxKind::SassDedent => {
10005                block_depth = block_depth.saturating_sub(1);
10006                index += 1;
10007                continue;
10008            }
10009            _ => {}
10010        }
10011        if block_depth > 0
10012            || tokens[index].kind != SyntaxKind::Ident
10013            || !tokens[index].text.eq_ignore_ascii_case("composes")
10014        {
10015            index += 1;
10016            continue;
10017        }
10018        let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end) else {
10019            index += 1;
10020            continue;
10021        };
10022        if tokens[colon_index].kind != SyntaxKind::Colon {
10023            index += 1;
10024            continue;
10025        }
10026
10027        let value_start = colon_index + 1;
10028        let value_end = css_module_value_statement_end(tokens, value_start).min(end);
10029        let from_index = top_level_token_text_index(tokens, value_start, value_end, "from");
10030        let target_end = from_index.unwrap_or(value_end);
10031        let target_names =
10032            collect_css_module_composes_target_names(tokens, value_start, target_end);
10033        if target_names.is_empty() {
10034            index = value_end;
10035            continue;
10036        }
10037
10038        let (kind, import_source) = from_index
10039            .and_then(|from_index| {
10040                css_module_composes_import_edge_source(tokens, from_index + 1, value_end)
10041            })
10042            .map(|source| {
10043                if source == "global" {
10044                    (ParsedCssModuleComposesEdgeKind::Global, Some(source))
10045                } else {
10046                    (ParsedCssModuleComposesEdgeKind::External, Some(source))
10047                }
10048            })
10049            .unwrap_or((ParsedCssModuleComposesEdgeKind::Local, None));
10050        let range_end = value_end
10051            .checked_sub(1)
10052            .and_then(|end| tokens.get(end))
10053            .map(|token| token.range.end())
10054            .unwrap_or_else(|| tokens[index].range.end());
10055
10056        edges.push(ParsedCssModuleComposesEdgeFact {
10057            kind,
10058            owner_selector_names: owner_selector_names.clone(),
10059            target_names,
10060            import_source,
10061            range: TextRange::new(tokens[index].range.start(), range_end),
10062        });
10063        index = value_end;
10064    }
10065}
10066
10067fn sorted_selector_branch_names(branches: &[SelectorBranch]) -> Vec<String> {
10068    branches
10069        .iter()
10070        .map(|branch| branch.name.clone())
10071        .collect::<BTreeSet<_>>()
10072        .into_iter()
10073        .collect()
10074}
10075
10076fn collect_css_module_composes_target_names(
10077    tokens: &[Token<'_>],
10078    start: usize,
10079    end: usize,
10080) -> Vec<String> {
10081    let mut names = Vec::new();
10082    let mut index = start;
10083    while index < end {
10084        if matches!(
10085            tokens[index].kind,
10086            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10087        ) && !tokens[index].text.eq_ignore_ascii_case("from")
10088            && !names.iter().any(|name| name == tokens[index].text)
10089        {
10090            names.push(tokens[index].text.to_string());
10091        }
10092        index += 1;
10093    }
10094    names
10095}
10096
10097fn css_module_composes_import_edge_source(
10098    tokens: &[Token<'_>],
10099    start: usize,
10100    end: usize,
10101) -> Option<String> {
10102    let source_index = next_non_trivia_token_index_until(tokens, start, end)?;
10103    let token = tokens[source_index];
10104    matches!(
10105        token.kind,
10106        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10107    )
10108    .then(|| css_module_value_source_name(token))
10109}
10110
10111fn collect_css_module_composes_targets(
10112    tokens: &[Token<'_>],
10113    start: usize,
10114    end: usize,
10115    composes: &mut Vec<ParsedCssModuleComposesFact>,
10116    seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
10117) {
10118    let mut index = start;
10119    while index < end {
10120        if matches!(
10121            tokens[index].kind,
10122            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10123        ) && !tokens[index].text.eq_ignore_ascii_case("from")
10124        {
10125            push_css_module_composes_fact(
10126                composes,
10127                seen,
10128                ParsedCssModuleComposesFactKind::Target,
10129                tokens[index].text.to_string(),
10130                tokens[index].range,
10131            );
10132        }
10133        index += 1;
10134    }
10135}
10136
10137fn collect_css_module_composes_import_source(
10138    tokens: &[Token<'_>],
10139    start: usize,
10140    end: usize,
10141    composes: &mut Vec<ParsedCssModuleComposesFact>,
10142    seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
10143) {
10144    if let Some(source_index) = next_non_trivia_token_index_until(tokens, start, end) {
10145        let token = tokens[source_index];
10146        if matches!(
10147            token.kind,
10148            SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10149        ) {
10150            push_css_module_composes_fact(
10151                composes,
10152                seen,
10153                ParsedCssModuleComposesFactKind::ImportSource,
10154                css_module_value_source_name(token),
10155                token.range,
10156            );
10157        }
10158    }
10159}
10160
10161fn push_css_module_composes_fact(
10162    composes: &mut Vec<ParsedCssModuleComposesFact>,
10163    seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
10164    kind: ParsedCssModuleComposesFactKind,
10165    name: String,
10166    range: TextRange,
10167) {
10168    if seen.insert((
10169        kind,
10170        name.clone(),
10171        u32::from(range.start()),
10172        u32::from(range.end()),
10173    )) {
10174        composes.push(ParsedCssModuleComposesFact { kind, name, range });
10175    }
10176}
10177
10178fn collect_icss_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedIcssFact> {
10179    let mut icss = Vec::new();
10180    let mut seen = BTreeSet::new();
10181    for (index, token) in tokens.iter().enumerate() {
10182        if token.kind != SyntaxKind::Colon {
10183            continue;
10184        }
10185        let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10186        else {
10187            continue;
10188        };
10189        let name = tokens[name_index].text;
10190        if !matches!(tokens[name_index].kind, SyntaxKind::Ident) {
10191            continue;
10192        }
10193        if name.eq_ignore_ascii_case("export") {
10194            if let Some((open, close)) =
10195                find_block_after_header(tokens, name_index + 1, tokens.len())
10196            {
10197                collect_icss_export_names(tokens, open + 1, close, &mut icss, &mut seen);
10198            }
10199            continue;
10200        }
10201        if name.eq_ignore_ascii_case("import") {
10202            collect_icss_import_source(tokens, name_index + 1, &mut icss, &mut seen);
10203            if let Some((open, close)) =
10204                find_block_after_header(tokens, name_index + 1, tokens.len())
10205            {
10206                collect_icss_import_names(tokens, open + 1, close, &mut icss, &mut seen);
10207            }
10208        }
10209    }
10210    icss
10211}
10212
10213fn collect_icss_import_edge_facts_from_tokens(
10214    tokens: &[Token<'_>],
10215) -> Vec<ParsedIcssImportEdgeFact> {
10216    let mut edges = Vec::new();
10217    for (index, token) in tokens.iter().enumerate() {
10218        if token.kind != SyntaxKind::Colon {
10219            continue;
10220        }
10221        let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10222        else {
10223            continue;
10224        };
10225        if tokens[name_index].kind != SyntaxKind::Ident
10226            || !tokens[name_index].text.eq_ignore_ascii_case("import")
10227        {
10228            continue;
10229        }
10230        let Some(import_source) = icss_import_edge_source(tokens, name_index + 1) else {
10231            continue;
10232        };
10233        if let Some((open, close)) = find_block_after_header(tokens, name_index + 1, tokens.len()) {
10234            collect_icss_import_edges(tokens, open + 1, close, import_source, &mut edges);
10235        }
10236    }
10237    edges
10238}
10239
10240fn collect_icss_export_edge_facts_from_tokens(
10241    tokens: &[Token<'_>],
10242) -> Vec<ParsedIcssExportEdgeFact> {
10243    let mut edges = Vec::new();
10244    for (index, token) in tokens.iter().enumerate() {
10245        if token.kind != SyntaxKind::Colon {
10246            continue;
10247        }
10248        let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10249        else {
10250            continue;
10251        };
10252        if tokens[name_index].kind != SyntaxKind::Ident
10253            || !tokens[name_index].text.eq_ignore_ascii_case("export")
10254        {
10255            continue;
10256        }
10257        if let Some((open, close)) = find_block_after_header(tokens, name_index + 1, tokens.len()) {
10258            collect_icss_export_edges(tokens, open + 1, close, &mut edges);
10259        }
10260    }
10261    edges
10262}
10263
10264fn collect_icss_export_edges(
10265    tokens: &[Token<'_>],
10266    start: usize,
10267    end: usize,
10268    edges: &mut Vec<ParsedIcssExportEdgeFact>,
10269) {
10270    let mut index = start;
10271    while index < end {
10272        let token = tokens[index];
10273        if matches!(
10274            token.kind,
10275            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10276        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10277            && tokens[colon_index].kind == SyntaxKind::Colon
10278        {
10279            let value_end = css_module_value_statement_end(tokens, colon_index + 1).min(end);
10280            let reference_names = collect_css_module_value_definition_edge_names(
10281                tokens,
10282                colon_index + 1,
10283                value_end,
10284                css_module_value_reference_token_can_be_name,
10285            );
10286            if !reference_names.is_empty() {
10287                let range_end = value_end
10288                    .checked_sub(1)
10289                    .and_then(|end| tokens.get(end))
10290                    .map(|token| token.range.end())
10291                    .unwrap_or_else(|| token.range.end());
10292                edges.push(ParsedIcssExportEdgeFact {
10293                    export_name: token.text.to_string(),
10294                    reference_names,
10295                    range: TextRange::new(token.range.start(), range_end),
10296                });
10297            }
10298            index = value_end;
10299            continue;
10300        }
10301        index += 1;
10302    }
10303}
10304
10305fn icss_import_edge_source(tokens: &[Token<'_>], start: usize) -> Option<String> {
10306    let open_index = next_non_trivia_token_index_until(tokens, start, tokens.len())?;
10307    if tokens[open_index].kind != SyntaxKind::LeftParen {
10308        return None;
10309    }
10310    let source_index = next_non_trivia_token_index_until(tokens, open_index + 1, tokens.len())?;
10311    let token = tokens[source_index];
10312    matches!(
10313        token.kind,
10314        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10315    )
10316    .then(|| css_module_value_source_name(token))
10317}
10318
10319fn collect_icss_import_edges(
10320    tokens: &[Token<'_>],
10321    start: usize,
10322    end: usize,
10323    import_source: String,
10324    edges: &mut Vec<ParsedIcssImportEdgeFact>,
10325) {
10326    let mut index = start;
10327    while index < end {
10328        let token = tokens[index];
10329        if matches!(
10330            token.kind,
10331            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10332        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10333            && tokens[colon_index].kind == SyntaxKind::Colon
10334            && let Some(remote_index) =
10335                next_non_trivia_token_index_until(tokens, colon_index + 1, end)
10336            && matches!(
10337                tokens[remote_index].kind,
10338                SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10339            )
10340        {
10341            edges.push(ParsedIcssImportEdgeFact {
10342                local_name: token.text.to_string(),
10343                remote_name: tokens[remote_index].text.to_string(),
10344                import_source: import_source.clone(),
10345                range: token.range,
10346            });
10347            index = css_module_value_statement_end(tokens, colon_index + 1);
10348            continue;
10349        }
10350        index += 1;
10351    }
10352}
10353
10354fn collect_icss_export_names(
10355    tokens: &[Token<'_>],
10356    start: usize,
10357    end: usize,
10358    icss: &mut Vec<ParsedIcssFact>,
10359    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10360) {
10361    let mut index = start;
10362    while index < end {
10363        let token = tokens[index];
10364        if matches!(
10365            token.kind,
10366            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10367        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10368            && tokens[colon_index].kind == SyntaxKind::Colon
10369        {
10370            push_icss_fact(
10371                icss,
10372                seen,
10373                ParsedIcssFactKind::ExportName,
10374                token.text.to_string(),
10375                token.range,
10376            );
10377            index = css_module_value_statement_end(tokens, colon_index + 1);
10378            continue;
10379        }
10380        index += 1;
10381    }
10382}
10383
10384fn collect_icss_import_source(
10385    tokens: &[Token<'_>],
10386    start: usize,
10387    icss: &mut Vec<ParsedIcssFact>,
10388    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10389) {
10390    let Some(open_index) = next_non_trivia_token_index_until(tokens, start, tokens.len()) else {
10391        return;
10392    };
10393    if tokens[open_index].kind != SyntaxKind::LeftParen {
10394        return;
10395    }
10396    let Some(source_index) =
10397        next_non_trivia_token_index_until(tokens, open_index + 1, tokens.len())
10398    else {
10399        return;
10400    };
10401    let token = tokens[source_index];
10402    if matches!(
10403        token.kind,
10404        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10405    ) {
10406        push_icss_fact(
10407            icss,
10408            seen,
10409            ParsedIcssFactKind::ImportSource,
10410            css_module_value_source_name(token),
10411            token.range,
10412        );
10413    }
10414}
10415
10416fn collect_icss_import_names(
10417    tokens: &[Token<'_>],
10418    start: usize,
10419    end: usize,
10420    icss: &mut Vec<ParsedIcssFact>,
10421    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10422) {
10423    let mut index = start;
10424    while index < end {
10425        let token = tokens[index];
10426        if matches!(
10427            token.kind,
10428            SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10429        ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10430            && tokens[colon_index].kind == SyntaxKind::Colon
10431        {
10432            push_icss_fact(
10433                icss,
10434                seen,
10435                ParsedIcssFactKind::ImportLocalName,
10436                token.text.to_string(),
10437                token.range,
10438            );
10439            if let Some(remote_index) =
10440                next_non_trivia_token_index_until(tokens, colon_index + 1, end)
10441                && matches!(
10442                    tokens[remote_index].kind,
10443                    SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10444                )
10445            {
10446                push_icss_fact(
10447                    icss,
10448                    seen,
10449                    ParsedIcssFactKind::ImportRemoteName,
10450                    tokens[remote_index].text.to_string(),
10451                    tokens[remote_index].range,
10452                );
10453            }
10454            index = css_module_value_statement_end(tokens, colon_index + 1);
10455            continue;
10456        }
10457        index += 1;
10458    }
10459}
10460
10461fn push_icss_fact(
10462    icss: &mut Vec<ParsedIcssFact>,
10463    seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10464    kind: ParsedIcssFactKind,
10465    name: String,
10466    range: TextRange,
10467) {
10468    if seen.insert((
10469        kind,
10470        name.clone(),
10471        u32::from(range.start()),
10472        u32::from(range.end()),
10473    )) {
10474        icss.push(ParsedIcssFact { kind, name, range });
10475    }
10476}
10477
10478fn collect_animation_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedAnimationFact> {
10479    let mut animations = Vec::new();
10480    let mut seen = BTreeSet::new();
10481    for (index, token) in tokens.iter().enumerate() {
10482        if token.kind == SyntaxKind::AtKeyword && at_keyword_is_keyframes_rule(token.text) {
10483            if let Some(name_index) =
10484                next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10485                && let Some(name) = animation_name_from_token(tokens[name_index])
10486            {
10487                push_animation_fact(
10488                    &mut animations,
10489                    &mut seen,
10490                    ParsedAnimationFactKind::KeyframesDeclaration,
10491                    name,
10492                    tokens[name_index].range,
10493                );
10494            }
10495            continue;
10496        }
10497
10498        if token.kind == SyntaxKind::Ident
10499            && token.text.eq_ignore_ascii_case("animation-name")
10500            && let Some(colon_index) =
10501                next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10502            && tokens[colon_index].kind == SyntaxKind::Colon
10503        {
10504            collect_animation_name_references_until(
10505                tokens,
10506                colon_index + 1,
10507                &mut animations,
10508                &mut seen,
10509            );
10510        }
10511
10512        if token.kind == SyntaxKind::Ident
10513            && token.text.eq_ignore_ascii_case("animation")
10514            && let Some(colon_index) =
10515                next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10516            && tokens[colon_index].kind == SyntaxKind::Colon
10517        {
10518            collect_animation_shorthand_references_until(
10519                tokens,
10520                colon_index + 1,
10521                &mut animations,
10522                &mut seen,
10523            );
10524        }
10525    }
10526    animations
10527}
10528
10529fn collect_animation_name_references_until(
10530    tokens: &[Token<'_>],
10531    start: usize,
10532    animations: &mut Vec<ParsedAnimationFact>,
10533    seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10534) {
10535    let mut index = start;
10536    let mut paren_depth = 0usize;
10537    let mut bracket_depth = 0usize;
10538    while index < tokens.len() {
10539        match tokens[index].kind {
10540            SyntaxKind::LeftParen => paren_depth += 1,
10541            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
10542            SyntaxKind::LeftBracket => bracket_depth += 1,
10543            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
10544            SyntaxKind::Semicolon
10545            | SyntaxKind::SassOptionalSemicolon
10546            | SyntaxKind::RightBrace
10547            | SyntaxKind::SassDedent
10548                if paren_depth == 0 && bracket_depth == 0 =>
10549            {
10550                break;
10551            }
10552            _ => {}
10553        }
10554
10555        if paren_depth == 0
10556            && bracket_depth == 0
10557            && !animation_name_token_is_interpolation_adjacent(tokens, index)
10558            && let Some(name) = animation_name_from_token(tokens[index])
10559        {
10560            push_animation_fact(
10561                animations,
10562                seen,
10563                ParsedAnimationFactKind::AnimationNameReference,
10564                name,
10565                tokens[index].range,
10566            );
10567        }
10568        index += 1;
10569    }
10570}
10571
10572fn collect_animation_shorthand_references_until(
10573    tokens: &[Token<'_>],
10574    start: usize,
10575    animations: &mut Vec<ParsedAnimationFact>,
10576    seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10577) {
10578    let mut index = start;
10579    let mut paren_depth = 0usize;
10580    let mut bracket_depth = 0usize;
10581    while index < tokens.len() {
10582        match tokens[index].kind {
10583            SyntaxKind::LeftParen => paren_depth += 1,
10584            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
10585            SyntaxKind::LeftBracket => bracket_depth += 1,
10586            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
10587            SyntaxKind::Semicolon
10588            | SyntaxKind::SassOptionalSemicolon
10589            | SyntaxKind::RightBrace
10590            | SyntaxKind::SassDedent
10591                if paren_depth == 0 && bracket_depth == 0 =>
10592            {
10593                break;
10594            }
10595            _ => {}
10596        }
10597
10598        if paren_depth == 0
10599            && bracket_depth == 0
10600            && animation_shorthand_token_can_be_name(tokens, index)
10601            && let Some(name) = animation_name_from_token(tokens[index])
10602        {
10603            push_animation_fact(
10604                animations,
10605                seen,
10606                ParsedAnimationFactKind::AnimationNameReference,
10607                name,
10608                tokens[index].range,
10609            );
10610        }
10611        index += 1;
10612    }
10613}
10614
10615fn animation_shorthand_token_can_be_name(tokens: &[Token<'_>], index: usize) -> bool {
10616    let token = tokens[index];
10617    if token.kind == SyntaxKind::String {
10618        return true;
10619    }
10620    if token.kind != SyntaxKind::Ident {
10621        return false;
10622    }
10623    // A literal fragment that is *immediately* adjacent to an interpolation boundary is part
10624    // of a statically-unknown name (`#{$dur}s` unit suffix, `#{$p}-spin` / `spin-#{$p}`
10625    // interpolated keyframes name), not a standalone animation name. Reject it so neither the
10626    // unit nor the literal fragment is misread as a missing `@keyframes` reference.
10627    if animation_name_token_is_interpolation_adjacent(tokens, index) {
10628        return false;
10629    }
10630    // Standalone CSS time-unit idents (`s` / `ms`) are durations, never animation names.
10631    if animation_shorthand_ident_is_time_unit(token.text) {
10632        return false;
10633    }
10634    if let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10635        && tokens[next_index].kind == SyntaxKind::LeftParen
10636    {
10637        return false;
10638    }
10639    !animation_shorthand_ident_is_non_name(token.text)
10640}
10641
10642fn animation_shorthand_ident_is_time_unit(name: &str) -> bool {
10643    name.eq_ignore_ascii_case("s") || name.eq_ignore_ascii_case("ms")
10644}
10645
10646/// An ident is part of an interpolated (statically-unknown) animation name when it is
10647/// *immediately* adjacent to an interpolation boundary — `#{$p}-spin` (post-interpolation
10648/// literal fragment) or `spin-#{$p}` (pre-interpolation literal fragment). The post-`#{...}`
10649/// text is the trailing fragment of a dynamic name, not a real keyframes reference, so it
10650/// must not be flagged as `missingKeyframes`.
10651///
10652/// Adjacency is checked against the *immediate* neighbor token (no trivia skipping): a
10653/// fully-static name separated from an interpolation by whitespace (`#{$p} spin`, a real
10654/// space-delimited keyframes reference) is NOT suppressed.
10655fn animation_name_token_is_interpolation_adjacent(tokens: &[Token<'_>], index: usize) -> bool {
10656    if index > 0
10657        && matches!(
10658            tokens[index - 1].kind,
10659            SyntaxKind::ScssInterpolationEnd | SyntaxKind::LessInterpolationEnd
10660        )
10661    {
10662        return true;
10663    }
10664    if let Some(next) = tokens.get(index + 1)
10665        && matches!(
10666            next.kind,
10667            SyntaxKind::ScssInterpolationStart | SyntaxKind::LessInterpolationStart
10668        )
10669    {
10670        return true;
10671    }
10672    false
10673}
10674
10675fn animation_shorthand_ident_is_non_name(name: &str) -> bool {
10676    matches!(
10677        name.to_ascii_lowercase().as_str(),
10678        "ease"
10679            | "ease-in"
10680            | "ease-out"
10681            | "ease-in-out"
10682            | "linear"
10683            | "step-start"
10684            | "step-end"
10685            | "infinite"
10686            | "normal"
10687            | "reverse"
10688            | "alternate"
10689            | "alternate-reverse"
10690            | "running"
10691            | "paused"
10692            | "forwards"
10693            | "backwards"
10694            | "both"
10695            | "replace"
10696            | "add"
10697            | "accumulate"
10698            | "auto"
10699    )
10700}
10701
10702fn push_animation_fact(
10703    animations: &mut Vec<ParsedAnimationFact>,
10704    seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10705    kind: ParsedAnimationFactKind,
10706    name: String,
10707    range: TextRange,
10708) {
10709    if seen.insert((
10710        kind,
10711        name.clone(),
10712        u32::from(range.start()),
10713        u32::from(range.end()),
10714    )) {
10715        animations.push(ParsedAnimationFact { kind, name, range });
10716    }
10717}
10718
10719fn animation_name_from_token(token: Token<'_>) -> Option<String> {
10720    if !matches!(token.kind, SyntaxKind::Ident | SyntaxKind::String) {
10721        return None;
10722    }
10723    let name = token
10724        .text
10725        .trim_matches(|character| character == '"' || character == '\'')
10726        .to_string();
10727    if name.is_empty() || animation_name_is_reserved(&name) {
10728        return None;
10729    }
10730    Some(name)
10731}
10732
10733fn animation_name_is_reserved(name: &str) -> bool {
10734    matches!(
10735        name.to_ascii_lowercase().as_str(),
10736        "none" | "initial" | "inherit" | "unset" | "revert" | "revert-layer"
10737    )
10738}
10739
10740/// Recognize an `@keyframes` at-rule prefix-insensitively.
10741///
10742/// Per CSS, `animation-name` resolves against any `@keyframes`/`@-webkit-keyframes`
10743/// (and other vendor prefixes) with a matching name, so a vendor-prefixed at-rule
10744/// must register the same bare keyframes-name fact as the unprefixed form. Strips a
10745/// leading `@`, then an optional `-vendor-` prefix, and compares the remainder to
10746/// `keyframes`.
10747fn at_keyword_is_keyframes_rule(text: &str) -> bool {
10748    let Some(rule) = text.strip_prefix('@') else {
10749        return false;
10750    };
10751    if rule.eq_ignore_ascii_case("keyframes") {
10752        return true;
10753    }
10754    // Accept a single `-vendor-` prefix (`-webkit-`, `-moz-`, `-o-`, `-ms-`, ...).
10755    if let Some(rest) = rule.strip_prefix('-')
10756        && let Some((vendor, remainder)) = rest.split_once('-')
10757        && !vendor.is_empty()
10758        && remainder.eq_ignore_ascii_case("keyframes")
10759    {
10760        return true;
10761    }
10762    false
10763}
10764
10765fn containing_at_rule_header_name<'text>(
10766    tokens: &'text [Token<'text>],
10767    index: usize,
10768) -> Option<&'text str> {
10769    let mut current = index;
10770    while current > 0 {
10771        current -= 1;
10772        let token = tokens.get(current)?;
10773        if token.kind.is_trivia() {
10774            continue;
10775        }
10776        if matches!(
10777            token.kind,
10778            SyntaxKind::Semicolon
10779                | SyntaxKind::SassOptionalSemicolon
10780                | SyntaxKind::LeftBrace
10781                | SyntaxKind::RightBrace
10782                | SyntaxKind::SassIndent
10783                | SyntaxKind::SassDedent
10784        ) {
10785            return None;
10786        }
10787        if token.kind == SyntaxKind::AtKeyword {
10788            return Some(token.text);
10789        }
10790    }
10791    None
10792}
10793
10794fn skip_trivia_tokens(tokens: &[Token<'_>], mut index: usize, end: usize) -> usize {
10795    while index < end && tokens[index].kind.is_trivia() {
10796        index += 1;
10797    }
10798    index
10799}
10800
10801fn skip_statement(tokens: &[Token<'_>], mut index: usize, end: usize) -> usize {
10802    while index < end {
10803        match tokens[index].kind {
10804            SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon => return index + 1,
10805            SyntaxKind::RightBrace | SyntaxKind::SassDedent => return index,
10806            _ => index += 1,
10807        }
10808    }
10809    index
10810}
10811
10812fn find_block_after_header(
10813    tokens: &[Token<'_>],
10814    start: usize,
10815    end: usize,
10816) -> Option<(usize, usize)> {
10817    let mut index = start;
10818    while index < end {
10819        match tokens[index].kind {
10820            SyntaxKind::Semicolon
10821            | SyntaxKind::SassOptionalSemicolon
10822            | SyntaxKind::RightBrace
10823            | SyntaxKind::SassDedent => return None,
10824            SyntaxKind::LeftBrace => {
10825                let close = matching_right_brace(tokens, index, end)?;
10826                return Some((index, close));
10827            }
10828            SyntaxKind::SassIndent => {
10829                let close = matching_sass_dedent(tokens, index, end)?;
10830                return Some((index, close));
10831            }
10832            _ => index += 1,
10833        }
10834    }
10835    None
10836}
10837
10838fn matching_right_brace(tokens: &[Token<'_>], open: usize, end: usize) -> Option<usize> {
10839    let mut depth = 0usize;
10840    let mut index = open;
10841    while index < end {
10842        match tokens[index].kind {
10843            SyntaxKind::LeftBrace => depth += 1,
10844            SyntaxKind::RightBrace => {
10845                depth = depth.saturating_sub(1);
10846                if depth == 0 {
10847                    return Some(index);
10848                }
10849            }
10850            _ => {}
10851        }
10852        index += 1;
10853    }
10854    None
10855}
10856
10857fn matching_sass_dedent(tokens: &[Token<'_>], open: usize, end: usize) -> Option<usize> {
10858    let mut depth = 0usize;
10859    let mut index = open;
10860    while index < end {
10861        match tokens[index].kind {
10862            SyntaxKind::SassIndent => depth += 1,
10863            SyntaxKind::SassDedent => {
10864                depth = depth.saturating_sub(1);
10865                if depth == 0 {
10866                    return Some(index);
10867                }
10868            }
10869            _ => {}
10870        }
10871        index += 1;
10872    }
10873    None
10874}
10875
10876fn style_wrapper_at_rule(name: &str) -> bool {
10877    matches_ignore_ascii_case(
10878        name,
10879        &[
10880            "@media",
10881            "@supports",
10882            "@when",
10883            "@else",
10884            "@layer",
10885            "@scope",
10886            "@container",
10887            "@starting-style",
10888            "@if",
10889            "@else",
10890            "@for",
10891            "@each",
10892            "@while",
10893            "@at-root",
10894            "@include",
10895        ],
10896    )
10897}
10898
10899fn is_selector_combinator_kind(kind: SyntaxKind) -> bool {
10900    matches!(
10901        kind,
10902        SyntaxKind::GreaterThan
10903            | SyntaxKind::Plus
10904            | SyntaxKind::Tilde
10905            | SyntaxKind::ColumnCombinator
10906            | SyntaxKind::DoublePipe
10907    )
10908}
10909
10910fn selector_component_can_start(kind: SyntaxKind) -> bool {
10911    matches!(
10912        kind,
10913        SyntaxKind::Dot
10914            | SyntaxKind::Hash
10915            | SyntaxKind::Ident
10916            | SyntaxKind::Star
10917            | SyntaxKind::Ampersand
10918            | SyntaxKind::ScssPlaceholder
10919            | SyntaxKind::LeftBracket
10920            | SyntaxKind::Colon
10921            | SyntaxKind::DoubleColon
10922    )
10923}
10924
10925fn namespace_selector_target_can_start(kind: SyntaxKind) -> bool {
10926    matches!(
10927        kind,
10928        SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star
10929    )
10930}
10931
10932fn keyframe_selector_token_is_valid(token: Token<'_>) -> bool {
10933    token.kind == SyntaxKind::Percentage
10934        || (token.kind == SyntaxKind::Ident
10935            && (token.text.eq_ignore_ascii_case("from") || token.text.eq_ignore_ascii_case("to")))
10936}
10937
10938fn selector_component_can_end(kind: SyntaxKind) -> bool {
10939    matches!(
10940        kind,
10941        SyntaxKind::Ident
10942            | SyntaxKind::CustomPropertyName
10943            | SyntaxKind::Hash
10944            | SyntaxKind::RightBracket
10945            | SyntaxKind::RightParen
10946            | SyntaxKind::Star
10947    )
10948}
10949
10950fn collect_at_rule_facts_from_tokens(
10951    tokens: &[Token<'_>],
10952    dialect: StyleDialect,
10953) -> Vec<ParsedAtRuleFact> {
10954    tokens
10955        .iter()
10956        .filter(|token| token.kind == SyntaxKind::AtKeyword)
10957        .map(|token| {
10958            let css_spec = at_rule_spec(token.text);
10959            let node_kind = css_spec
10960                .or_else(|| match dialect {
10961                    StyleDialect::Scss | StyleDialect::Sass => scss_at_rule_spec(token.text),
10962                    StyleDialect::Css | StyleDialect::Less => None,
10963                })
10964                .map(|spec| spec.node_kind);
10965            let name = if css_spec.is_some() {
10966                token.text.to_ascii_lowercase()
10967            } else {
10968                token.text.to_string()
10969            };
10970            ParsedAtRuleFact {
10971                name,
10972                node_kind,
10973                range: token.range,
10974            }
10975        })
10976        .collect()
10977}
10978
10979fn next_non_trivia_token<'text>(
10980    tokens: &'text [Token<'text>],
10981    mut index: usize,
10982) -> Option<Token<'text>> {
10983    while let Some(token) = tokens.get(index).copied() {
10984        if !token.kind.is_trivia() {
10985            return Some(token);
10986        }
10987        index += 1;
10988    }
10989    None
10990}
10991
10992fn next_non_trivia_token_until<'text>(
10993    tokens: &'text [Token<'text>],
10994    mut index: usize,
10995    end: usize,
10996) -> Option<Token<'text>> {
10997    while index < end {
10998        let token = tokens.get(index).copied()?;
10999        if !token.kind.is_trivia() {
11000            return Some(token);
11001        }
11002        index += 1;
11003    }
11004    None
11005}
11006
11007fn next_non_trivia_token_index_until(
11008    tokens: &[Token<'_>],
11009    mut index: usize,
11010    end: usize,
11011) -> Option<usize> {
11012    while index < end {
11013        let token = tokens.get(index)?;
11014        if !token.kind.is_trivia() {
11015            return Some(index);
11016        }
11017        index += 1;
11018    }
11019    None
11020}
11021
11022fn next_non_trivia_token_after_range<'text>(
11023    tokens: &'text [Token<'text>],
11024    range: TextRange,
11025    end: usize,
11026) -> Option<Token<'text>> {
11027    let index = token_index_by_range(tokens, range)?;
11028    next_non_trivia_token_until(tokens, index + 1, end)
11029}
11030
11031fn token_index_by_range(tokens: &[Token<'_>], range: TextRange) -> Option<usize> {
11032    tokens.iter().position(|token| token.range == range)
11033}
11034
11035fn matching_right_paren_from_range(
11036    tokens: &[Token<'_>],
11037    open_range: TextRange,
11038    end: usize,
11039) -> Option<usize> {
11040    let mut depth = 0usize;
11041    let mut index = token_index_by_range(tokens, open_range)?;
11042    while index < end {
11043        match tokens[index].kind {
11044            SyntaxKind::LeftParen => depth += 1,
11045            SyntaxKind::RightParen => {
11046                depth = depth.saturating_sub(1);
11047                if depth == 0 {
11048                    return Some(index);
11049                }
11050            }
11051            _ => {}
11052        }
11053        index += 1;
11054    }
11055    None
11056}
11057
11058fn previous_non_trivia_token<'text>(
11059    tokens: &'text [Token<'text>],
11060    start: usize,
11061    index: usize,
11062) -> Option<Token<'text>> {
11063    let mut current = index;
11064    while current > start {
11065        current -= 1;
11066        let token = tokens.get(current).copied()?;
11067        if !token.kind.is_trivia() {
11068            return Some(token);
11069        }
11070    }
11071    None
11072}
11073
11074fn at_rule_spec(text: &str) -> Option<AtRuleSpec> {
11075    let lowered = text.to_ascii_lowercase();
11076    let (node_kind, block_kind) = match lowered.as_str() {
11077        "@media" => (SyntaxKind::MediaRule, AtRuleBlockKind::GroupRuleList),
11078        "@supports" => (SyntaxKind::SupportsRule, AtRuleBlockKind::GroupRuleList),
11079        "@when" => (SyntaxKind::WhenRule, AtRuleBlockKind::GroupRuleList),
11080        "@else" => (SyntaxKind::ElseRule, AtRuleBlockKind::GroupRuleList),
11081        "@container" => (SyntaxKind::ContainerRule, AtRuleBlockKind::GroupRuleList),
11082        "@layer" => (SyntaxKind::LayerRule, AtRuleBlockKind::GroupRuleList),
11083        "@scope" => (SyntaxKind::ScopeRule, AtRuleBlockKind::GroupRuleList),
11084        "@starting-style" => (
11085            SyntaxKind::StartingStyleRule,
11086            AtRuleBlockKind::GroupRuleList,
11087        ),
11088        "@nest" => (SyntaxKind::NestRule, AtRuleBlockKind::DeclarationList),
11089        "@keyframes" => (SyntaxKind::KeyframesRule, AtRuleBlockKind::Keyframes),
11090        "@font-face" => (SyntaxKind::FontFaceRule, AtRuleBlockKind::DeclarationList),
11091        "@page" => (SyntaxKind::PageRule, AtRuleBlockKind::DeclarationList),
11092        "@property" => (SyntaxKind::PropertyRule, AtRuleBlockKind::DeclarationList),
11093        "@counter-style" => (
11094            SyntaxKind::CounterStyleRule,
11095            AtRuleBlockKind::DeclarationList,
11096        ),
11097        "@font-palette-values" => (
11098            SyntaxKind::FontPaletteValuesRule,
11099            AtRuleBlockKind::DeclarationList,
11100        ),
11101        "@color-profile" => (
11102            SyntaxKind::ColorProfileRule,
11103            AtRuleBlockKind::DeclarationList,
11104        ),
11105        "@position-try" => (
11106            SyntaxKind::PositionTryRule,
11107            AtRuleBlockKind::DeclarationList,
11108        ),
11109        "@font-feature-values" => (
11110            SyntaxKind::FontFeatureValuesRule,
11111            AtRuleBlockKind::GroupRuleList,
11112        ),
11113        "@stylistic" => (
11114            SyntaxKind::FontFeatureValuesStylisticRule,
11115            AtRuleBlockKind::DeclarationList,
11116        ),
11117        "@styleset" => (
11118            SyntaxKind::FontFeatureValuesStylesetRule,
11119            AtRuleBlockKind::DeclarationList,
11120        ),
11121        "@character-variant" => (
11122            SyntaxKind::FontFeatureValuesCharacterVariantRule,
11123            AtRuleBlockKind::DeclarationList,
11124        ),
11125        "@swash" => (
11126            SyntaxKind::FontFeatureValuesSwashRule,
11127            AtRuleBlockKind::DeclarationList,
11128        ),
11129        "@ornaments" => (
11130            SyntaxKind::FontFeatureValuesOrnamentsRule,
11131            AtRuleBlockKind::DeclarationList,
11132        ),
11133        "@annotation" => (
11134            SyntaxKind::FontFeatureValuesAnnotationRule,
11135            AtRuleBlockKind::DeclarationList,
11136        ),
11137        "@historical-forms" => (
11138            SyntaxKind::FontFeatureValuesHistoricalFormsRule,
11139            AtRuleBlockKind::DeclarationList,
11140        ),
11141        "@view-transition" => (
11142            SyntaxKind::ViewTransitionRule,
11143            AtRuleBlockKind::DeclarationList,
11144        ),
11145        "@charset" => (SyntaxKind::CharsetRule, AtRuleBlockKind::Raw),
11146        "@import" => (SyntaxKind::ImportRule, AtRuleBlockKind::Raw),
11147        "@namespace" => (SyntaxKind::NamespaceRule, AtRuleBlockKind::Raw),
11148        "@custom-media" => (SyntaxKind::CustomMediaRule, AtRuleBlockKind::Raw),
11149        text if is_page_margin_at_rule(text) => {
11150            (SyntaxKind::PageMarginRule, AtRuleBlockKind::DeclarationList)
11151        }
11152        _ => return None,
11153    };
11154    Some(AtRuleSpec {
11155        node_kind,
11156        block_kind,
11157    })
11158}
11159
11160fn is_page_margin_at_rule(text: &str) -> bool {
11161    matches!(
11162        text,
11163        "@top-left-corner"
11164            | "@top-left"
11165            | "@top-center"
11166            | "@top-right"
11167            | "@top-right-corner"
11168            | "@bottom-left-corner"
11169            | "@bottom-left"
11170            | "@bottom-center"
11171            | "@bottom-right"
11172            | "@bottom-right-corner"
11173            | "@left-top"
11174            | "@left-middle"
11175            | "@left-bottom"
11176            | "@right-top"
11177            | "@right-middle"
11178            | "@right-bottom"
11179    )
11180}
11181
11182fn scss_at_rule_spec(text: &str) -> Option<AtRuleSpec> {
11183    let (node_kind, block_kind) = match text {
11184        "@use" => (SyntaxKind::ScssUseRule, AtRuleBlockKind::Raw),
11185        "@forward" => (SyntaxKind::ScssForwardRule, AtRuleBlockKind::Raw),
11186        "@mixin" => (
11187            SyntaxKind::ScssMixinDeclaration,
11188            AtRuleBlockKind::DeclarationList,
11189        ),
11190        "@include" => (
11191            SyntaxKind::ScssIncludeRule,
11192            AtRuleBlockKind::DeclarationList,
11193        ),
11194        "@function" => (
11195            SyntaxKind::ScssFunctionDeclaration,
11196            AtRuleBlockKind::DeclarationList,
11197        ),
11198        "@return" => (SyntaxKind::ScssReturnRule, AtRuleBlockKind::Raw),
11199        "@extend" => (SyntaxKind::ScssExtendRule, AtRuleBlockKind::Raw),
11200        "@if" => (SyntaxKind::ScssControlIf, AtRuleBlockKind::DeclarationList),
11201        "@else" => (
11202            SyntaxKind::ScssControlElse,
11203            AtRuleBlockKind::DeclarationList,
11204        ),
11205        "@each" => (
11206            SyntaxKind::ScssControlEach,
11207            AtRuleBlockKind::DeclarationList,
11208        ),
11209        "@for" => (SyntaxKind::ScssControlFor, AtRuleBlockKind::DeclarationList),
11210        "@while" => (
11211            SyntaxKind::ScssControlWhile,
11212            AtRuleBlockKind::DeclarationList,
11213        ),
11214        "@at-root" => (SyntaxKind::ScssAtRootRule, AtRuleBlockKind::DeclarationList),
11215        "@error" => (SyntaxKind::ScssErrorRule, AtRuleBlockKind::Raw),
11216        "@warn" => (SyntaxKind::ScssWarnRule, AtRuleBlockKind::Raw),
11217        "@debug" => (SyntaxKind::ScssDebugRule, AtRuleBlockKind::Raw),
11218        "@content" => (SyntaxKind::ScssContentRule, AtRuleBlockKind::Raw),
11219        _ => return None,
11220    };
11221    Some(AtRuleSpec {
11222        node_kind,
11223        block_kind,
11224    })
11225}
11226
11227fn is_selector_boundary(kind: SyntaxKind) -> bool {
11228    matches!(
11229        kind,
11230        SyntaxKind::Comma
11231            | SyntaxKind::LeftBrace
11232            | SyntaxKind::SassIndent
11233            | SyntaxKind::RightBrace
11234            | SyntaxKind::SassDedent
11235            | SyntaxKind::Semicolon
11236            | SyntaxKind::SassOptionalSemicolon
11237    )
11238}
11239
11240fn is_selector_boundary_until(kind: SyntaxKind, recovery: &[SyntaxKind]) -> bool {
11241    is_selector_boundary(kind) || recovery.contains(&kind)
11242}
11243
11244fn is_selector_list_pseudo_class(text: &str) -> bool {
11245    matches!(text, "is" | "where" | "local" | "global")
11246}
11247
11248fn is_nth_pseudo_class(text: &str) -> bool {
11249    matches!(
11250        text,
11251        "nth-child" | "nth-last-child" | "nth-of-type" | "nth-last-of-type"
11252    )
11253}
11254
11255fn language_tag_token_can_start(kind: SyntaxKind) -> bool {
11256    matches!(kind, SyntaxKind::Ident | SyntaxKind::String)
11257}
11258
11259fn selector_item_token_is_recoverable(kind: SyntaxKind) -> bool {
11260    matches!(
11261        kind,
11262        SyntaxKind::Whitespace
11263            | SyntaxKind::SassIndentedNewline
11264            | SyntaxKind::Dot
11265            | SyntaxKind::Comma
11266            | SyntaxKind::Hash
11267            | SyntaxKind::Ident
11268            | SyntaxKind::CustomPropertyName
11269            | SyntaxKind::String
11270            | SyntaxKind::Number
11271            | SyntaxKind::Percentage
11272            | SyntaxKind::Dimension
11273            | SyntaxKind::Star
11274            | SyntaxKind::Ampersand
11275            | SyntaxKind::ScssPlaceholder
11276            | SyntaxKind::LeftBracket
11277            | SyntaxKind::RightBracket
11278            | SyntaxKind::Colon
11279            | SyntaxKind::DoubleColon
11280            | SyntaxKind::LeftParen
11281            | SyntaxKind::RightParen
11282            | SyntaxKind::Equals
11283            | SyntaxKind::IncludesMatch
11284            | SyntaxKind::DashMatch
11285            | SyntaxKind::PrefixMatch
11286            | SyntaxKind::SuffixMatch
11287            | SyntaxKind::SubstringMatch
11288            | SyntaxKind::Pipe
11289            | SyntaxKind::ColumnCombinator
11290            | SyntaxKind::GreaterThan
11291            | SyntaxKind::Plus
11292            | SyntaxKind::Minus
11293            | SyntaxKind::Tilde
11294            | SyntaxKind::KeywordAnd
11295            | SyntaxKind::KeywordOr
11296            | SyntaxKind::KeywordNot
11297    )
11298}
11299
11300fn is_at_rule_prelude_boundary(kind: SyntaxKind) -> bool {
11301    matches!(
11302        kind,
11303        SyntaxKind::LeftBrace
11304            | SyntaxKind::SassIndent
11305            | SyntaxKind::Semicolon
11306            | SyntaxKind::SassOptionalSemicolon
11307    )
11308}
11309
11310fn is_statement_end(kind: SyntaxKind) -> bool {
11311    matches!(
11312        kind,
11313        SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon
11314    )
11315}
11316
11317fn sass_token_can_end_statement(kind: SyntaxKind) -> bool {
11318    !matches!(
11319        kind,
11320        SyntaxKind::Whitespace
11321            | SyntaxKind::LineComment
11322            | SyntaxKind::BlockComment
11323            | SyntaxKind::SassIndentedNewline
11324            | SyntaxKind::SassIndent
11325            | SyntaxKind::SassDedent
11326            | SyntaxKind::SassOptionalSemicolon
11327            | SyntaxKind::Comma
11328            | SyntaxKind::Colon
11329            | SyntaxKind::DoubleColon
11330            | SyntaxKind::LeftBrace
11331            | SyntaxKind::LeftParen
11332            | SyntaxKind::LeftBracket
11333            | SyntaxKind::Plus
11334            | SyntaxKind::Minus
11335            | SyntaxKind::Star
11336            | SyntaxKind::Slash
11337            | SyntaxKind::GreaterThan
11338            | SyntaxKind::LessThan
11339            | SyntaxKind::Equals
11340            | SyntaxKind::Arrow
11341            | SyntaxKind::Pipe
11342            | SyntaxKind::Tilde
11343            | SyntaxKind::Caret
11344            | SyntaxKind::Ampersand
11345            | SyntaxKind::DoubleAmpersand
11346            | SyntaxKind::ColumnCombinator
11347            | SyntaxKind::IncludesMatch
11348            | SyntaxKind::DashMatch
11349            | SyntaxKind::PrefixMatch
11350            | SyntaxKind::SuffixMatch
11351            | SyntaxKind::SubstringMatch
11352            | SyntaxKind::PlusEquals
11353            | SyntaxKind::MinusEquals
11354            | SyntaxKind::SlashEquals
11355    )
11356}
11357
11358fn function_argument_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11359    let mut kinds = vec![SyntaxKind::RightParen];
11360    for kind in recovery {
11361        if !kinds.contains(kind) {
11362            kinds.push(*kind);
11363        }
11364    }
11365    kinds
11366}
11367
11368fn bracketed_value_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11369    let mut kinds = vec![SyntaxKind::RightBracket];
11370    for kind in recovery {
11371        if !kinds.contains(kind) {
11372            kinds.push(*kind);
11373        }
11374    }
11375    kinds
11376}
11377
11378fn simple_block_recovery(close_kind: SyntaxKind, recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11379    let mut kinds = vec![close_kind];
11380    for kind in recovery {
11381        if !kinds.contains(kind) {
11382            kinds.push(*kind);
11383        }
11384    }
11385    kinds
11386}
11387
11388fn matching_simple_block_close(open_kind: SyntaxKind) -> Option<SyntaxKind> {
11389    match open_kind {
11390        SyntaxKind::LeftBrace => Some(SyntaxKind::RightBrace),
11391        SyntaxKind::LeftBracket => Some(SyntaxKind::RightBracket),
11392        SyntaxKind::LeftParen => Some(SyntaxKind::RightParen),
11393        _ => None,
11394    }
11395}
11396
11397fn value_list_item_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11398    let mut kinds = vec![SyntaxKind::Comma];
11399    for kind in recovery {
11400        if !kinds.contains(kind) {
11401            kinds.push(*kind);
11402        }
11403    }
11404    kinds
11405}
11406
11407fn comma_separated_component_value_list_item_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11408    let mut kinds = vec![SyntaxKind::Comma];
11409    for kind in recovery {
11410        if !kinds.contains(kind) {
11411            kinds.push(*kind);
11412        }
11413    }
11414    kinds
11415}
11416
11417fn variable_declaration_node_kind(kind: SyntaxKind, has_colon: bool) -> SyntaxKind {
11418    if has_colon {
11419        return kind;
11420    }
11421    match kind {
11422        SyntaxKind::ScssVariableDeclaration => SyntaxKind::BogusScssVariable,
11423        SyntaxKind::LessVariableDeclaration => SyntaxKind::BogusLessVariable,
11424        _ => kind,
11425    }
11426}
11427
11428fn is_attribute_matcher(kind: SyntaxKind) -> bool {
11429    matches!(
11430        kind,
11431        SyntaxKind::Equals
11432            | SyntaxKind::IncludesMatch
11433            | SyntaxKind::DashMatch
11434            | SyntaxKind::PrefixMatch
11435            | SyntaxKind::SuffixMatch
11436            | SyntaxKind::SubstringMatch
11437    )
11438}
11439
11440fn attribute_name_token_can_start(kind: SyntaxKind) -> bool {
11441    matches!(
11442        kind,
11443        SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star
11444    )
11445}
11446
11447fn attribute_name_token_can_continue(kind: SyntaxKind) -> bool {
11448    matches!(
11449        kind,
11450        SyntaxKind::Ident
11451            | SyntaxKind::CustomPropertyName
11452            | SyntaxKind::Star
11453            | SyntaxKind::Pipe
11454            | SyntaxKind::ColumnCombinator
11455    )
11456}
11457
11458fn attribute_value_token_can_start(kind: SyntaxKind) -> bool {
11459    matches!(
11460        kind,
11461        SyntaxKind::Ident
11462            | SyntaxKind::CustomPropertyName
11463            | SyntaxKind::String
11464            | SyntaxKind::Hash
11465            | SyntaxKind::Number
11466            | SyntaxKind::Dimension
11467    )
11468}
11469
11470fn is_combinator(kind: SyntaxKind) -> bool {
11471    matches!(
11472        kind,
11473        SyntaxKind::GreaterThan
11474            | SyntaxKind::Plus
11475            | SyntaxKind::Tilde
11476            | SyntaxKind::ColumnCombinator
11477    )
11478}
11479
11480fn infix_binding_power(kind: SyntaxKind) -> Option<(u8, u8)> {
11481    match kind {
11482        SyntaxKind::Plus | SyntaxKind::Minus => Some((1, 2)),
11483        SyntaxKind::Star | SyntaxKind::Slash | SyntaxKind::Percent => Some((3, 4)),
11484        _ => None,
11485    }
11486}
11487
11488fn specialized_function_kind(text: &str) -> Option<SyntaxKind> {
11489    if text.eq_ignore_ascii_case("var") {
11490        return Some(SyntaxKind::VarFunction);
11491    }
11492    if text.eq_ignore_ascii_case("calc") {
11493        return Some(SyntaxKind::CalcFunction);
11494    }
11495    if text.eq_ignore_ascii_case("env") {
11496        return Some(SyntaxKind::EnvFunction);
11497    }
11498    if text.eq_ignore_ascii_case("attr") {
11499        return Some(SyntaxKind::AttrFunction);
11500    }
11501    if matches_ignore_ascii_case(text, VALUES_L4_MATH_FUNCTION_NAMES) {
11502        return Some(SyntaxKind::MathFunction);
11503    }
11504    if matches_ignore_ascii_case(text, CSS_COLOR_FUNCTION_NAMES) {
11505        return Some(SyntaxKind::ColorValue);
11506    }
11507    if matches_ignore_ascii_case(text, CSS_GRADIENT_FUNCTION_NAMES) {
11508        return Some(SyntaxKind::GradientFunction);
11509    }
11510    if matches_ignore_ascii_case(text, CSS_TRANSFORM_FUNCTION_NAMES) {
11511        return Some(SyntaxKind::TransformFunction);
11512    }
11513    if matches_ignore_ascii_case(text, CSS_FILTER_FUNCTION_NAMES) {
11514        return Some(SyntaxKind::FilterFunction);
11515    }
11516    if matches_ignore_ascii_case(text, CSS_IMAGE_FUNCTION_NAMES) {
11517        return Some(SyntaxKind::ImageFunction);
11518    }
11519    if matches_ignore_ascii_case(text, CSS_SHAPE_FUNCTION_NAMES) {
11520        return Some(SyntaxKind::ShapeFunction);
11521    }
11522    None
11523}
11524
11525fn function_argument_count_is_valid(function_name: &str, argument_count: usize) -> bool {
11526    if function_name.eq_ignore_ascii_case("calc") {
11527        return argument_count == 1;
11528    }
11529    if matches_ignore_ascii_case(function_name, &["min", "max", "hypot"]) {
11530        return argument_count >= 1;
11531    }
11532    if function_name.eq_ignore_ascii_case("clamp") {
11533        return argument_count == 3;
11534    }
11535    if function_name.eq_ignore_ascii_case("round") {
11536        return (2..=3).contains(&argument_count);
11537    }
11538    if function_name.eq_ignore_ascii_case("log") {
11539        return (1..=2).contains(&argument_count);
11540    }
11541    if matches_ignore_ascii_case(function_name, &["mod", "rem", "pow", "atan2"]) {
11542        return argument_count == 2;
11543    }
11544    if matches_ignore_ascii_case(
11545        function_name,
11546        &[
11547            "sin", "cos", "tan", "asin", "acos", "atan", "sqrt", "exp", "abs", "sign",
11548        ],
11549    ) {
11550        return argument_count == 1;
11551    }
11552    if function_name.eq_ignore_ascii_case("color-mix") {
11553        return argument_count == 3;
11554    }
11555    if function_name.eq_ignore_ascii_case("light-dark") {
11556        return argument_count == 2;
11557    }
11558    if function_name.eq_ignore_ascii_case("contrast-color") {
11559        return argument_count == 1;
11560    }
11561    true
11562}
11563
11564fn function_requires_filled_top_level_arguments(function_name: &str) -> bool {
11565    function_name.eq_ignore_ascii_case("calc")
11566        || matches_ignore_ascii_case(function_name, VALUES_L4_MATH_FUNCTION_NAMES)
11567        || matches_ignore_ascii_case(
11568            function_name,
11569            &["color-mix", "light-dark", "contrast-color"],
11570        )
11571}
11572
11573fn at_rule_prelude_head_is_custom_property_name(kind: SyntaxKind) -> bool {
11574    kind == SyntaxKind::CustomPropertyName || is_interpolation_start(kind)
11575}
11576
11577fn at_rule_prelude_head_is_custom_ident(kind: SyntaxKind) -> bool {
11578    kind == SyntaxKind::Ident || is_interpolation_start(kind)
11579}
11580
11581fn is_dynamic_function_argument_head(kind: SyntaxKind) -> bool {
11582    matches!(
11583        kind,
11584        SyntaxKind::ScssVariable
11585            | SyntaxKind::LessVariable
11586            | SyntaxKind::ScssInterpolationStart
11587            | SyntaxKind::LessInterpolationStart
11588    )
11589}
11590
11591fn is_scss_module_source_token(kind: SyntaxKind) -> bool {
11592    matches!(
11593        kind,
11594        SyntaxKind::String | SyntaxKind::Url | SyntaxKind::ScssInterpolationStart
11595    )
11596}
11597
11598fn is_scss_module_namespace_token(kind: SyntaxKind) -> bool {
11599    matches!(
11600        kind,
11601        SyntaxKind::Ident | SyntaxKind::Star | SyntaxKind::ScssInterpolationStart
11602    )
11603}
11604
11605fn is_scss_module_visibility_name_token(kind: SyntaxKind) -> bool {
11606    matches!(
11607        kind,
11608        SyntaxKind::Ident
11609            | SyntaxKind::ScssVariable
11610            | SyntaxKind::ScssPlaceholder
11611            | SyntaxKind::ScssInterpolationStart
11612    )
11613}
11614
11615fn is_css_module_from_source_token(kind: SyntaxKind, text: &str) -> bool {
11616    matches!(
11617        kind,
11618        SyntaxKind::String
11619            | SyntaxKind::Url
11620            | SyntaxKind::ScssInterpolationStart
11621            | SyntaxKind::LessInterpolationStart
11622    ) || (kind == SyntaxKind::Ident && text == "global")
11623}
11624
11625fn is_scss_control_rule_kind(kind: SyntaxKind) -> bool {
11626    matches!(
11627        kind,
11628        SyntaxKind::ScssControlIf
11629            | SyntaxKind::ScssControlElse
11630            | SyntaxKind::ScssControlEach
11631            | SyntaxKind::ScssControlFor
11632            | SyntaxKind::ScssControlWhile
11633    )
11634}
11635
11636fn matches_ignore_ascii_case(value: &str, candidates: &[&str]) -> bool {
11637    candidates
11638        .iter()
11639        .any(|candidate| value.eq_ignore_ascii_case(candidate))
11640}
11641
11642fn css_module_scope_function_kind(text: &str) -> Option<SyntaxKind> {
11643    match text {
11644        "local" => Some(SyntaxKind::CssModuleLocalBlock),
11645        "global" => Some(SyntaxKind::CssModuleGlobalBlock),
11646        _ => None,
11647    }
11648}
11649
11650fn text_range(start: usize, end: usize) -> TextRange {
11651    TextRange::new(TextSize::from(start as u32), TextSize::from(end as u32))
11652}
11653
11654#[cfg(test)]
11655mod tests;