Skip to main content

engine_style_parser/
lib.rs

1use serde::Serialize;
2use std::collections::{BTreeMap, BTreeSet};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum StyleLanguage {
6    Css,
7    Scss,
8    Less,
9}
10
11impl StyleLanguage {
12    pub fn from_module_path(path: &str) -> Option<Self> {
13        if path.ends_with(".module.css") {
14            Some(Self::Css)
15        } else if path.ends_with(".module.scss") {
16            Some(Self::Scss)
17        } else if path.ends_with(".module.less") {
18            Some(Self::Less)
19        } else {
20            None
21        }
22    }
23
24    fn supports_line_comments(self) -> bool {
25        matches!(self, Self::Scss | Self::Less)
26    }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct TextSpan {
31    pub start: usize,
32    pub end: usize,
33}
34
35impl TextSpan {
36    fn new(start: usize, end: usize) -> Self {
37        Self { start, end }
38    }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum TokenKind {
43    Whitespace,
44    Ident,
45    Number,
46    String,
47    LineComment,
48    BlockComment,
49    Dot,
50    Ampersand,
51    Hash,
52    Colon,
53    Semicolon,
54    Comma,
55    At,
56    OpenBrace,
57    CloseBrace,
58    OpenParen,
59    CloseParen,
60    OpenBracket,
61    CloseBracket,
62    InterpolationStart,
63    Other,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct Token {
68    pub kind: TokenKind,
69    pub span: TextSpan,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct ParseDiagnostic {
74    pub message: String,
75    pub span: TextSpan,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum SyntaxNodeKind {
80    Rule,
81    AtRule,
82    Declaration,
83    Comment,
84    Unknown,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct SyntaxNode {
89    pub kind: SyntaxNodeKind,
90    pub span: TextSpan,
91    pub header_span: Option<TextSpan>,
92    pub payload: Option<SyntaxNodePayload>,
93    pub children: Vec<SyntaxNode>,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub enum SyntaxNodePayload {
98    Rule(RulePayload),
99    AtRule(AtRulePayload),
100    Declaration(DeclarationPayload),
101    Comment(CommentPayload),
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct RulePayload {
106    pub prelude: String,
107    pub selector_groups: Vec<SelectorGroup>,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct SelectorGroup {
112    pub raw: String,
113    pub segments: Vec<SelectorSegment>,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub enum SelectorSegment {
118    ClassName(String),
119    Ampersand,
120    BemSuffix(String),
121    AmpersandSuffix(String),
122    Pseudo(String),
123    Combinator(String),
124    Other(String),
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct AtRulePayload {
129    pub kind: AtRuleKind,
130    pub name: String,
131    pub params: String,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub enum AtRuleKind {
136    Media,
137    Supports,
138    Layer,
139    Keyframes,
140    Value,
141    AtRoot,
142    Mixin,
143    Include,
144    Function,
145    Use,
146    Forward,
147    Import,
148    Generic,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct DeclarationPayload {
153    pub property: String,
154    pub value: String,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct CommentPayload {
159    pub text: String,
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct Stylesheet {
164    pub language: StyleLanguage,
165    pub source: String,
166    pub tokens: Vec<Token>,
167    pub nodes: Vec<SyntaxNode>,
168    pub diagnostics: Vec<ParseDiagnostic>,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
172#[serde(rename_all = "camelCase")]
173pub struct ParserParityLiteSummaryV0 {
174    pub schema_version: &'static str,
175    pub language: &'static str,
176    pub selector_names: Vec<String>,
177    pub keyframes_names: Vec<String>,
178    pub value_decl_names: Vec<String>,
179    pub diagnostic_count: usize,
180    pub rule_count: usize,
181    pub declaration_count: usize,
182    pub grouped_selector_count: usize,
183    pub max_nesting_depth: usize,
184    pub at_rule_kind_counts: AtRuleKindCountsV0,
185    pub declaration_kind_counts: DeclarationKindCountsV0,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
189#[serde(rename_all = "camelCase")]
190pub struct ParserIndexSummaryV0 {
191    pub schema_version: &'static str,
192    pub language: &'static str,
193    pub selectors: ParserIndexSelectorFactsV0,
194    pub values: ParserIndexValueFactsV0,
195    pub custom_properties: ParserIndexCustomPropertyFactsV0,
196    pub sass: ParserIndexSassFactsV0,
197    pub keyframes: ParserIndexKeyframesFactsV0,
198    pub composes: ParserIndexComposesFactsV0,
199    pub wrappers: ParserIndexWrapperFactsV0,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
203#[serde(rename_all = "camelCase")]
204pub struct ParserSemanticBoundarySummaryV0 {
205    pub schema_version: &'static str,
206    pub language: &'static str,
207    pub parser_facts: ParserBoundarySyntaxFactsV0,
208    pub semantic_facts: StyleSemanticFactsV0,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
212#[serde(rename_all = "camelCase")]
213pub struct ParserBoundarySyntaxFactsV0 {
214    pub lossless_cst: ParserLosslessCstFactsV0,
215    pub selectors: ParserIndexSelectorFactsV0,
216    pub values: ParserIndexValueFactsV0,
217    pub custom_properties: ParserIndexCustomPropertyFactsV0,
218    pub sass: ParserSassSyntaxFactsV0,
219    pub keyframes: ParserIndexKeyframesFactsV0,
220    pub composes: ParserIndexComposesFactsV0,
221    pub wrappers: ParserIndexWrapperFactsV0,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
225#[serde(rename_all = "camelCase")]
226pub struct ParserLosslessCstFactsV0 {
227    pub source_byte_len: usize,
228    pub token_count: usize,
229    pub root_node_count: usize,
230    pub diagnostic_count: usize,
231    pub all_token_spans_within_source: bool,
232    pub all_node_spans_within_source: bool,
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
236#[serde(rename_all = "camelCase")]
237pub struct ParserSassSyntaxFactsV0 {
238    pub variable_decl_names: Vec<String>,
239    pub variable_parameter_names: Vec<String>,
240    pub variable_ref_names: Vec<String>,
241    pub mixin_decl_names: Vec<String>,
242    pub mixin_include_names: Vec<String>,
243    pub function_decl_names: Vec<String>,
244    pub function_call_names: Vec<String>,
245    pub module_use_sources: Vec<String>,
246    pub module_use_edges: Vec<ParserIndexSassModuleUseFactV0>,
247    pub module_forward_sources: Vec<String>,
248    pub module_import_sources: Vec<String>,
249}
250
251#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
252#[serde(rename_all = "camelCase")]
253pub struct StyleSemanticFactsV0 {
254    pub selector_identity: StyleSelectorIdentityFactsV0,
255    pub custom_properties: StyleCustomPropertySemanticFactsV0,
256    pub sass: StyleSassSemanticFactsV0,
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
260#[serde(rename_all = "camelCase")]
261pub struct StyleSelectorIdentityFactsV0 {
262    pub canonical_names: Vec<String>,
263    pub bem_suffix_safe_names: Vec<String>,
264    pub bem_suffix_parent_names: Vec<String>,
265    pub nested_unsafe_names: Vec<String>,
266    pub nested_safety_counts: NestedSafetyCountsV0,
267}
268
269#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
270#[serde(rename_all = "camelCase")]
271pub struct StyleCustomPropertySemanticFactsV0 {
272    pub decl_names: Vec<String>,
273    pub ref_names: Vec<String>,
274    pub resolved_ref_names: Vec<String>,
275    pub unresolved_ref_names: Vec<String>,
276    pub selectors_with_refs_names: Vec<String>,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
280#[serde(rename_all = "camelCase")]
281pub struct StyleSassSemanticFactsV0 {
282    pub selector_symbol_facts: Vec<ParserIndexSassSelectorSymbolFactV0>,
283    pub selectors_with_resolved_variable_refs_names: Vec<String>,
284    pub selectors_with_unresolved_variable_refs_names: Vec<String>,
285    pub selectors_with_resolved_mixin_includes_names: Vec<String>,
286    pub selectors_with_unresolved_mixin_includes_names: Vec<String>,
287    pub selectors_with_function_calls_names: Vec<String>,
288    pub same_file_resolution: ParserIndexSassSameFileResolutionFactsV0,
289}
290
291#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
292#[serde(rename_all = "camelCase")]
293pub struct ParserCanonicalCandidateBundleV0 {
294    pub schema_version: &'static str,
295    pub language: &'static str,
296    pub parity_lite: ParserParityLiteSummaryV0,
297    pub css_modules_intermediate: ParserIndexSummaryV0,
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
301#[serde(rename_all = "camelCase")]
302pub struct ParserEvaluatorCandidateV0 {
303    pub kind: &'static str,
304    pub selector_name: String,
305    pub nested_safety_kind: &'static str,
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub bem_suffix_parent_name: Option<String>,
308    pub under_media: bool,
309    pub under_supports: bool,
310    pub under_layer: bool,
311    pub has_value_refs: bool,
312    pub has_local_value_refs: bool,
313    pub has_imported_value_refs: bool,
314    pub has_custom_property_refs: bool,
315    pub has_animation_ref: bool,
316    pub has_animation_name_ref: bool,
317    pub has_composes: bool,
318    pub has_local_composes: bool,
319    pub has_imported_composes: bool,
320    pub has_global_composes: bool,
321}
322
323#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
324#[serde(rename_all = "camelCase")]
325pub struct ParserEvaluatorCandidatesV0 {
326    pub schema_version: &'static str,
327    pub language: &'static str,
328    pub results: Vec<ParserEvaluatorCandidateV0>,
329}
330
331#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
332#[serde(rename_all = "camelCase")]
333pub struct ParserCanonicalProducerSignalV0 {
334    pub schema_version: &'static str,
335    pub language: &'static str,
336    pub canonical_candidate: ParserCanonicalCandidateBundleV0,
337    pub evaluator_candidates: ParserEvaluatorCandidatesV0,
338    pub public_product_gate: ParserPublicProductGateSignalV0,
339}
340
341#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
342#[serde(rename_all = "camelCase")]
343pub struct ParserPublicProductGateSignalV0 {
344    pub canonical_candidate_command: &'static str,
345    pub consumer_boundary_command: &'static str,
346    pub public_product_gate_command: &'static str,
347    pub included_in_parser_lane: bool,
348    pub included_in_rust_lane_bundle: bool,
349    pub included_in_rust_release_bundle: bool,
350}
351
352#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
353#[serde(rename_all = "camelCase")]
354pub struct ParserIndexSelectorFactsV0 {
355    pub names: Vec<String>,
356    pub bem_suffix_parent_names: Vec<String>,
357    pub bem_suffix_safe_names: Vec<String>,
358    pub nested_unsafe_names: Vec<String>,
359    pub selectors_with_value_refs_names: Vec<String>,
360    pub selectors_with_animation_ref_names: Vec<String>,
361    pub selectors_with_animation_name_ref_names: Vec<String>,
362    pub bem_suffix_count: usize,
363    pub nested_safety_counts: NestedSafetyCountsV0,
364}
365
366#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
367#[serde(rename_all = "camelCase")]
368pub struct ParserIndexValueFactsV0 {
369    pub decl_names: Vec<String>,
370    pub decl_names_with_local_refs: Vec<String>,
371    pub decl_names_with_imported_refs: Vec<String>,
372    pub import_names: Vec<String>,
373    pub import_sources: Vec<String>,
374    pub import_alias_count: usize,
375    pub ref_names: Vec<String>,
376    pub local_ref_names: Vec<String>,
377    pub imported_ref_names: Vec<String>,
378    pub imported_ref_sources: Vec<String>,
379    pub declaration_ref_names: Vec<String>,
380    pub declaration_imported_ref_sources: Vec<String>,
381    pub value_decl_ref_names: Vec<String>,
382    pub value_decl_imported_ref_sources: Vec<String>,
383    pub selectors_with_refs_names: Vec<String>,
384    pub selectors_with_local_refs_names: Vec<String>,
385    pub selectors_with_imported_refs_names: Vec<String>,
386    pub selectors_with_refs_under_media_names: Vec<String>,
387    pub selectors_with_refs_under_supports_names: Vec<String>,
388    pub selectors_with_refs_under_layer_names: Vec<String>,
389    pub selectors_with_local_refs_under_media_names: Vec<String>,
390    pub selectors_with_local_refs_under_supports_names: Vec<String>,
391    pub selectors_with_local_refs_under_layer_names: Vec<String>,
392    pub selectors_with_imported_refs_under_media_names: Vec<String>,
393    pub selectors_with_imported_refs_under_supports_names: Vec<String>,
394    pub selectors_with_imported_refs_under_layer_names: Vec<String>,
395}
396
397#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
398#[serde(rename_all = "camelCase")]
399pub struct ParserIndexCustomPropertyFactsV0 {
400    pub decl_names: Vec<String>,
401    pub ref_names: Vec<String>,
402    pub selectors_with_refs_names: Vec<String>,
403    pub selectors_with_refs_under_media_names: Vec<String>,
404    pub selectors_with_refs_under_supports_names: Vec<String>,
405    pub selectors_with_refs_under_layer_names: Vec<String>,
406}
407
408#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
409#[serde(rename_all = "camelCase")]
410pub struct ParserIndexSassFactsV0 {
411    pub variable_decl_names: Vec<String>,
412    pub variable_parameter_names: Vec<String>,
413    pub variable_ref_names: Vec<String>,
414    pub selectors_with_variable_refs_names: Vec<String>,
415    pub selectors_with_resolved_variable_refs_names: Vec<String>,
416    pub selectors_with_unresolved_variable_refs_names: Vec<String>,
417    pub mixin_decl_names: Vec<String>,
418    pub mixin_include_names: Vec<String>,
419    pub selectors_with_mixin_includes_names: Vec<String>,
420    pub selectors_with_resolved_mixin_includes_names: Vec<String>,
421    pub selectors_with_unresolved_mixin_includes_names: Vec<String>,
422    pub function_decl_names: Vec<String>,
423    pub function_call_names: Vec<String>,
424    pub selectors_with_function_calls_names: Vec<String>,
425    pub selector_symbol_facts: Vec<ParserIndexSassSelectorSymbolFactV0>,
426    pub module_use_sources: Vec<String>,
427    pub module_use_edges: Vec<ParserIndexSassModuleUseFactV0>,
428    pub module_forward_sources: Vec<String>,
429    pub module_import_sources: Vec<String>,
430    pub same_file_resolution: ParserIndexSassSameFileResolutionFactsV0,
431}
432
433#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
434#[serde(rename_all = "camelCase")]
435pub struct ParserIndexSassModuleUseFactV0 {
436    pub source: String,
437    pub namespace_kind: &'static str,
438    pub namespace: Option<String>,
439}
440
441#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
442#[serde(rename_all = "camelCase")]
443pub struct ParserIndexSassSameFileResolutionFactsV0 {
444    pub resolved_variable_ref_names: Vec<String>,
445    pub unresolved_variable_ref_names: Vec<String>,
446    pub resolved_mixin_include_names: Vec<String>,
447    pub unresolved_mixin_include_names: Vec<String>,
448    pub resolved_function_call_names: Vec<String>,
449}
450
451#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
452#[serde(rename_all = "camelCase")]
453pub struct ParserByteSpanV0 {
454    pub start: usize,
455    pub end: usize,
456}
457
458impl From<TextSpan> for ParserByteSpanV0 {
459    fn from(span: TextSpan) -> Self {
460        Self {
461            start: span.start,
462            end: span.end,
463        }
464    }
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
468#[serde(rename_all = "camelCase")]
469pub struct ParserPositionV0 {
470    pub line: usize,
471    pub character: usize,
472}
473
474#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
475#[serde(rename_all = "camelCase")]
476pub struct ParserRangeV0 {
477    pub start: ParserPositionV0,
478    pub end: ParserPositionV0,
479}
480
481#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
482#[serde(rename_all = "camelCase")]
483pub struct ParserIndexSassSelectorSymbolFactV0 {
484    pub selector_name: String,
485    pub symbol_kind: &'static str,
486    pub name: String,
487    pub role: &'static str,
488    pub resolution: &'static str,
489    pub byte_span: ParserByteSpanV0,
490    pub range: ParserRangeV0,
491}
492
493#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
494#[serde(rename_all = "camelCase")]
495pub struct ParserIndexKeyframesFactsV0 {
496    pub names: Vec<String>,
497    pub names_under_media: Vec<String>,
498    pub names_under_supports: Vec<String>,
499    pub names_under_layer: Vec<String>,
500    pub animation_ref_names: Vec<String>,
501    pub animation_name_ref_names: Vec<String>,
502    pub selectors_with_animation_ref_names: Vec<String>,
503    pub selectors_with_animation_name_ref_names: Vec<String>,
504    pub selectors_with_animation_refs_under_media_names: Vec<String>,
505    pub selectors_with_animation_refs_under_supports_names: Vec<String>,
506    pub selectors_with_animation_refs_under_layer_names: Vec<String>,
507    pub selectors_with_animation_name_refs_under_media_names: Vec<String>,
508    pub selectors_with_animation_name_refs_under_supports_names: Vec<String>,
509    pub selectors_with_animation_name_refs_under_layer_names: Vec<String>,
510}
511
512#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
513#[serde(rename_all = "camelCase")]
514pub struct ParserIndexComposesFactsV0 {
515    pub selectors_with_composes_names: Vec<String>,
516    pub selectors_with_composes_under_media_names: Vec<String>,
517    pub selectors_with_composes_under_supports_names: Vec<String>,
518    pub selectors_with_composes_under_layer_names: Vec<String>,
519    pub local_selector_names: Vec<String>,
520    pub imported_selector_names: Vec<String>,
521    pub global_selector_names: Vec<String>,
522    pub local_selector_names_under_media: Vec<String>,
523    pub local_selector_names_under_supports: Vec<String>,
524    pub local_selector_names_under_layer: Vec<String>,
525    pub imported_selector_names_under_media: Vec<String>,
526    pub imported_selector_names_under_supports: Vec<String>,
527    pub imported_selector_names_under_layer: Vec<String>,
528    pub global_selector_names_under_media: Vec<String>,
529    pub global_selector_names_under_supports: Vec<String>,
530    pub global_selector_names_under_layer: Vec<String>,
531    pub import_sources: Vec<String>,
532    pub import_sources_under_media: Vec<String>,
533    pub import_sources_under_supports: Vec<String>,
534    pub import_sources_under_layer: Vec<String>,
535    pub class_name_count: usize,
536    pub local_class_name_count: usize,
537    pub imported_class_name_count: usize,
538    pub global_class_name_count: usize,
539}
540
541#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
542#[serde(rename_all = "camelCase")]
543pub struct ParserIndexWrapperFactsV0 {
544    pub selectors_under_media_names: Vec<String>,
545    pub selectors_under_supports_names: Vec<String>,
546    pub selectors_under_layer_names: Vec<String>,
547}
548
549#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
550#[serde(rename_all = "camelCase")]
551pub struct AtRuleKindCountsV0 {
552    pub media: usize,
553    pub supports: usize,
554    pub layer: usize,
555    pub keyframes: usize,
556    pub value: usize,
557    pub at_root: usize,
558    pub generic: usize,
559}
560
561#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
562#[serde(rename_all = "camelCase")]
563pub struct DeclarationKindCountsV0 {
564    pub composes: usize,
565    pub animation: usize,
566    pub animation_name: usize,
567    pub generic: usize,
568}
569
570#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
571#[serde(rename_all = "camelCase")]
572pub struct NestedSafetyCountsV0 {
573    pub flat: usize,
574    pub bem_suffix_safe: usize,
575    pub nested_unsafe: usize,
576}
577
578#[derive(Debug, Default)]
579struct ParityLiteAcc {
580    selector_names: Vec<String>,
581    keyframes_names: Vec<String>,
582    value_decl_names: Vec<String>,
583    rule_count: usize,
584    declaration_count: usize,
585    grouped_selector_count: usize,
586    max_nesting_depth: usize,
587    at_rule_kind_counts: AtRuleKindCountsV0,
588    declaration_kind_counts: DeclarationKindCountsV0,
589}
590
591#[derive(Debug, Default)]
592struct IndexSummaryAcc {
593    selector_names: Vec<String>,
594    bem_suffix_parent_names: Vec<String>,
595    bem_suffix_safe_selector_names: Vec<String>,
596    selectors_with_composes_names: Vec<String>,
597    selectors_with_composes_under_media_names: Vec<String>,
598    selectors_with_composes_under_supports_names: Vec<String>,
599    selectors_with_composes_under_layer_names: Vec<String>,
600    local_composes_selector_names: Vec<String>,
601    imported_composes_selector_names: Vec<String>,
602    global_composes_selector_names: Vec<String>,
603    local_composes_selector_names_under_media: Vec<String>,
604    local_composes_selector_names_under_supports: Vec<String>,
605    local_composes_selector_names_under_layer: Vec<String>,
606    imported_composes_selector_names_under_media: Vec<String>,
607    imported_composes_selector_names_under_supports: Vec<String>,
608    imported_composes_selector_names_under_layer: Vec<String>,
609    global_composes_selector_names_under_media: Vec<String>,
610    global_composes_selector_names_under_supports: Vec<String>,
611    global_composes_selector_names_under_layer: Vec<String>,
612    composes_import_sources: Vec<String>,
613    composes_import_sources_under_media: Vec<String>,
614    composes_import_sources_under_supports: Vec<String>,
615    composes_import_sources_under_layer: Vec<String>,
616    keyframes_names: Vec<String>,
617    nested_unsafe_selector_names: Vec<String>,
618    value_decl_names: Vec<String>,
619    value_decl_names_with_local_refs: Vec<String>,
620    value_decl_names_with_imported_refs: Vec<String>,
621    value_import_names: Vec<String>,
622    value_import_sources: Vec<String>,
623    value_import_source_by_name: BTreeMap<String, String>,
624    value_ref_names: Vec<String>,
625    local_value_ref_names: Vec<String>,
626    imported_value_ref_names: Vec<String>,
627    imported_value_ref_sources: Vec<String>,
628    declaration_value_ref_names: Vec<String>,
629    declaration_imported_value_ref_sources: Vec<String>,
630    value_decl_ref_names: Vec<String>,
631    value_decl_imported_value_ref_sources: Vec<String>,
632    custom_property_decl_names: Vec<String>,
633    custom_property_ref_names: Vec<String>,
634    selectors_with_custom_property_refs_names: Vec<String>,
635    selectors_with_custom_property_refs_under_media_names: Vec<String>,
636    selectors_with_custom_property_refs_under_supports_names: Vec<String>,
637    selectors_with_custom_property_refs_under_layer_names: Vec<String>,
638    sass_variable_decl_names: Vec<String>,
639    sass_variable_decl_facts: Vec<SassVariableDeclFact>,
640    sass_variable_parameter_names: Vec<String>,
641    sass_variable_ref_names: Vec<String>,
642    sass_variable_ref_facts: Vec<SassVariableRefFact>,
643    sass_selectors_with_variable_refs_names: Vec<String>,
644    sass_selectors_with_resolved_variable_refs_names: Vec<String>,
645    sass_selectors_with_unresolved_variable_refs_names: Vec<String>,
646    sass_mixin_decl_names: Vec<String>,
647    sass_mixin_include_names: Vec<String>,
648    sass_selectors_with_mixin_includes_names: Vec<String>,
649    sass_selectors_with_resolved_mixin_includes_names: Vec<String>,
650    sass_selectors_with_unresolved_mixin_includes_names: Vec<String>,
651    sass_function_decl_names: Vec<String>,
652    sass_function_call_names: Vec<String>,
653    sass_selectors_with_function_calls_names: Vec<String>,
654    sass_selector_symbol_facts: Vec<ParserIndexSassSelectorSymbolFactV0>,
655    sass_module_use_sources: Vec<String>,
656    sass_module_use_edges: Vec<ParserIndexSassModuleUseFactV0>,
657    sass_module_forward_sources: Vec<String>,
658    sass_module_import_sources: Vec<String>,
659    selectors_with_value_refs_names: Vec<String>,
660    selectors_with_local_value_refs_names: Vec<String>,
661    selectors_with_imported_value_refs_names: Vec<String>,
662    selectors_with_value_refs_under_media_names: Vec<String>,
663    selectors_with_value_refs_under_supports_names: Vec<String>,
664    selectors_with_value_refs_under_layer_names: Vec<String>,
665    selectors_with_local_value_refs_under_media_names: Vec<String>,
666    selectors_with_local_value_refs_under_supports_names: Vec<String>,
667    selectors_with_local_value_refs_under_layer_names: Vec<String>,
668    selectors_with_imported_value_refs_under_media_names: Vec<String>,
669    selectors_with_imported_value_refs_under_supports_names: Vec<String>,
670    selectors_with_imported_value_refs_under_layer_names: Vec<String>,
671    selectors_with_animation_ref_names: Vec<String>,
672    selectors_with_animation_refs_under_media_names: Vec<String>,
673    selectors_with_animation_refs_under_supports_names: Vec<String>,
674    selectors_with_animation_refs_under_layer_names: Vec<String>,
675    selectors_with_animation_name_ref_names: Vec<String>,
676    selectors_with_animation_name_refs_under_media_names: Vec<String>,
677    selectors_with_animation_name_refs_under_supports_names: Vec<String>,
678    selectors_with_animation_name_refs_under_layer_names: Vec<String>,
679    selectors_under_media_names: Vec<String>,
680    selectors_under_supports_names: Vec<String>,
681    selectors_under_layer_names: Vec<String>,
682    animation_ref_names: Vec<String>,
683    animation_name_ref_names: Vec<String>,
684    keyframes_names_under_media: Vec<String>,
685    keyframes_names_under_supports: Vec<String>,
686    keyframes_names_under_layer: Vec<String>,
687    value_import_alias_count: usize,
688    composes_class_name_count: usize,
689    local_composes_class_name_count: usize,
690    imported_composes_class_name_count: usize,
691    global_composes_class_name_count: usize,
692    bem_suffix_count: usize,
693    nested_safety_counts: NestedSafetyCountsV0,
694}
695
696#[derive(Debug, Clone, PartialEq, Eq)]
697struct ResolvedSelectorBranch {
698    name: String,
699    bare_suffix_base: bool,
700}
701
702pub fn parse_style_module(path: &str, source: &str) -> Option<Stylesheet> {
703    let language = StyleLanguage::from_module_path(path)?;
704    Some(parse_stylesheet(language, source))
705}
706
707pub fn parse_stylesheet(language: StyleLanguage, source: &str) -> Stylesheet {
708    let (tokens, mut diagnostics) = tokenize(language, source);
709    let mut parser = Parser::new(source, &tokens, &mut diagnostics);
710    let nodes = parser.parse_root();
711    Stylesheet {
712        language,
713        source: source.to_string(),
714        tokens,
715        nodes,
716        diagnostics,
717    }
718}
719
720pub fn summarize_parity_lite(sheet: &Stylesheet) -> ParserParityLiteSummaryV0 {
721    let mut acc = ParityLiteAcc::default();
722    collect_parity_names(&sheet.nodes, &mut acc);
723    acc.selector_names.sort();
724    acc.keyframes_names.sort();
725    acc.keyframes_names.dedup();
726    acc.value_decl_names.sort();
727    acc.value_decl_names.dedup();
728
729    ParserParityLiteSummaryV0 {
730        schema_version: "0",
731        language: match sheet.language {
732            StyleLanguage::Css => "css",
733            StyleLanguage::Scss => "scss",
734            StyleLanguage::Less => "less",
735        },
736        selector_names: acc.selector_names,
737        keyframes_names: acc.keyframes_names,
738        value_decl_names: acc.value_decl_names,
739        diagnostic_count: sheet.diagnostics.len(),
740        rule_count: acc.rule_count,
741        declaration_count: acc.declaration_count,
742        grouped_selector_count: acc.grouped_selector_count,
743        max_nesting_depth: acc.max_nesting_depth,
744        at_rule_kind_counts: acc.at_rule_kind_counts,
745        declaration_kind_counts: acc.declaration_kind_counts,
746    }
747}
748
749pub fn summarize_css_modules_intermediate(sheet: &Stylesheet) -> ParserIndexSummaryV0 {
750    let mut acc = IndexSummaryAcc::default();
751    collect_index_names(&sheet.nodes, &mut acc, &[], false, None);
752    let local_value_names: BTreeSet<String> = acc.value_decl_names.iter().cloned().collect();
753    let imported_value_names: BTreeSet<String> = acc.value_import_names.iter().cloned().collect();
754    let known_value_names: BTreeSet<String> = acc
755        .value_decl_names
756        .iter()
757        .chain(acc.value_import_names.iter())
758        .cloned()
759        .collect();
760    let value_ref_ctx = ValueRefContext {
761        known: &known_value_names,
762        local: &local_value_names,
763        imported: &imported_value_names,
764    };
765    let known_keyframe_names: BTreeSet<String> = acc.keyframes_names.iter().cloned().collect();
766    collect_index_refs_and_counts(&sheet.nodes, value_ref_ctx, &known_keyframe_names, &mut acc);
767    let known_sass_function_names: BTreeSet<String> =
768        acc.sass_function_decl_names.iter().cloned().collect();
769    collect_sass_ref_facts(
770        &sheet.nodes,
771        &sheet.source,
772        &known_sass_function_names,
773        &mut acc,
774    );
775    let sass_variable_decl_facts = acc.sass_variable_decl_facts.clone();
776    let sass_mixin_targets: BTreeSet<String> = acc.sass_mixin_decl_names.iter().cloned().collect();
777    let sass_ref_ctx = SassRefContext {
778        variable_decls: &sass_variable_decl_facts,
779        mixin_targets: &sass_mixin_targets,
780        function_targets: &known_sass_function_names,
781    };
782    let selector_attachment_ctx = SelectorAttachmentContext {
783        source: &sheet.source,
784        value_ref_ctx,
785        known_keyframe_names: &known_keyframe_names,
786        sass_ref_ctx,
787    };
788    collect_index_selector_attachment_facts(&sheet.nodes, selector_attachment_ctx, &mut acc, &[]);
789
790    acc.selector_names.sort();
791    acc.bem_suffix_parent_names.sort();
792    acc.bem_suffix_safe_selector_names.sort();
793    acc.selectors_with_composes_names.sort();
794    acc.selectors_with_composes_under_media_names.sort();
795    acc.selectors_with_composes_under_supports_names.sort();
796    acc.selectors_with_composes_under_layer_names.sort();
797    acc.local_composes_selector_names.sort();
798    acc.imported_composes_selector_names.sort();
799    acc.global_composes_selector_names.sort();
800    acc.local_composes_selector_names_under_media.sort();
801    acc.local_composes_selector_names_under_supports.sort();
802    acc.local_composes_selector_names_under_layer.sort();
803    acc.imported_composes_selector_names_under_media.sort();
804    acc.imported_composes_selector_names_under_supports.sort();
805    acc.imported_composes_selector_names_under_layer.sort();
806    acc.global_composes_selector_names_under_media.sort();
807    acc.global_composes_selector_names_under_supports.sort();
808    acc.global_composes_selector_names_under_layer.sort();
809    acc.composes_import_sources.sort();
810    acc.composes_import_sources_under_media.sort();
811    acc.composes_import_sources_under_supports.sort();
812    acc.composes_import_sources_under_layer.sort();
813    acc.keyframes_names.sort();
814    acc.keyframes_names.dedup();
815    acc.nested_unsafe_selector_names.sort();
816    acc.value_decl_names.sort();
817    acc.value_decl_names.dedup();
818    acc.value_decl_names_with_local_refs.sort();
819    acc.value_decl_names_with_local_refs.dedup();
820    acc.value_decl_names_with_imported_refs.sort();
821    acc.value_decl_names_with_imported_refs.dedup();
822    acc.value_import_names.sort();
823    acc.value_import_names.dedup();
824    acc.value_import_sources.sort();
825    acc.value_ref_names.sort();
826    acc.value_ref_names.dedup();
827    acc.local_value_ref_names.sort();
828    acc.local_value_ref_names.dedup();
829    acc.imported_value_ref_names.sort();
830    acc.imported_value_ref_names.dedup();
831    acc.imported_value_ref_sources.sort();
832    acc.declaration_value_ref_names.sort();
833    acc.declaration_value_ref_names.dedup();
834    acc.declaration_imported_value_ref_sources.sort();
835    acc.value_decl_ref_names.sort();
836    acc.value_decl_ref_names.dedup();
837    acc.value_decl_imported_value_ref_sources.sort();
838    acc.custom_property_decl_names.sort();
839    acc.custom_property_decl_names.dedup();
840    acc.custom_property_ref_names.sort();
841    acc.custom_property_ref_names.dedup();
842    acc.selectors_with_custom_property_refs_names.sort();
843    acc.selectors_with_custom_property_refs_names.dedup();
844    acc.selectors_with_custom_property_refs_under_media_names
845        .sort();
846    acc.selectors_with_custom_property_refs_under_media_names
847        .dedup();
848    acc.selectors_with_custom_property_refs_under_supports_names
849        .sort();
850    acc.selectors_with_custom_property_refs_under_supports_names
851        .dedup();
852    acc.selectors_with_custom_property_refs_under_layer_names
853        .sort();
854    acc.selectors_with_custom_property_refs_under_layer_names
855        .dedup();
856    acc.sass_variable_decl_names.sort();
857    acc.sass_variable_decl_names.dedup();
858    acc.sass_variable_parameter_names.sort();
859    acc.sass_variable_parameter_names.dedup();
860    acc.sass_variable_ref_names.sort();
861    acc.sass_variable_ref_names.dedup();
862    acc.sass_selectors_with_variable_refs_names.sort();
863    acc.sass_selectors_with_variable_refs_names.dedup();
864    acc.sass_selectors_with_resolved_variable_refs_names.sort();
865    acc.sass_selectors_with_resolved_variable_refs_names.dedup();
866    acc.sass_selectors_with_unresolved_variable_refs_names
867        .sort();
868    acc.sass_selectors_with_unresolved_variable_refs_names
869        .dedup();
870    acc.sass_mixin_decl_names.sort();
871    acc.sass_mixin_decl_names.dedup();
872    acc.sass_mixin_include_names.sort();
873    acc.sass_mixin_include_names.dedup();
874    acc.sass_selectors_with_mixin_includes_names.sort();
875    acc.sass_selectors_with_mixin_includes_names.dedup();
876    acc.sass_selectors_with_resolved_mixin_includes_names.sort();
877    acc.sass_selectors_with_resolved_mixin_includes_names
878        .dedup();
879    acc.sass_selectors_with_unresolved_mixin_includes_names
880        .sort();
881    acc.sass_selectors_with_unresolved_mixin_includes_names
882        .dedup();
883    acc.sass_function_decl_names.sort();
884    acc.sass_function_decl_names.dedup();
885    acc.sass_function_call_names.sort();
886    acc.sass_function_call_names.dedup();
887    acc.sass_selectors_with_function_calls_names.sort();
888    acc.sass_selectors_with_function_calls_names.dedup();
889    acc.sass_selector_symbol_facts.sort();
890    acc.sass_selector_symbol_facts.dedup();
891    acc.sass_module_use_sources.sort();
892    acc.sass_module_use_sources.dedup();
893    acc.sass_module_use_edges.sort();
894    acc.sass_module_use_edges.dedup();
895    acc.sass_module_forward_sources.sort();
896    acc.sass_module_forward_sources.dedup();
897    acc.sass_module_import_sources.sort();
898    acc.sass_module_import_sources.dedup();
899    acc.selectors_with_value_refs_names.sort();
900    acc.selectors_with_local_value_refs_names.sort();
901    acc.selectors_with_imported_value_refs_names.sort();
902    acc.selectors_with_value_refs_under_media_names.sort();
903    acc.selectors_with_value_refs_under_supports_names.sort();
904    acc.selectors_with_value_refs_under_layer_names.sort();
905    acc.selectors_with_local_value_refs_under_media_names.sort();
906    acc.selectors_with_local_value_refs_under_supports_names
907        .sort();
908    acc.selectors_with_local_value_refs_under_layer_names.sort();
909    acc.selectors_with_imported_value_refs_under_media_names
910        .sort();
911    acc.selectors_with_imported_value_refs_under_supports_names
912        .sort();
913    acc.selectors_with_imported_value_refs_under_layer_names
914        .sort();
915    acc.selectors_with_animation_ref_names.sort();
916    acc.selectors_with_animation_refs_under_media_names.sort();
917    acc.selectors_with_animation_refs_under_supports_names
918        .sort();
919    acc.selectors_with_animation_refs_under_layer_names.sort();
920    acc.selectors_with_animation_name_ref_names.sort();
921    acc.selectors_with_animation_name_refs_under_media_names
922        .sort();
923    acc.selectors_with_animation_name_refs_under_supports_names
924        .sort();
925    acc.selectors_with_animation_name_refs_under_layer_names
926        .sort();
927    acc.selectors_under_media_names.sort();
928    acc.selectors_under_supports_names.sort();
929    acc.selectors_under_layer_names.sort();
930    acc.animation_ref_names.sort();
931    acc.animation_ref_names.dedup();
932    acc.animation_name_ref_names.sort();
933    acc.animation_name_ref_names.dedup();
934    acc.keyframes_names_under_media.sort();
935    acc.keyframes_names_under_media.dedup();
936    acc.keyframes_names_under_supports.sort();
937    acc.keyframes_names_under_supports.dedup();
938    acc.keyframes_names_under_layer.sort();
939    acc.keyframes_names_under_layer.dedup();
940    let selectors_with_value_refs_names = acc.selectors_with_value_refs_names.clone();
941    let selectors_with_animation_ref_names = acc.selectors_with_animation_ref_names.clone();
942    let selectors_with_animation_name_ref_names =
943        acc.selectors_with_animation_name_ref_names.clone();
944    let sass_same_file_resolution = summarize_sass_same_file_resolution(&acc);
945
946    ParserIndexSummaryV0 {
947        schema_version: "0",
948        language: match sheet.language {
949            StyleLanguage::Css => "css",
950            StyleLanguage::Scss => "scss",
951            StyleLanguage::Less => "less",
952        },
953        selectors: ParserIndexSelectorFactsV0 {
954            names: acc.selector_names,
955            bem_suffix_parent_names: acc.bem_suffix_parent_names,
956            bem_suffix_safe_names: acc.bem_suffix_safe_selector_names,
957            nested_unsafe_names: acc.nested_unsafe_selector_names,
958            selectors_with_value_refs_names,
959            selectors_with_animation_ref_names,
960            selectors_with_animation_name_ref_names,
961            bem_suffix_count: acc.bem_suffix_count,
962            nested_safety_counts: acc.nested_safety_counts,
963        },
964        values: ParserIndexValueFactsV0 {
965            decl_names: acc.value_decl_names,
966            decl_names_with_local_refs: acc.value_decl_names_with_local_refs,
967            decl_names_with_imported_refs: acc.value_decl_names_with_imported_refs,
968            import_names: acc.value_import_names,
969            import_sources: acc.value_import_sources,
970            import_alias_count: acc.value_import_alias_count,
971            ref_names: acc.value_ref_names,
972            local_ref_names: acc.local_value_ref_names,
973            imported_ref_names: acc.imported_value_ref_names,
974            imported_ref_sources: acc.imported_value_ref_sources,
975            declaration_ref_names: acc.declaration_value_ref_names,
976            declaration_imported_ref_sources: acc.declaration_imported_value_ref_sources,
977            value_decl_ref_names: acc.value_decl_ref_names,
978            value_decl_imported_ref_sources: acc.value_decl_imported_value_ref_sources,
979            selectors_with_refs_names: acc.selectors_with_value_refs_names,
980            selectors_with_local_refs_names: acc.selectors_with_local_value_refs_names,
981            selectors_with_imported_refs_names: acc.selectors_with_imported_value_refs_names,
982            selectors_with_refs_under_media_names: acc.selectors_with_value_refs_under_media_names,
983            selectors_with_refs_under_supports_names: acc
984                .selectors_with_value_refs_under_supports_names,
985            selectors_with_refs_under_layer_names: acc.selectors_with_value_refs_under_layer_names,
986            selectors_with_local_refs_under_media_names: acc
987                .selectors_with_local_value_refs_under_media_names,
988            selectors_with_local_refs_under_supports_names: acc
989                .selectors_with_local_value_refs_under_supports_names,
990            selectors_with_local_refs_under_layer_names: acc
991                .selectors_with_local_value_refs_under_layer_names,
992            selectors_with_imported_refs_under_media_names: acc
993                .selectors_with_imported_value_refs_under_media_names,
994            selectors_with_imported_refs_under_supports_names: acc
995                .selectors_with_imported_value_refs_under_supports_names,
996            selectors_with_imported_refs_under_layer_names: acc
997                .selectors_with_imported_value_refs_under_layer_names,
998        },
999        custom_properties: ParserIndexCustomPropertyFactsV0 {
1000            decl_names: acc.custom_property_decl_names,
1001            ref_names: acc.custom_property_ref_names,
1002            selectors_with_refs_names: acc.selectors_with_custom_property_refs_names,
1003            selectors_with_refs_under_media_names: acc
1004                .selectors_with_custom_property_refs_under_media_names,
1005            selectors_with_refs_under_supports_names: acc
1006                .selectors_with_custom_property_refs_under_supports_names,
1007            selectors_with_refs_under_layer_names: acc
1008                .selectors_with_custom_property_refs_under_layer_names,
1009        },
1010        sass: ParserIndexSassFactsV0 {
1011            variable_decl_names: acc.sass_variable_decl_names,
1012            variable_parameter_names: acc.sass_variable_parameter_names,
1013            variable_ref_names: acc.sass_variable_ref_names,
1014            selectors_with_variable_refs_names: acc.sass_selectors_with_variable_refs_names,
1015            selectors_with_resolved_variable_refs_names: acc
1016                .sass_selectors_with_resolved_variable_refs_names,
1017            selectors_with_unresolved_variable_refs_names: acc
1018                .sass_selectors_with_unresolved_variable_refs_names,
1019            mixin_decl_names: acc.sass_mixin_decl_names,
1020            mixin_include_names: acc.sass_mixin_include_names,
1021            selectors_with_mixin_includes_names: acc.sass_selectors_with_mixin_includes_names,
1022            selectors_with_resolved_mixin_includes_names: acc
1023                .sass_selectors_with_resolved_mixin_includes_names,
1024            selectors_with_unresolved_mixin_includes_names: acc
1025                .sass_selectors_with_unresolved_mixin_includes_names,
1026            function_decl_names: acc.sass_function_decl_names,
1027            function_call_names: acc.sass_function_call_names,
1028            selectors_with_function_calls_names: acc.sass_selectors_with_function_calls_names,
1029            selector_symbol_facts: acc.sass_selector_symbol_facts,
1030            module_use_sources: acc.sass_module_use_sources,
1031            module_use_edges: acc.sass_module_use_edges,
1032            module_forward_sources: acc.sass_module_forward_sources,
1033            module_import_sources: acc.sass_module_import_sources,
1034            same_file_resolution: sass_same_file_resolution,
1035        },
1036        keyframes: ParserIndexKeyframesFactsV0 {
1037            names: acc.keyframes_names,
1038            names_under_media: acc.keyframes_names_under_media,
1039            names_under_supports: acc.keyframes_names_under_supports,
1040            names_under_layer: acc.keyframes_names_under_layer,
1041            animation_ref_names: acc.animation_ref_names,
1042            animation_name_ref_names: acc.animation_name_ref_names,
1043            selectors_with_animation_ref_names: acc.selectors_with_animation_ref_names,
1044            selectors_with_animation_name_ref_names: acc.selectors_with_animation_name_ref_names,
1045            selectors_with_animation_refs_under_media_names: acc
1046                .selectors_with_animation_refs_under_media_names,
1047            selectors_with_animation_refs_under_supports_names: acc
1048                .selectors_with_animation_refs_under_supports_names,
1049            selectors_with_animation_refs_under_layer_names: acc
1050                .selectors_with_animation_refs_under_layer_names,
1051            selectors_with_animation_name_refs_under_media_names: acc
1052                .selectors_with_animation_name_refs_under_media_names,
1053            selectors_with_animation_name_refs_under_supports_names: acc
1054                .selectors_with_animation_name_refs_under_supports_names,
1055            selectors_with_animation_name_refs_under_layer_names: acc
1056                .selectors_with_animation_name_refs_under_layer_names,
1057        },
1058        composes: ParserIndexComposesFactsV0 {
1059            selectors_with_composes_names: acc.selectors_with_composes_names,
1060            selectors_with_composes_under_media_names: acc
1061                .selectors_with_composes_under_media_names,
1062            selectors_with_composes_under_supports_names: acc
1063                .selectors_with_composes_under_supports_names,
1064            selectors_with_composes_under_layer_names: acc
1065                .selectors_with_composes_under_layer_names,
1066            local_selector_names: acc.local_composes_selector_names,
1067            imported_selector_names: acc.imported_composes_selector_names,
1068            global_selector_names: acc.global_composes_selector_names,
1069            local_selector_names_under_media: acc.local_composes_selector_names_under_media,
1070            local_selector_names_under_supports: acc.local_composes_selector_names_under_supports,
1071            local_selector_names_under_layer: acc.local_composes_selector_names_under_layer,
1072            imported_selector_names_under_media: acc.imported_composes_selector_names_under_media,
1073            imported_selector_names_under_supports: acc
1074                .imported_composes_selector_names_under_supports,
1075            imported_selector_names_under_layer: acc.imported_composes_selector_names_under_layer,
1076            global_selector_names_under_media: acc.global_composes_selector_names_under_media,
1077            global_selector_names_under_supports: acc.global_composes_selector_names_under_supports,
1078            global_selector_names_under_layer: acc.global_composes_selector_names_under_layer,
1079            import_sources: acc.composes_import_sources,
1080            import_sources_under_media: acc.composes_import_sources_under_media,
1081            import_sources_under_supports: acc.composes_import_sources_under_supports,
1082            import_sources_under_layer: acc.composes_import_sources_under_layer,
1083            class_name_count: acc.composes_class_name_count,
1084            local_class_name_count: acc.local_composes_class_name_count,
1085            imported_class_name_count: acc.imported_composes_class_name_count,
1086            global_class_name_count: acc.global_composes_class_name_count,
1087        },
1088        wrappers: ParserIndexWrapperFactsV0 {
1089            selectors_under_media_names: acc.selectors_under_media_names,
1090            selectors_under_supports_names: acc.selectors_under_supports_names,
1091            selectors_under_layer_names: acc.selectors_under_layer_names,
1092        },
1093    }
1094}
1095
1096pub fn summarize_parser_canonical_candidate(
1097    sheet: &Stylesheet,
1098) -> ParserCanonicalCandidateBundleV0 {
1099    let parity_lite = summarize_parity_lite(sheet);
1100    let css_modules_intermediate = summarize_css_modules_intermediate(sheet);
1101
1102    ParserCanonicalCandidateBundleV0 {
1103        schema_version: "0",
1104        language: parity_lite.language,
1105        parity_lite,
1106        css_modules_intermediate,
1107    }
1108}
1109
1110pub fn summarize_parser_evaluator_candidates(sheet: &Stylesheet) -> ParserEvaluatorCandidatesV0 {
1111    let intermediate = summarize_css_modules_intermediate(sheet);
1112    let bem_suffix_safe_names: BTreeSet<&str> = intermediate
1113        .selectors
1114        .bem_suffix_safe_names
1115        .iter()
1116        .map(String::as_str)
1117        .collect();
1118    let nested_unsafe_names: BTreeSet<&str> = intermediate
1119        .selectors
1120        .nested_unsafe_names
1121        .iter()
1122        .map(String::as_str)
1123        .collect();
1124    let selectors_under_media_names: BTreeSet<&str> = intermediate
1125        .wrappers
1126        .selectors_under_media_names
1127        .iter()
1128        .map(String::as_str)
1129        .collect();
1130    let selectors_under_supports_names: BTreeSet<&str> = intermediate
1131        .wrappers
1132        .selectors_under_supports_names
1133        .iter()
1134        .map(String::as_str)
1135        .collect();
1136    let selectors_under_layer_names: BTreeSet<&str> = intermediate
1137        .wrappers
1138        .selectors_under_layer_names
1139        .iter()
1140        .map(String::as_str)
1141        .collect();
1142    let selectors_with_refs_names: BTreeSet<&str> = intermediate
1143        .values
1144        .selectors_with_refs_names
1145        .iter()
1146        .map(String::as_str)
1147        .collect();
1148    let selectors_with_local_refs_names: BTreeSet<&str> = intermediate
1149        .values
1150        .selectors_with_local_refs_names
1151        .iter()
1152        .map(String::as_str)
1153        .collect();
1154    let selectors_with_imported_refs_names: BTreeSet<&str> = intermediate
1155        .values
1156        .selectors_with_imported_refs_names
1157        .iter()
1158        .map(String::as_str)
1159        .collect();
1160    let selectors_with_custom_property_refs_names: BTreeSet<&str> = intermediate
1161        .custom_properties
1162        .selectors_with_refs_names
1163        .iter()
1164        .map(String::as_str)
1165        .collect();
1166    let selectors_with_animation_ref_names: BTreeSet<&str> = intermediate
1167        .keyframes
1168        .selectors_with_animation_ref_names
1169        .iter()
1170        .map(String::as_str)
1171        .collect();
1172    let selectors_with_animation_name_ref_names: BTreeSet<&str> = intermediate
1173        .keyframes
1174        .selectors_with_animation_name_ref_names
1175        .iter()
1176        .map(String::as_str)
1177        .collect();
1178    let selectors_with_composes_names: BTreeSet<&str> = intermediate
1179        .composes
1180        .selectors_with_composes_names
1181        .iter()
1182        .map(String::as_str)
1183        .collect();
1184    let local_selector_names: BTreeSet<&str> = intermediate
1185        .composes
1186        .local_selector_names
1187        .iter()
1188        .map(String::as_str)
1189        .collect();
1190    let imported_selector_names: BTreeSet<&str> = intermediate
1191        .composes
1192        .imported_selector_names
1193        .iter()
1194        .map(String::as_str)
1195        .collect();
1196    let global_selector_names: BTreeSet<&str> = intermediate
1197        .composes
1198        .global_selector_names
1199        .iter()
1200        .map(String::as_str)
1201        .collect();
1202
1203    let results = intermediate
1204        .selectors
1205        .names
1206        .iter()
1207        .map(|selector_name| {
1208            let selector = selector_name.as_str();
1209            let nested_safety_kind = if nested_unsafe_names.contains(selector) {
1210                "nestedUnsafe"
1211            } else if bem_suffix_safe_names.contains(selector) {
1212                "bemSuffixSafe"
1213            } else {
1214                "flat"
1215            };
1216            let bem_suffix_parent_name = if nested_safety_kind == "bemSuffixSafe" {
1217                let suffix_split_index = [selector.rfind("__"), selector.rfind("--")]
1218                    .into_iter()
1219                    .flatten()
1220                    .max();
1221                suffix_split_index.map(|index| selector[..index].to_string())
1222            } else {
1223                None
1224            };
1225
1226            ParserEvaluatorCandidateV0 {
1227                kind: "selector-index-facts",
1228                selector_name: selector_name.clone(),
1229                nested_safety_kind,
1230                bem_suffix_parent_name,
1231                under_media: selectors_under_media_names.contains(selector),
1232                under_supports: selectors_under_supports_names.contains(selector),
1233                under_layer: selectors_under_layer_names.contains(selector),
1234                has_value_refs: selectors_with_refs_names.contains(selector),
1235                has_local_value_refs: selectors_with_local_refs_names.contains(selector),
1236                has_imported_value_refs: selectors_with_imported_refs_names.contains(selector),
1237                has_custom_property_refs: selectors_with_custom_property_refs_names
1238                    .contains(selector),
1239                has_animation_ref: selectors_with_animation_ref_names.contains(selector),
1240                has_animation_name_ref: selectors_with_animation_name_ref_names.contains(selector),
1241                has_composes: selectors_with_composes_names.contains(selector),
1242                has_local_composes: local_selector_names.contains(selector),
1243                has_imported_composes: imported_selector_names.contains(selector),
1244                has_global_composes: global_selector_names.contains(selector),
1245            }
1246        })
1247        .collect();
1248
1249    ParserEvaluatorCandidatesV0 {
1250        schema_version: "0",
1251        language: intermediate.language,
1252        results,
1253    }
1254}
1255
1256pub fn summarize_parser_canonical_producer_signal(
1257    sheet: &Stylesheet,
1258) -> ParserCanonicalProducerSignalV0 {
1259    let canonical_candidate = summarize_parser_canonical_candidate(sheet);
1260    let evaluator_candidates = summarize_parser_evaluator_candidates(sheet);
1261
1262    ParserCanonicalProducerSignalV0 {
1263        schema_version: "0",
1264        language: canonical_candidate.language,
1265        canonical_candidate,
1266        evaluator_candidates,
1267        public_product_gate: ParserPublicProductGateSignalV0 {
1268            canonical_candidate_command: "pnpm check:rust-parser-canonical-candidate",
1269            consumer_boundary_command: "pnpm check:rust-parser-consumer-boundary",
1270            public_product_gate_command: "pnpm check:rust-parser-public-product",
1271            included_in_parser_lane: true,
1272            included_in_rust_lane_bundle: true,
1273            included_in_rust_release_bundle: true,
1274        },
1275    }
1276}
1277
1278pub fn summarize_index_bridge(sheet: &Stylesheet) -> ParserIndexSummaryV0 {
1279    summarize_css_modules_intermediate(sheet)
1280}
1281
1282pub fn summarize_semantic_boundary(sheet: &Stylesheet) -> ParserSemanticBoundarySummaryV0 {
1283    let index = summarize_css_modules_intermediate(sheet);
1284    let ParserIndexSummaryV0 {
1285        schema_version: _,
1286        language,
1287        selectors,
1288        values,
1289        custom_properties,
1290        sass,
1291        keyframes,
1292        composes,
1293        wrappers,
1294    } = index;
1295    let ParserIndexSelectorFactsV0 {
1296        names,
1297        bem_suffix_parent_names,
1298        bem_suffix_safe_names,
1299        nested_unsafe_names,
1300        selectors_with_value_refs_names,
1301        selectors_with_animation_ref_names,
1302        selectors_with_animation_name_ref_names,
1303        bem_suffix_count,
1304        nested_safety_counts,
1305    } = selectors;
1306    let ParserIndexSassFactsV0 {
1307        variable_decl_names,
1308        variable_parameter_names,
1309        variable_ref_names,
1310        selectors_with_variable_refs_names: _,
1311        selectors_with_resolved_variable_refs_names,
1312        selectors_with_unresolved_variable_refs_names,
1313        mixin_decl_names,
1314        mixin_include_names,
1315        selectors_with_mixin_includes_names: _,
1316        selectors_with_resolved_mixin_includes_names,
1317        selectors_with_unresolved_mixin_includes_names,
1318        function_decl_names,
1319        function_call_names,
1320        selectors_with_function_calls_names,
1321        selector_symbol_facts,
1322        module_use_sources,
1323        module_use_edges,
1324        module_forward_sources,
1325        module_import_sources,
1326        same_file_resolution,
1327    } = sass;
1328    let custom_property_semantic_facts =
1329        summarize_custom_property_semantic_facts(&custom_properties);
1330
1331    ParserSemanticBoundarySummaryV0 {
1332        schema_version: "0",
1333        language,
1334        parser_facts: ParserBoundarySyntaxFactsV0 {
1335            lossless_cst: summarize_lossless_cst(sheet),
1336            selectors: ParserIndexSelectorFactsV0 {
1337                names: names.clone(),
1338                bem_suffix_parent_names: bem_suffix_parent_names.clone(),
1339                bem_suffix_safe_names: bem_suffix_safe_names.clone(),
1340                nested_unsafe_names: nested_unsafe_names.clone(),
1341                selectors_with_value_refs_names,
1342                selectors_with_animation_ref_names,
1343                selectors_with_animation_name_ref_names,
1344                bem_suffix_count,
1345                nested_safety_counts: nested_safety_counts.clone(),
1346            },
1347            values,
1348            custom_properties,
1349            sass: ParserSassSyntaxFactsV0 {
1350                variable_decl_names,
1351                variable_parameter_names,
1352                variable_ref_names,
1353                mixin_decl_names,
1354                mixin_include_names,
1355                function_decl_names,
1356                function_call_names,
1357                module_use_sources,
1358                module_use_edges,
1359                module_forward_sources,
1360                module_import_sources,
1361            },
1362            keyframes,
1363            composes,
1364            wrappers,
1365        },
1366        semantic_facts: StyleSemanticFactsV0 {
1367            selector_identity: StyleSelectorIdentityFactsV0 {
1368                canonical_names: names,
1369                bem_suffix_safe_names,
1370                bem_suffix_parent_names,
1371                nested_unsafe_names,
1372                nested_safety_counts,
1373            },
1374            custom_properties: custom_property_semantic_facts,
1375            sass: StyleSassSemanticFactsV0 {
1376                selector_symbol_facts,
1377                selectors_with_resolved_variable_refs_names,
1378                selectors_with_unresolved_variable_refs_names,
1379                selectors_with_resolved_mixin_includes_names,
1380                selectors_with_unresolved_mixin_includes_names,
1381                selectors_with_function_calls_names,
1382                same_file_resolution,
1383            },
1384        },
1385    }
1386}
1387
1388fn summarize_custom_property_semantic_facts(
1389    facts: &ParserIndexCustomPropertyFactsV0,
1390) -> StyleCustomPropertySemanticFactsV0 {
1391    let decl_names: BTreeSet<&str> = facts.decl_names.iter().map(String::as_str).collect();
1392    let resolved_ref_names = facts
1393        .ref_names
1394        .iter()
1395        .filter(|name| decl_names.contains(name.as_str()))
1396        .cloned()
1397        .collect();
1398    let unresolved_ref_names = facts
1399        .ref_names
1400        .iter()
1401        .filter(|name| !decl_names.contains(name.as_str()))
1402        .cloned()
1403        .collect();
1404
1405    StyleCustomPropertySemanticFactsV0 {
1406        decl_names: facts.decl_names.clone(),
1407        ref_names: facts.ref_names.clone(),
1408        resolved_ref_names,
1409        unresolved_ref_names,
1410        selectors_with_refs_names: facts.selectors_with_refs_names.clone(),
1411    }
1412}
1413
1414fn summarize_lossless_cst(sheet: &Stylesheet) -> ParserLosslessCstFactsV0 {
1415    let source_byte_len = sheet.source.len();
1416    ParserLosslessCstFactsV0 {
1417        source_byte_len,
1418        token_count: sheet.tokens.len(),
1419        root_node_count: sheet.nodes.len(),
1420        diagnostic_count: sheet.diagnostics.len(),
1421        all_token_spans_within_source: sheet
1422            .tokens
1423            .iter()
1424            .all(|token| is_valid_span(token.span, source_byte_len)),
1425        all_node_spans_within_source: nodes_have_valid_spans(&sheet.nodes, source_byte_len),
1426    }
1427}
1428
1429fn nodes_have_valid_spans(nodes: &[SyntaxNode], source_byte_len: usize) -> bool {
1430    nodes.iter().all(|node| {
1431        is_valid_span(node.span, source_byte_len)
1432            && node
1433                .header_span
1434                .is_none_or(|span| is_valid_span(span, source_byte_len))
1435            && nodes_have_valid_spans(&node.children, source_byte_len)
1436    })
1437}
1438
1439fn is_valid_span(span: TextSpan, source_byte_len: usize) -> bool {
1440    span.start <= span.end && span.end <= source_byte_len
1441}
1442
1443fn collect_parity_names(nodes: &[SyntaxNode], acc: &mut ParityLiteAcc) {
1444    collect_parity_names_with_parent(nodes, acc, &[], 0);
1445}
1446
1447fn collect_parity_names_with_parent(
1448    nodes: &[SyntaxNode],
1449    acc: &mut ParityLiteAcc,
1450    parent_branches: &[ResolvedSelectorBranch],
1451    depth: usize,
1452) {
1453    for node in nodes {
1454        let mut next_parent_branches = parent_branches.to_vec();
1455        let mut next_depth = depth;
1456        match &node.payload {
1457            Some(SyntaxNodePayload::Rule(rule)) => {
1458                acc.rule_count += 1;
1459                next_depth = depth + 1;
1460                acc.max_nesting_depth = acc.max_nesting_depth.max(next_depth);
1461                if rule.selector_groups.len() > 1 {
1462                    acc.grouped_selector_count += rule.selector_groups.len();
1463                }
1464                let resolved = resolve_rule_selector_branches(rule, parent_branches);
1465                if !resolved.is_empty() {
1466                    acc.selector_names
1467                        .extend(resolved.iter().map(|branch| branch.name.clone()));
1468                    next_parent_branches = resolved;
1469                }
1470            }
1471            Some(SyntaxNodePayload::AtRule(at_rule)) => {
1472                next_depth = depth + 1;
1473                acc.max_nesting_depth = acc.max_nesting_depth.max(next_depth);
1474                increment_at_rule_kind_count(&mut acc.at_rule_kind_counts, at_rule.kind);
1475                match at_rule.kind {
1476                    AtRuleKind::Keyframes if !at_rule.params.is_empty() => {
1477                        acc.keyframes_names.push(at_rule.params.clone());
1478                    }
1479                    AtRuleKind::Keyframes => {}
1480                    AtRuleKind::Value => {
1481                        if let Some((name, _)) = at_rule.params.split_once(':') {
1482                            let trimmed = name.trim();
1483                            if !trimmed.is_empty() {
1484                                acc.value_decl_names.push(trimmed.to_string());
1485                            }
1486                        }
1487                    }
1488                    _ => {}
1489                }
1490            }
1491            Some(SyntaxNodePayload::Declaration(declaration)) => {
1492                acc.declaration_count += 1;
1493                increment_declaration_kind_count(
1494                    &mut acc.declaration_kind_counts,
1495                    classify_declaration_kind(&declaration.property),
1496                );
1497            }
1498            _ => {}
1499        }
1500        collect_parity_names_with_parent(&node.children, acc, &next_parent_branches, next_depth);
1501    }
1502}
1503
1504fn increment_at_rule_kind_count(counts: &mut AtRuleKindCountsV0, kind: AtRuleKind) {
1505    match kind {
1506        AtRuleKind::Media => counts.media += 1,
1507        AtRuleKind::Supports => counts.supports += 1,
1508        AtRuleKind::Layer => counts.layer += 1,
1509        AtRuleKind::Keyframes => counts.keyframes += 1,
1510        AtRuleKind::Value => counts.value += 1,
1511        AtRuleKind::AtRoot => counts.at_root += 1,
1512        AtRuleKind::Mixin
1513        | AtRuleKind::Include
1514        | AtRuleKind::Function
1515        | AtRuleKind::Use
1516        | AtRuleKind::Forward
1517        | AtRuleKind::Import => counts.generic += 1,
1518        AtRuleKind::Generic => counts.generic += 1,
1519    }
1520}
1521
1522#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1523enum DeclarationKind {
1524    Composes,
1525    Animation,
1526    AnimationName,
1527    Generic,
1528}
1529
1530#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1531enum NestedSafetyKind {
1532    Flat,
1533    BemSuffixSafe,
1534    NestedUnsafe,
1535}
1536
1537struct RuleSelectorFacts {
1538    nested_safety: NestedSafetyKind,
1539    bem_suffix_count: usize,
1540}
1541
1542#[derive(Debug, Default)]
1543struct RuleComposesFacts {
1544    local_class_name_count: usize,
1545    imported_class_name_count: usize,
1546    global_class_name_count: usize,
1547    imported_sources: Vec<String>,
1548}
1549
1550#[derive(Debug, Default)]
1551struct RuleReferenceFacts {
1552    has_value_refs: bool,
1553    has_local_value_refs: bool,
1554    has_imported_value_refs: bool,
1555    has_animation_refs: bool,
1556    has_animation_name_refs: bool,
1557    has_custom_property_refs: bool,
1558    has_sass_variable_refs: bool,
1559    has_resolved_sass_variable_refs: bool,
1560    has_unresolved_sass_variable_refs: bool,
1561    has_sass_mixin_includes: bool,
1562    has_resolved_sass_mixin_includes: bool,
1563    has_unresolved_sass_mixin_includes: bool,
1564    has_sass_function_calls: bool,
1565    sass_symbol_facts: Vec<RuleSassSymbolFact>,
1566}
1567
1568#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
1569struct RuleSassSymbolFact {
1570    symbol_kind: &'static str,
1571    name: String,
1572    role: &'static str,
1573    resolution: &'static str,
1574    byte_span: ParserByteSpanV0,
1575}
1576
1577#[derive(Debug, Clone, PartialEq, Eq)]
1578struct SassNameSpan {
1579    name: String,
1580    byte_span: ParserByteSpanV0,
1581}
1582
1583#[derive(Debug, Clone, PartialEq, Eq)]
1584struct SassVariableDeclFact {
1585    name: String,
1586    scope: SassVariableScope,
1587}
1588
1589#[derive(Debug, Clone, PartialEq, Eq)]
1590struct SassVariableRefFact {
1591    name: String,
1592    byte_span: ParserByteSpanV0,
1593}
1594
1595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1596enum SassVariableScope {
1597    File,
1598    Span(TextSpan),
1599}
1600
1601#[derive(Debug, Clone, Copy, Default)]
1602struct WrapperContext {
1603    under_media: bool,
1604    under_supports: bool,
1605    under_layer: bool,
1606}
1607
1608#[derive(Debug, Clone, Copy)]
1609struct ValueRefContext<'a> {
1610    known: &'a BTreeSet<String>,
1611    local: &'a BTreeSet<String>,
1612    imported: &'a BTreeSet<String>,
1613}
1614
1615#[derive(Debug, Clone, Copy)]
1616struct SassRefContext<'a> {
1617    variable_decls: &'a [SassVariableDeclFact],
1618    mixin_targets: &'a BTreeSet<String>,
1619    function_targets: &'a BTreeSet<String>,
1620}
1621
1622#[derive(Debug, Clone, Copy)]
1623struct SelectorAttachmentContext<'a> {
1624    source: &'a str,
1625    value_ref_ctx: ValueRefContext<'a>,
1626    known_keyframe_names: &'a BTreeSet<String>,
1627    sass_ref_ctx: SassRefContext<'a>,
1628}
1629
1630#[derive(Debug, Clone, Copy)]
1631enum ValueRefOrigin {
1632    Declaration,
1633    ValueDecl,
1634}
1635
1636#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1637enum ComposesKind {
1638    Local,
1639    Imported,
1640    Global,
1641}
1642
1643#[derive(Debug)]
1644struct ComposesSpec {
1645    class_names: Vec<String>,
1646    kind: ComposesKind,
1647    from_source: Option<String>,
1648}
1649
1650fn classify_declaration_kind(property: &str) -> DeclarationKind {
1651    match property.trim().to_ascii_lowercase().as_str() {
1652        "composes" => DeclarationKind::Composes,
1653        "animation" => DeclarationKind::Animation,
1654        "animation-name" => DeclarationKind::AnimationName,
1655        _ => DeclarationKind::Generic,
1656    }
1657}
1658
1659fn increment_declaration_kind_count(counts: &mut DeclarationKindCountsV0, kind: DeclarationKind) {
1660    match kind {
1661        DeclarationKind::Composes => counts.composes += 1,
1662        DeclarationKind::Animation => counts.animation += 1,
1663        DeclarationKind::AnimationName => counts.animation_name += 1,
1664        DeclarationKind::Generic => counts.generic += 1,
1665    }
1666}
1667
1668fn classify_rule_selector_facts(
1669    rule: &RulePayload,
1670    parent_branches: &[ResolvedSelectorBranch],
1671    parent_is_grouped: bool,
1672) -> RuleSelectorFacts {
1673    let is_nested = !parent_branches.is_empty()
1674        || rule
1675            .selector_groups
1676            .iter()
1677            .any(|group| group.raw.contains('&'));
1678    if !is_nested {
1679        return RuleSelectorFacts {
1680            nested_safety: NestedSafetyKind::Flat,
1681            bem_suffix_count: 0,
1682        };
1683    }
1684
1685    let bem_suffix_safe = rule.selector_groups.len() == 1
1686        && parent_branches.len() == 1
1687        && parent_branches[0].bare_suffix_base
1688        && !parent_is_grouped
1689        && matches!(
1690            rule.selector_groups[0].segments.as_slice(),
1691            [SelectorSegment::Ampersand, SelectorSegment::BemSuffix(_)]
1692        );
1693
1694    if bem_suffix_safe {
1695        RuleSelectorFacts {
1696            nested_safety: NestedSafetyKind::BemSuffixSafe,
1697            bem_suffix_count: 1,
1698        }
1699    } else {
1700        RuleSelectorFacts {
1701            nested_safety: NestedSafetyKind::NestedUnsafe,
1702            bem_suffix_count: 0,
1703        }
1704    }
1705}
1706
1707fn increment_nested_safety_count(
1708    counts: &mut NestedSafetyCountsV0,
1709    kind: NestedSafetyKind,
1710    amount: usize,
1711) {
1712    match kind {
1713        NestedSafetyKind::Flat => counts.flat += amount,
1714        NestedSafetyKind::BemSuffixSafe => counts.bem_suffix_safe += amount,
1715        NestedSafetyKind::NestedUnsafe => counts.nested_unsafe += amount,
1716    }
1717}
1718
1719fn collect_rule_composes_facts(children: &[SyntaxNode]) -> RuleComposesFacts {
1720    let mut facts = RuleComposesFacts::default();
1721    for child in children {
1722        if let Some(SyntaxNodePayload::Declaration(declaration)) = &child.payload
1723            && classify_declaration_kind(&declaration.property) == DeclarationKind::Composes
1724            && let Some(spec) = parse_composes_spec(&declaration.value)
1725        {
1726            match spec.kind {
1727                ComposesKind::Local => facts.local_class_name_count += spec.class_names.len(),
1728                ComposesKind::Imported => {
1729                    facts.imported_class_name_count += spec.class_names.len();
1730                    if let Some(source) = spec.from_source {
1731                        facts.imported_sources.push(source);
1732                    }
1733                }
1734                ComposesKind::Global => facts.global_class_name_count += spec.class_names.len(),
1735            }
1736        }
1737    }
1738    facts
1739}
1740
1741fn declaration_value_span(source: &str, node: &SyntaxNode) -> TextSpan {
1742    let header_span = node.header_span.unwrap_or(node.span);
1743    let raw = &source[header_span.start..header_span.end];
1744    let Some(colon_index) = raw.find(':') else {
1745        return TextSpan::new(header_span.end, header_span.end);
1746    };
1747    trim_source_span(
1748        source,
1749        TextSpan::new(header_span.start + colon_index + 1, header_span.end),
1750    )
1751}
1752
1753fn at_rule_params_span(source: &str, node: &SyntaxNode, at_rule: &AtRulePayload) -> TextSpan {
1754    let header_span = node.header_span.unwrap_or(node.span);
1755    let raw = &source[header_span.start..header_span.end];
1756    let search_start = raw.find('@').map_or(0, |index| index + 1);
1757    let Some(name_index) = raw[search_start..].find(&at_rule.name) else {
1758        return TextSpan::new(header_span.end, header_span.end);
1759    };
1760    let params_start = header_span.start + search_start + name_index + at_rule.name.len();
1761    trim_source_span(source, TextSpan::new(params_start, header_span.end))
1762}
1763
1764fn trim_source_span(source: &str, span: TextSpan) -> TextSpan {
1765    let raw = &source[span.start..span.end];
1766    let trimmed_start = raw.len() - raw.trim_start().len();
1767    let trimmed_len = raw.trim().len();
1768    let start = span.start + trimmed_start;
1769    TextSpan::new(start, start + trimmed_len)
1770}
1771
1772fn source_range_for_byte_span(source: &str, span: ParserByteSpanV0) -> ParserRangeV0 {
1773    ParserRangeV0 {
1774        start: source_position_for_byte_offset(source, span.start),
1775        end: source_position_for_byte_offset(source, span.end),
1776    }
1777}
1778
1779fn source_position_for_byte_offset(source: &str, offset: usize) -> ParserPositionV0 {
1780    let clamped_offset = offset.min(source.len());
1781    let mut line = 0usize;
1782    let mut character = 0usize;
1783
1784    for (byte_index, ch) in source.char_indices() {
1785        if byte_index >= clamped_offset {
1786            break;
1787        }
1788        if ch == '\n' {
1789            line += 1;
1790            character = 0;
1791        } else {
1792            character += ch.len_utf16();
1793        }
1794    }
1795
1796    ParserPositionV0 { line, character }
1797}
1798
1799fn collect_rule_reference_facts(
1800    children: &[SyntaxNode],
1801    source: &str,
1802    value_ref_ctx: ValueRefContext<'_>,
1803    known_keyframe_names: &BTreeSet<String>,
1804    sass_ref_ctx: SassRefContext<'_>,
1805) -> RuleReferenceFacts {
1806    let mut facts = RuleReferenceFacts::default();
1807    for child in children {
1808        match &child.payload {
1809            Some(SyntaxNodePayload::Declaration(declaration)) => {
1810                let value_span = declaration_value_span(source, child);
1811                match classify_declaration_kind(&declaration.property) {
1812                    DeclarationKind::Composes => {}
1813                    DeclarationKind::Animation => {
1814                        if !find_identifier_matches(&declaration.value, known_keyframe_names)
1815                            .is_empty()
1816                        {
1817                            facts.has_animation_refs = true;
1818                        }
1819                        extend_rule_value_ref_facts(&mut facts, &declaration.value, value_ref_ctx);
1820                    }
1821                    DeclarationKind::AnimationName => {
1822                        if !find_identifier_matches(&declaration.value, known_keyframe_names)
1823                            .is_empty()
1824                        {
1825                            facts.has_animation_name_refs = true;
1826                        }
1827                        extend_rule_value_ref_facts(&mut facts, &declaration.value, value_ref_ctx);
1828                    }
1829                    DeclarationKind::Generic => {
1830                        extend_rule_value_ref_facts(&mut facts, &declaration.value, value_ref_ctx);
1831                    }
1832                }
1833                extend_rule_sass_value_ref_facts(
1834                    &mut facts,
1835                    &declaration.value,
1836                    value_span,
1837                    sass_ref_ctx,
1838                );
1839                if !find_css_var_ref_names(&declaration.value).is_empty() {
1840                    facts.has_custom_property_refs = true;
1841                }
1842            }
1843            Some(SyntaxNodePayload::AtRule(at_rule)) => match at_rule.kind {
1844                AtRuleKind::Mixin | AtRuleKind::Function => {}
1845                AtRuleKind::Include => {
1846                    let params_span = at_rule_params_span(source, child, at_rule);
1847                    extend_rule_sass_value_ref_facts(
1848                        &mut facts,
1849                        &at_rule.params,
1850                        params_span,
1851                        sass_ref_ctx,
1852                    );
1853                    if let Some(name_span) =
1854                        parse_sass_callable_name_with_span(&at_rule.params, params_span.start)
1855                    {
1856                        facts.has_sass_mixin_includes = true;
1857                        let resolution = if sass_ref_ctx.mixin_targets.contains(&name_span.name) {
1858                            facts.has_resolved_sass_mixin_includes = true;
1859                            "resolved"
1860                        } else {
1861                            facts.has_unresolved_sass_mixin_includes = true;
1862                            "unresolved"
1863                        };
1864                        facts.sass_symbol_facts.push(RuleSassSymbolFact {
1865                            symbol_kind: "mixin",
1866                            name: name_span.name,
1867                            role: "include",
1868                            resolution,
1869                            byte_span: name_span.byte_span,
1870                        });
1871                    }
1872                }
1873                _ => {
1874                    let params_span = at_rule_params_span(source, child, at_rule);
1875                    extend_rule_sass_value_ref_facts(
1876                        &mut facts,
1877                        &at_rule.params,
1878                        params_span,
1879                        sass_ref_ctx,
1880                    );
1881                }
1882            },
1883            _ => {}
1884        }
1885    }
1886    facts
1887}
1888
1889fn extend_rule_value_ref_facts(
1890    facts: &mut RuleReferenceFacts,
1891    value: &str,
1892    value_ref_ctx: ValueRefContext<'_>,
1893) {
1894    let value_refs = find_identifier_matches(value, value_ref_ctx.known);
1895    if !value_refs.is_empty() {
1896        facts.has_value_refs = true;
1897        facts.has_local_value_refs |= value_refs
1898            .iter()
1899            .any(|name| value_ref_ctx.local.contains(name));
1900        facts.has_imported_value_refs |= value_refs
1901            .iter()
1902            .any(|name| value_ref_ctx.imported.contains(name));
1903    }
1904}
1905
1906fn extend_rule_sass_value_ref_facts(
1907    facts: &mut RuleReferenceFacts,
1908    value: &str,
1909    value_span: TextSpan,
1910    sass_ref_ctx: SassRefContext<'_>,
1911) {
1912    let variable_refs = find_sass_variable_ref_spans(value, value_span.start);
1913    if !variable_refs.is_empty() {
1914        facts.has_sass_variable_refs = true;
1915        for name_span in variable_refs {
1916            let resolution = if resolve_sass_variable_ref(
1917                &name_span.name,
1918                name_span.byte_span,
1919                sass_ref_ctx.variable_decls,
1920            ) {
1921                facts.has_resolved_sass_variable_refs = true;
1922                "resolved"
1923            } else {
1924                facts.has_unresolved_sass_variable_refs = true;
1925                "unresolved"
1926            };
1927            facts.sass_symbol_facts.push(RuleSassSymbolFact {
1928                symbol_kind: "variable",
1929                name: name_span.name,
1930                role: "reference",
1931                resolution,
1932                byte_span: name_span.byte_span,
1933            });
1934        }
1935    }
1936
1937    let function_calls =
1938        find_sass_function_call_spans(value, value_span.start, sass_ref_ctx.function_targets);
1939    if !function_calls.is_empty() {
1940        facts.has_sass_function_calls = true;
1941        facts
1942            .sass_symbol_facts
1943            .extend(
1944                function_calls
1945                    .into_iter()
1946                    .map(|name_span| RuleSassSymbolFact {
1947                        symbol_kind: "function",
1948                        name: name_span.name,
1949                        role: "call",
1950                        resolution: "resolved",
1951                        byte_span: name_span.byte_span,
1952                    }),
1953            );
1954    }
1955}
1956
1957fn resolve_sass_variable_ref(
1958    name: &str,
1959    byte_span: ParserByteSpanV0,
1960    variable_decls: &[SassVariableDeclFact],
1961) -> bool {
1962    variable_decls
1963        .iter()
1964        .any(|decl| decl.name == name && sass_variable_scope_contains(decl.scope, byte_span))
1965}
1966
1967fn sass_variable_scope_contains(scope: SassVariableScope, byte_span: ParserByteSpanV0) -> bool {
1968    match scope {
1969        SassVariableScope::File => true,
1970        SassVariableScope::Span(scope_span) => {
1971            scope_span.start <= byte_span.start && scope_span.end >= byte_span.end
1972        }
1973    }
1974}
1975
1976fn collect_index_names(
1977    nodes: &[SyntaxNode],
1978    acc: &mut IndexSummaryAcc,
1979    parent_branches: &[ResolvedSelectorBranch],
1980    parent_is_grouped: bool,
1981    current_sass_scope: Option<TextSpan>,
1982) {
1983    for node in nodes {
1984        let mut next_parent_branches = parent_branches.to_vec();
1985        let mut next_parent_is_grouped = false;
1986        let mut split_child_branches = false;
1987        let mut next_sass_scope = current_sass_scope;
1988        match &node.payload {
1989            Some(SyntaxNodePayload::Rule(rule)) => {
1990                next_sass_scope = Some(node.span);
1991                let resolved_branches = resolve_rule_selector_branches(rule, parent_branches);
1992                if !resolved_branches.is_empty() {
1993                    let resolved: Vec<String> = resolved_branches
1994                        .iter()
1995                        .map(|branch| branch.name.clone())
1996                        .collect();
1997                    let selector_facts =
1998                        classify_rule_selector_facts(rule, parent_branches, parent_is_grouped);
1999                    acc.bem_suffix_count += selector_facts.bem_suffix_count;
2000                    increment_nested_safety_count(
2001                        &mut acc.nested_safety_counts,
2002                        selector_facts.nested_safety,
2003                        resolved.len(),
2004                    );
2005                    acc.selector_names.extend(resolved.iter().cloned());
2006                    let composes_facts = collect_rule_composes_facts(&node.children);
2007                    if composes_facts.local_class_name_count > 0
2008                        || composes_facts.imported_class_name_count > 0
2009                        || composes_facts.global_class_name_count > 0
2010                    {
2011                        acc.selectors_with_composes_names
2012                            .extend(resolved.iter().cloned());
2013                        let selector_multiplier = resolved.len();
2014                        if composes_facts.local_class_name_count > 0 {
2015                            acc.local_composes_selector_names
2016                                .extend(resolved.iter().cloned());
2017                            acc.local_composes_class_name_count +=
2018                                composes_facts.local_class_name_count * selector_multiplier;
2019                        }
2020                        if composes_facts.imported_class_name_count > 0 {
2021                            acc.imported_composes_selector_names
2022                                .extend(resolved.iter().cloned());
2023                            acc.imported_composes_class_name_count +=
2024                                composes_facts.imported_class_name_count * selector_multiplier;
2025                            acc.composes_import_sources.extend(
2026                                composes_facts.imported_sources.iter().flat_map(|source| {
2027                                    std::iter::repeat_n(source.clone(), selector_multiplier)
2028                                }),
2029                            );
2030                        }
2031                        if composes_facts.global_class_name_count > 0 {
2032                            acc.global_composes_selector_names
2033                                .extend(resolved.iter().cloned());
2034                            acc.global_composes_class_name_count +=
2035                                composes_facts.global_class_name_count * selector_multiplier;
2036                        }
2037                        acc.composes_class_name_count += (composes_facts.local_class_name_count
2038                            + composes_facts.imported_class_name_count
2039                            + composes_facts.global_class_name_count)
2040                            * selector_multiplier;
2041                    }
2042                    match selector_facts.nested_safety {
2043                        NestedSafetyKind::BemSuffixSafe => {
2044                            acc.bem_suffix_safe_selector_names
2045                                .extend(resolved.iter().cloned());
2046                            if let Some(parent) = parent_branches.first() {
2047                                acc.bem_suffix_parent_names.push(parent.name.clone());
2048                            }
2049                        }
2050                        NestedSafetyKind::NestedUnsafe => {
2051                            acc.nested_unsafe_selector_names
2052                                .extend(resolved.iter().cloned());
2053                        }
2054                        NestedSafetyKind::Flat => {}
2055                    }
2056                    next_parent_is_grouped = resolved.len() > 1;
2057                    next_parent_branches = resolved_branches;
2058                    split_child_branches = true;
2059                }
2060            }
2061            Some(SyntaxNodePayload::AtRule(at_rule)) => match at_rule.kind {
2062                AtRuleKind::Media
2063                | AtRuleKind::Supports
2064                | AtRuleKind::Layer
2065                | AtRuleKind::AtRoot
2066                | AtRuleKind::Mixin
2067                | AtRuleKind::Function
2068                | AtRuleKind::Generic => {
2069                    next_sass_scope = Some(node.span);
2070                }
2071                AtRuleKind::Keyframes
2072                | AtRuleKind::Value
2073                | AtRuleKind::Include
2074                | AtRuleKind::Use
2075                | AtRuleKind::Forward
2076                | AtRuleKind::Import => {}
2077            },
2078            _ => {}
2079        }
2080
2081        match &node.payload {
2082            Some(SyntaxNodePayload::AtRule(at_rule)) => match at_rule.kind {
2083                AtRuleKind::Keyframes if !at_rule.params.is_empty() => {
2084                    acc.keyframes_names.push(at_rule.params.clone());
2085                }
2086                AtRuleKind::Keyframes => {}
2087                AtRuleKind::Value => {
2088                    if let Some(import_specs) = parse_value_import_specs(&at_rule.params) {
2089                        acc.value_import_alias_count += import_specs
2090                            .iter()
2091                            .filter(|spec| spec.imported_name != spec.local_name)
2092                            .count();
2093                        acc.value_import_names
2094                            .extend(import_specs.iter().map(|spec| spec.local_name.clone()));
2095                        acc.value_import_source_by_name
2096                            .extend(import_specs.iter().filter_map(|spec| {
2097                                spec.from_source
2098                                    .as_ref()
2099                                    .map(|source| (spec.local_name.clone(), source.clone()))
2100                            }));
2101                        acc.value_import_sources
2102                            .extend(import_specs.into_iter().filter_map(|spec| spec.from_source));
2103                    } else if let Some((name, _)) = parse_local_value_decl_parts(&at_rule.params) {
2104                        acc.value_decl_names.push(name.to_string());
2105                    }
2106                }
2107                AtRuleKind::Mixin => {
2108                    if let Some(name) = parse_sass_callable_name(&at_rule.params) {
2109                        acc.sass_mixin_decl_names.push(name);
2110                    }
2111                    let parameter_names = parse_sass_parameter_names(&at_rule.params);
2112                    acc.sass_variable_decl_facts
2113                        .extend(parameter_names.iter().map(|name| SassVariableDeclFact {
2114                            name: name.clone(),
2115                            scope: SassVariableScope::Span(node.span),
2116                        }));
2117                    acc.sass_variable_parameter_names.extend(parameter_names);
2118                }
2119                AtRuleKind::Include => {
2120                    if let Some(name) = parse_sass_callable_name(&at_rule.params) {
2121                        acc.sass_mixin_include_names.push(name);
2122                    }
2123                }
2124                AtRuleKind::Function => {
2125                    if let Some(name) = parse_sass_callable_name(&at_rule.params) {
2126                        acc.sass_function_decl_names.push(name);
2127                    }
2128                    let parameter_names = parse_sass_parameter_names(&at_rule.params);
2129                    acc.sass_variable_decl_facts
2130                        .extend(parameter_names.iter().map(|name| SassVariableDeclFact {
2131                            name: name.clone(),
2132                            scope: SassVariableScope::Span(node.span),
2133                        }));
2134                    acc.sass_variable_parameter_names.extend(parameter_names);
2135                }
2136                AtRuleKind::Use => {
2137                    acc.sass_module_use_sources
2138                        .extend(parse_sass_module_sources(&at_rule.params));
2139                    acc.sass_module_use_edges
2140                        .extend(parse_sass_module_use_edges(&at_rule.params));
2141                }
2142                AtRuleKind::Forward => {
2143                    acc.sass_module_forward_sources
2144                        .extend(parse_sass_module_sources(&at_rule.params));
2145                }
2146                AtRuleKind::Import => {
2147                    acc.sass_module_import_sources
2148                        .extend(parse_sass_module_sources(&at_rule.params));
2149                    acc.sass_module_use_sources
2150                        .extend(parse_sass_module_sources(&at_rule.params));
2151                    acc.sass_module_use_edges
2152                        .extend(parse_sass_module_import_use_edges(&at_rule.params));
2153                }
2154                _ => {}
2155            },
2156            Some(SyntaxNodePayload::Declaration(declaration)) => {
2157                if is_css_custom_property_name(&declaration.property) {
2158                    acc.custom_property_decl_names
2159                        .push(declaration.property.clone());
2160                }
2161                if let Some(name) = parse_sass_variable_decl_name(&declaration.property) {
2162                    acc.sass_variable_decl_facts.push(SassVariableDeclFact {
2163                        name: name.clone(),
2164                        scope: current_sass_scope
2165                            .map(SassVariableScope::Span)
2166                            .unwrap_or(SassVariableScope::File),
2167                    });
2168                    acc.sass_variable_decl_names.push(name);
2169                }
2170            }
2171            _ => {}
2172        }
2173        if split_child_branches {
2174            for parent_branch in &next_parent_branches {
2175                collect_index_names(
2176                    &node.children,
2177                    acc,
2178                    std::slice::from_ref(parent_branch),
2179                    next_parent_is_grouped,
2180                    next_sass_scope,
2181                );
2182            }
2183        } else {
2184            collect_index_names(
2185                &node.children,
2186                acc,
2187                &next_parent_branches,
2188                next_parent_is_grouped,
2189                next_sass_scope,
2190            );
2191        }
2192    }
2193}
2194
2195fn collect_index_refs_and_counts(
2196    nodes: &[SyntaxNode],
2197    value_ref_ctx: ValueRefContext<'_>,
2198    known_keyframe_names: &BTreeSet<String>,
2199    acc: &mut IndexSummaryAcc,
2200) {
2201    for node in nodes {
2202        match &node.payload {
2203            Some(SyntaxNodePayload::Declaration(declaration)) => {
2204                acc.custom_property_ref_names
2205                    .extend(find_css_var_ref_names(&declaration.value));
2206                match classify_declaration_kind(&declaration.property) {
2207                    DeclarationKind::Composes => {}
2208                    DeclarationKind::Animation => {
2209                        acc.animation_ref_names.extend(find_identifier_matches(
2210                            &declaration.value,
2211                            known_keyframe_names,
2212                        ));
2213                        extend_value_ref_facts(
2214                            acc,
2215                            find_identifier_matches(&declaration.value, value_ref_ctx.known),
2216                            value_ref_ctx,
2217                            ValueRefOrigin::Declaration,
2218                        );
2219                    }
2220                    DeclarationKind::AnimationName => {
2221                        acc.animation_name_ref_names.extend(find_identifier_matches(
2222                            &declaration.value,
2223                            known_keyframe_names,
2224                        ));
2225                        extend_value_ref_facts(
2226                            acc,
2227                            find_identifier_matches(&declaration.value, value_ref_ctx.known),
2228                            value_ref_ctx,
2229                            ValueRefOrigin::Declaration,
2230                        );
2231                    }
2232                    DeclarationKind::Generic => {
2233                        extend_value_ref_facts(
2234                            acc,
2235                            find_identifier_matches(&declaration.value, value_ref_ctx.known),
2236                            value_ref_ctx,
2237                            ValueRefOrigin::Declaration,
2238                        );
2239                    }
2240                }
2241            }
2242            Some(SyntaxNodePayload::AtRule(at_rule)) if at_rule.kind == AtRuleKind::Value => {
2243                if let Some((name, value)) = parse_local_value_decl_parts(&at_rule.params) {
2244                    let value_refs: Vec<String> =
2245                        find_identifier_matches(value, value_ref_ctx.known)
2246                            .into_iter()
2247                            .filter(|candidate| candidate != name)
2248                            .collect();
2249                    if value_refs
2250                        .iter()
2251                        .any(|candidate| value_ref_ctx.local.contains(candidate))
2252                    {
2253                        acc.value_decl_names_with_local_refs.push(name.to_string());
2254                    }
2255                    if value_refs
2256                        .iter()
2257                        .any(|candidate| value_ref_ctx.imported.contains(candidate))
2258                    {
2259                        acc.value_decl_names_with_imported_refs
2260                            .push(name.to_string());
2261                    }
2262                    extend_value_ref_facts(
2263                        acc,
2264                        value_refs,
2265                        value_ref_ctx,
2266                        ValueRefOrigin::ValueDecl,
2267                    );
2268                }
2269            }
2270            _ => {}
2271        }
2272        collect_index_refs_and_counts(&node.children, value_ref_ctx, known_keyframe_names, acc);
2273    }
2274}
2275
2276fn collect_sass_ref_facts(
2277    nodes: &[SyntaxNode],
2278    source: &str,
2279    known_function_names: &BTreeSet<String>,
2280    acc: &mut IndexSummaryAcc,
2281) {
2282    for node in nodes {
2283        match &node.payload {
2284            Some(SyntaxNodePayload::Declaration(declaration)) => {
2285                let value_span = declaration_value_span(source, node);
2286                let variable_refs =
2287                    find_sass_variable_ref_spans(&declaration.value, value_span.start);
2288                acc.sass_variable_ref_names
2289                    .extend(variable_refs.iter().map(|span| span.name.clone()));
2290                acc.sass_variable_ref_facts
2291                    .extend(variable_refs.into_iter().map(|span| SassVariableRefFact {
2292                        name: span.name,
2293                        byte_span: span.byte_span,
2294                    }));
2295                acc.sass_function_call_names
2296                    .extend(find_sass_function_calls(
2297                        &declaration.value,
2298                        known_function_names,
2299                    ));
2300            }
2301            Some(SyntaxNodePayload::AtRule(at_rule)) => match at_rule.kind {
2302                AtRuleKind::Mixin | AtRuleKind::Function => {}
2303                _ => {
2304                    let params_span = at_rule_params_span(source, node, at_rule);
2305                    let variable_refs =
2306                        find_sass_variable_ref_spans(&at_rule.params, params_span.start);
2307                    acc.sass_variable_ref_names
2308                        .extend(variable_refs.iter().map(|span| span.name.clone()));
2309                    acc.sass_variable_ref_facts
2310                        .extend(variable_refs.into_iter().map(|span| SassVariableRefFact {
2311                            name: span.name,
2312                            byte_span: span.byte_span,
2313                        }));
2314                    acc.sass_function_call_names
2315                        .extend(find_sass_function_calls(
2316                            &at_rule.params,
2317                            known_function_names,
2318                        ));
2319                }
2320            },
2321            _ => {}
2322        }
2323        collect_sass_ref_facts(&node.children, source, known_function_names, acc);
2324    }
2325}
2326
2327fn summarize_sass_same_file_resolution(
2328    acc: &IndexSummaryAcc,
2329) -> ParserIndexSassSameFileResolutionFactsV0 {
2330    let mixin_targets: BTreeSet<&str> = acc
2331        .sass_mixin_decl_names
2332        .iter()
2333        .map(String::as_str)
2334        .collect();
2335    let function_targets: BTreeSet<&str> = acc
2336        .sass_function_decl_names
2337        .iter()
2338        .map(String::as_str)
2339        .collect();
2340
2341    let mut resolved_variable_ref_names = BTreeSet::new();
2342    let mut unresolved_variable_ref_names = BTreeSet::new();
2343    for fact in &acc.sass_variable_ref_facts {
2344        if resolve_sass_variable_ref(&fact.name, fact.byte_span, &acc.sass_variable_decl_facts) {
2345            resolved_variable_ref_names.insert(fact.name.clone());
2346        } else {
2347            unresolved_variable_ref_names.insert(fact.name.clone());
2348        }
2349    }
2350
2351    ParserIndexSassSameFileResolutionFactsV0 {
2352        resolved_variable_ref_names: resolved_variable_ref_names.into_iter().collect(),
2353        unresolved_variable_ref_names: unresolved_variable_ref_names.into_iter().collect(),
2354        resolved_mixin_include_names: names_matching(&acc.sass_mixin_include_names, &mixin_targets),
2355        unresolved_mixin_include_names: names_not_matching(
2356            &acc.sass_mixin_include_names,
2357            &mixin_targets,
2358        ),
2359        resolved_function_call_names: names_matching(
2360            &acc.sass_function_call_names,
2361            &function_targets,
2362        ),
2363    }
2364}
2365
2366fn names_matching(names: &[String], targets: &BTreeSet<&str>) -> Vec<String> {
2367    names
2368        .iter()
2369        .filter(|name| targets.contains(name.as_str()))
2370        .cloned()
2371        .collect()
2372}
2373
2374fn names_not_matching(names: &[String], targets: &BTreeSet<&str>) -> Vec<String> {
2375    names
2376        .iter()
2377        .filter(|name| !targets.contains(name.as_str()))
2378        .cloned()
2379        .collect()
2380}
2381
2382fn extend_value_ref_facts(
2383    acc: &mut IndexSummaryAcc,
2384    value_refs: Vec<String>,
2385    value_ref_ctx: ValueRefContext<'_>,
2386    origin: ValueRefOrigin,
2387) {
2388    acc.value_ref_names.extend(value_refs.iter().cloned());
2389
2390    let local_refs: Vec<String> = value_refs
2391        .iter()
2392        .filter(|name| value_ref_ctx.local.contains(*name))
2393        .cloned()
2394        .collect();
2395    acc.local_value_ref_names.extend(local_refs);
2396
2397    let imported_refs: Vec<String> = value_refs
2398        .iter()
2399        .filter(|name| value_ref_ctx.imported.contains(*name))
2400        .cloned()
2401        .collect();
2402    acc.imported_value_ref_names
2403        .extend(imported_refs.iter().cloned());
2404
2405    let imported_ref_sources: Vec<String> = imported_refs
2406        .iter()
2407        .filter_map(|name| acc.value_import_source_by_name.get(name))
2408        .cloned()
2409        .collect();
2410    acc.imported_value_ref_sources
2411        .extend(imported_ref_sources.iter().cloned());
2412
2413    match origin {
2414        ValueRefOrigin::Declaration => {
2415            acc.declaration_value_ref_names.extend(value_refs);
2416            acc.declaration_imported_value_ref_sources
2417                .extend(imported_ref_sources);
2418        }
2419        ValueRefOrigin::ValueDecl => {
2420            acc.value_decl_ref_names.extend(value_refs);
2421            acc.value_decl_imported_value_ref_sources
2422                .extend(imported_ref_sources);
2423        }
2424    }
2425}
2426
2427fn collect_index_selector_attachment_facts(
2428    nodes: &[SyntaxNode],
2429    ctx: SelectorAttachmentContext<'_>,
2430    acc: &mut IndexSummaryAcc,
2431    parent_branches: &[ResolvedSelectorBranch],
2432) {
2433    collect_index_selector_attachment_facts_with_context(
2434        nodes,
2435        ctx,
2436        acc,
2437        parent_branches,
2438        WrapperContext::default(),
2439    );
2440}
2441
2442fn collect_index_selector_attachment_facts_with_context(
2443    nodes: &[SyntaxNode],
2444    ctx: SelectorAttachmentContext<'_>,
2445    acc: &mut IndexSummaryAcc,
2446    parent_branches: &[ResolvedSelectorBranch],
2447    wrapper_ctx: WrapperContext,
2448) {
2449    for node in nodes {
2450        let mut next_parent_branches = parent_branches.to_vec();
2451        let mut split_child_branches = false;
2452        let mut child_wrapper_ctx = wrapper_ctx;
2453        if let Some(SyntaxNodePayload::Rule(rule)) = &node.payload {
2454            let resolved_branches = resolve_rule_selector_branches(rule, parent_branches);
2455            if !resolved_branches.is_empty() {
2456                let resolved: Vec<String> = resolved_branches
2457                    .iter()
2458                    .map(|branch| branch.name.clone())
2459                    .collect();
2460                let ref_facts = collect_rule_reference_facts(
2461                    &node.children,
2462                    ctx.source,
2463                    ctx.value_ref_ctx,
2464                    ctx.known_keyframe_names,
2465                    ctx.sass_ref_ctx,
2466                );
2467                if ref_facts.has_value_refs {
2468                    acc.selectors_with_value_refs_names
2469                        .extend(resolved.iter().cloned());
2470                    if wrapper_ctx.under_media {
2471                        acc.selectors_with_value_refs_under_media_names
2472                            .extend(resolved.iter().cloned());
2473                    }
2474                    if wrapper_ctx.under_supports {
2475                        acc.selectors_with_value_refs_under_supports_names
2476                            .extend(resolved.iter().cloned());
2477                    }
2478                    if wrapper_ctx.under_layer {
2479                        acc.selectors_with_value_refs_under_layer_names
2480                            .extend(resolved.iter().cloned());
2481                    }
2482                }
2483                if ref_facts.has_local_value_refs {
2484                    acc.selectors_with_local_value_refs_names
2485                        .extend(resolved.iter().cloned());
2486                    if wrapper_ctx.under_media {
2487                        acc.selectors_with_local_value_refs_under_media_names
2488                            .extend(resolved.iter().cloned());
2489                    }
2490                    if wrapper_ctx.under_supports {
2491                        acc.selectors_with_local_value_refs_under_supports_names
2492                            .extend(resolved.iter().cloned());
2493                    }
2494                    if wrapper_ctx.under_layer {
2495                        acc.selectors_with_local_value_refs_under_layer_names
2496                            .extend(resolved.iter().cloned());
2497                    }
2498                }
2499                if ref_facts.has_imported_value_refs {
2500                    acc.selectors_with_imported_value_refs_names
2501                        .extend(resolved.iter().cloned());
2502                    if wrapper_ctx.under_media {
2503                        acc.selectors_with_imported_value_refs_under_media_names
2504                            .extend(resolved.iter().cloned());
2505                    }
2506                    if wrapper_ctx.under_supports {
2507                        acc.selectors_with_imported_value_refs_under_supports_names
2508                            .extend(resolved.iter().cloned());
2509                    }
2510                    if wrapper_ctx.under_layer {
2511                        acc.selectors_with_imported_value_refs_under_layer_names
2512                            .extend(resolved.iter().cloned());
2513                    }
2514                }
2515                if ref_facts.has_animation_refs {
2516                    acc.selectors_with_animation_ref_names
2517                        .extend(resolved.iter().cloned());
2518                    if wrapper_ctx.under_media {
2519                        acc.selectors_with_animation_refs_under_media_names
2520                            .extend(resolved.iter().cloned());
2521                    }
2522                    if wrapper_ctx.under_supports {
2523                        acc.selectors_with_animation_refs_under_supports_names
2524                            .extend(resolved.iter().cloned());
2525                    }
2526                    if wrapper_ctx.under_layer {
2527                        acc.selectors_with_animation_refs_under_layer_names
2528                            .extend(resolved.iter().cloned());
2529                    }
2530                }
2531                if ref_facts.has_animation_name_refs {
2532                    acc.selectors_with_animation_name_ref_names
2533                        .extend(resolved.iter().cloned());
2534                    if wrapper_ctx.under_media {
2535                        acc.selectors_with_animation_name_refs_under_media_names
2536                            .extend(resolved.iter().cloned());
2537                    }
2538                    if wrapper_ctx.under_supports {
2539                        acc.selectors_with_animation_name_refs_under_supports_names
2540                            .extend(resolved.iter().cloned());
2541                    }
2542                    if wrapper_ctx.under_layer {
2543                        acc.selectors_with_animation_name_refs_under_layer_names
2544                            .extend(resolved.iter().cloned());
2545                    }
2546                }
2547                if ref_facts.has_custom_property_refs {
2548                    acc.selectors_with_custom_property_refs_names
2549                        .extend(resolved.iter().cloned());
2550                    if wrapper_ctx.under_media {
2551                        acc.selectors_with_custom_property_refs_under_media_names
2552                            .extend(resolved.iter().cloned());
2553                    }
2554                    if wrapper_ctx.under_supports {
2555                        acc.selectors_with_custom_property_refs_under_supports_names
2556                            .extend(resolved.iter().cloned());
2557                    }
2558                    if wrapper_ctx.under_layer {
2559                        acc.selectors_with_custom_property_refs_under_layer_names
2560                            .extend(resolved.iter().cloned());
2561                    }
2562                }
2563                if ref_facts.has_sass_variable_refs {
2564                    acc.sass_selectors_with_variable_refs_names
2565                        .extend(resolved.iter().cloned());
2566                }
2567                if ref_facts.has_resolved_sass_variable_refs {
2568                    acc.sass_selectors_with_resolved_variable_refs_names
2569                        .extend(resolved.iter().cloned());
2570                }
2571                if ref_facts.has_unresolved_sass_variable_refs {
2572                    acc.sass_selectors_with_unresolved_variable_refs_names
2573                        .extend(resolved.iter().cloned());
2574                }
2575                if ref_facts.has_sass_mixin_includes {
2576                    acc.sass_selectors_with_mixin_includes_names
2577                        .extend(resolved.iter().cloned());
2578                }
2579                if ref_facts.has_resolved_sass_mixin_includes {
2580                    acc.sass_selectors_with_resolved_mixin_includes_names
2581                        .extend(resolved.iter().cloned());
2582                }
2583                if ref_facts.has_unresolved_sass_mixin_includes {
2584                    acc.sass_selectors_with_unresolved_mixin_includes_names
2585                        .extend(resolved.iter().cloned());
2586                }
2587                if ref_facts.has_sass_function_calls {
2588                    acc.sass_selectors_with_function_calls_names
2589                        .extend(resolved.iter().cloned());
2590                }
2591                for selector_name in &resolved {
2592                    acc.sass_selector_symbol_facts
2593                        .extend(ref_facts.sass_symbol_facts.iter().map(|fact| {
2594                            ParserIndexSassSelectorSymbolFactV0 {
2595                                selector_name: selector_name.clone(),
2596                                symbol_kind: fact.symbol_kind,
2597                                name: fact.name.clone(),
2598                                role: fact.role,
2599                                resolution: fact.resolution,
2600                                byte_span: fact.byte_span,
2601                                range: source_range_for_byte_span(ctx.source, fact.byte_span),
2602                            }
2603                        }));
2604                }
2605                let composes_facts = collect_rule_composes_facts(&node.children);
2606                if composes_facts.local_class_name_count > 0
2607                    || composes_facts.imported_class_name_count > 0
2608                    || composes_facts.global_class_name_count > 0
2609                {
2610                    if wrapper_ctx.under_media {
2611                        acc.selectors_with_composes_under_media_names
2612                            .extend(resolved.iter().cloned());
2613                    }
2614                    if wrapper_ctx.under_supports {
2615                        acc.selectors_with_composes_under_supports_names
2616                            .extend(resolved.iter().cloned());
2617                    }
2618                    if wrapper_ctx.under_layer {
2619                        acc.selectors_with_composes_under_layer_names
2620                            .extend(resolved.iter().cloned());
2621                    }
2622                }
2623                if composes_facts.local_class_name_count > 0 {
2624                    if wrapper_ctx.under_media {
2625                        acc.local_composes_selector_names_under_media
2626                            .extend(resolved.iter().cloned());
2627                    }
2628                    if wrapper_ctx.under_supports {
2629                        acc.local_composes_selector_names_under_supports
2630                            .extend(resolved.iter().cloned());
2631                    }
2632                    if wrapper_ctx.under_layer {
2633                        acc.local_composes_selector_names_under_layer
2634                            .extend(resolved.iter().cloned());
2635                    }
2636                }
2637                if composes_facts.imported_class_name_count > 0 {
2638                    if wrapper_ctx.under_media {
2639                        acc.imported_composes_selector_names_under_media
2640                            .extend(resolved.iter().cloned());
2641                        acc.composes_import_sources_under_media.extend(
2642                            composes_facts.imported_sources.iter().flat_map(|source| {
2643                                std::iter::repeat_n(source.clone(), resolved.len())
2644                            }),
2645                        );
2646                    }
2647                    if wrapper_ctx.under_supports {
2648                        acc.imported_composes_selector_names_under_supports
2649                            .extend(resolved.iter().cloned());
2650                        acc.composes_import_sources_under_supports.extend(
2651                            composes_facts.imported_sources.iter().flat_map(|source| {
2652                                std::iter::repeat_n(source.clone(), resolved.len())
2653                            }),
2654                        );
2655                    }
2656                    if wrapper_ctx.under_layer {
2657                        acc.imported_composes_selector_names_under_layer
2658                            .extend(resolved.iter().cloned());
2659                        acc.composes_import_sources_under_layer.extend(
2660                            composes_facts.imported_sources.iter().flat_map(|source| {
2661                                std::iter::repeat_n(source.clone(), resolved.len())
2662                            }),
2663                        );
2664                    }
2665                }
2666                if composes_facts.global_class_name_count > 0 {
2667                    if wrapper_ctx.under_media {
2668                        acc.global_composes_selector_names_under_media
2669                            .extend(resolved.iter().cloned());
2670                    }
2671                    if wrapper_ctx.under_supports {
2672                        acc.global_composes_selector_names_under_supports
2673                            .extend(resolved.iter().cloned());
2674                    }
2675                    if wrapper_ctx.under_layer {
2676                        acc.global_composes_selector_names_under_layer
2677                            .extend(resolved.iter().cloned());
2678                    }
2679                }
2680                if wrapper_ctx.under_media {
2681                    acc.selectors_under_media_names
2682                        .extend(resolved.iter().cloned());
2683                }
2684                if wrapper_ctx.under_supports {
2685                    acc.selectors_under_supports_names
2686                        .extend(resolved.iter().cloned());
2687                }
2688                if wrapper_ctx.under_layer {
2689                    acc.selectors_under_layer_names
2690                        .extend(resolved.iter().cloned());
2691                }
2692                next_parent_branches = resolved_branches;
2693                split_child_branches = true;
2694            }
2695        } else if let Some(SyntaxNodePayload::AtRule(at_rule)) = &node.payload {
2696            if at_rule.kind == AtRuleKind::Keyframes && !at_rule.params.is_empty() {
2697                if wrapper_ctx.under_media {
2698                    acc.keyframes_names_under_media.push(at_rule.params.clone());
2699                }
2700                if wrapper_ctx.under_supports {
2701                    acc.keyframes_names_under_supports
2702                        .push(at_rule.params.clone());
2703                }
2704                if wrapper_ctx.under_layer {
2705                    acc.keyframes_names_under_layer.push(at_rule.params.clone());
2706                }
2707            }
2708            match at_rule.kind {
2709                AtRuleKind::Media => child_wrapper_ctx.under_media = true,
2710                AtRuleKind::Supports => child_wrapper_ctx.under_supports = true,
2711                AtRuleKind::Layer => child_wrapper_ctx.under_layer = true,
2712                _ => {}
2713            }
2714        }
2715
2716        if split_child_branches {
2717            for parent_branch in &next_parent_branches {
2718                collect_index_selector_attachment_facts_with_context(
2719                    &node.children,
2720                    ctx,
2721                    acc,
2722                    std::slice::from_ref(parent_branch),
2723                    child_wrapper_ctx,
2724                );
2725            }
2726        } else {
2727            collect_index_selector_attachment_facts_with_context(
2728                &node.children,
2729                ctx,
2730                acc,
2731                &next_parent_branches,
2732                child_wrapper_ctx,
2733            );
2734        }
2735    }
2736}
2737
2738fn parse_local_value_decl_parts(params: &str) -> Option<(&str, &str)> {
2739    if params.contains(" from ") {
2740        return None;
2741    }
2742    let (name, value) = params.split_once(':')?;
2743    let trimmed_name = name.trim();
2744    let trimmed_value = value.trim();
2745    if trimmed_name.is_empty() || trimmed_value.is_empty() {
2746        return None;
2747    }
2748    Some((trimmed_name, trimmed_value))
2749}
2750
2751struct ValueImportSpec {
2752    imported_name: String,
2753    local_name: String,
2754    from_source: Option<String>,
2755}
2756
2757fn parse_value_import_specs(params: &str) -> Option<Vec<ValueImportSpec>> {
2758    let (raw_specs, raw_source) = params.split_once(" from ")?;
2759    let from_source = parse_quoted_import_source(raw_source);
2760    let mut specs = Vec::new();
2761    for raw_spec in raw_specs.split(',') {
2762        let trimmed = raw_spec.trim();
2763        if trimmed.is_empty() {
2764            continue;
2765        }
2766        let imported_name = trimmed
2767            .split_once(" as ")
2768            .map(|(imported, _)| imported.trim())
2769            .unwrap_or(trimmed);
2770        let local_name = trimmed
2771            .split_once(" as ")
2772            .map(|(_, local)| local.trim())
2773            .unwrap_or(trimmed);
2774        if !imported_name.is_empty() && !local_name.is_empty() {
2775            specs.push(ValueImportSpec {
2776                imported_name: imported_name.to_string(),
2777                local_name: local_name.to_string(),
2778                from_source: from_source.clone(),
2779            });
2780        }
2781    }
2782    (!specs.is_empty()).then_some(specs)
2783}
2784
2785fn parse_composes_spec(value: &str) -> Option<ComposesSpec> {
2786    let head = value
2787        .split_once(" from ")
2788        .map(|(left, _)| left)
2789        .unwrap_or(value);
2790    let class_names: Vec<String> = head
2791        .split_whitespace()
2792        .filter(|name| !name.is_empty())
2793        .map(ToString::to_string)
2794        .collect();
2795    if class_names.is_empty() {
2796        return None;
2797    }
2798    let from_source = value
2799        .split_once(" from ")
2800        .and_then(|(_, source)| parse_quoted_import_source(source));
2801    let kind = match value.split_once(" from ").map(|(_, source)| source.trim()) {
2802        Some("global") => ComposesKind::Global,
2803        Some(_) => ComposesKind::Imported,
2804        None => ComposesKind::Local,
2805    };
2806    Some(ComposesSpec {
2807        class_names,
2808        kind,
2809        from_source,
2810    })
2811}
2812
2813fn parse_quoted_import_source(raw: &str) -> Option<String> {
2814    let trimmed = raw.trim();
2815    if trimmed.len() < 2 {
2816        return None;
2817    }
2818    let quote = trimmed.chars().next()?;
2819    if !matches!(quote, '"' | '\'') || !trimmed.ends_with(quote) {
2820        return None;
2821    }
2822    Some(trimmed[1..trimmed.len() - 1].to_string())
2823}
2824
2825fn parse_sass_variable_decl_name(property: &str) -> Option<String> {
2826    let name = property.trim().strip_prefix('$')?;
2827    (!name.is_empty() && name.chars().all(is_sass_ident_continue)).then(|| name.to_string())
2828}
2829
2830fn parse_sass_callable_name(params: &str) -> Option<String> {
2831    parse_sass_callable_name_with_span(params, 0).map(|span| span.name)
2832}
2833
2834fn parse_sass_callable_name_with_span(params: &str, base_start: usize) -> Option<SassNameSpan> {
2835    let trimmed_start = params.find(|ch: char| !ch.is_whitespace())?;
2836    let trimmed = &params[trimmed_start..];
2837    let end = trimmed
2838        .find(|ch: char| ch.is_whitespace() || ch == '(')
2839        .unwrap_or(trimmed.len());
2840    let name = &trimmed[..end];
2841    (!name.is_empty() && name.chars().all(is_sass_ident_continue)).then(|| SassNameSpan {
2842        name: name.to_string(),
2843        byte_span: ParserByteSpanV0 {
2844            start: base_start + trimmed_start,
2845            end: base_start + trimmed_start + end,
2846        },
2847    })
2848}
2849
2850fn parse_sass_parameter_names(params: &str) -> Vec<String> {
2851    let Some(open_index) = params.find('(') else {
2852        return Vec::new();
2853    };
2854    let close_index = params[open_index + 1..]
2855        .find(')')
2856        .map(|index| open_index + 1 + index)
2857        .unwrap_or(params.len());
2858    find_sass_variable_refs(&params[open_index + 1..close_index])
2859}
2860
2861fn parse_sass_module_sources(params: &str) -> Vec<String> {
2862    let mut sources = Vec::new();
2863    let chars: Vec<char> = params.chars().collect();
2864    let mut index = 0usize;
2865
2866    while index < chars.len() {
2867        let quote = chars[index];
2868        if !matches!(quote, '"' | '\'') {
2869            index += 1;
2870            continue;
2871        }
2872        index += 1;
2873        let start = index;
2874        while index < chars.len() {
2875            if chars[index] == '\\' {
2876                index = (index + 2).min(chars.len());
2877                continue;
2878            }
2879            if chars[index] == quote {
2880                break;
2881            }
2882            index += 1;
2883        }
2884        if start < index {
2885            sources.push(chars[start..index].iter().collect());
2886        }
2887        index += usize::from(index < chars.len());
2888    }
2889
2890    sources
2891}
2892
2893fn parse_sass_module_use_edges(params: &str) -> Vec<ParserIndexSassModuleUseFactV0> {
2894    let alias = parse_sass_use_alias(params);
2895    parse_sass_module_sources(params)
2896        .into_iter()
2897        .map(|source| match alias.as_deref() {
2898            Some("*") => ParserIndexSassModuleUseFactV0 {
2899                source,
2900                namespace_kind: "wildcard",
2901                namespace: None,
2902            },
2903            Some(namespace) if is_valid_sass_namespace(namespace) => {
2904                ParserIndexSassModuleUseFactV0 {
2905                    source,
2906                    namespace_kind: "alias",
2907                    namespace: Some(namespace.to_string()),
2908                }
2909            }
2910            _ => ParserIndexSassModuleUseFactV0 {
2911                namespace: default_sass_namespace_for_source(&source),
2912                source,
2913                namespace_kind: "default",
2914            },
2915        })
2916        .collect()
2917}
2918
2919fn parse_sass_module_import_use_edges(params: &str) -> Vec<ParserIndexSassModuleUseFactV0> {
2920    parse_sass_module_sources(params)
2921        .into_iter()
2922        .map(|source| ParserIndexSassModuleUseFactV0 {
2923            source,
2924            namespace_kind: "wildcard",
2925            namespace: None,
2926        })
2927        .collect()
2928}
2929
2930fn parse_sass_use_alias(params: &str) -> Option<String> {
2931    let chars: Vec<char> = params.chars().collect();
2932    let mut tokens = Vec::new();
2933    let mut current = String::new();
2934    let mut index = 0usize;
2935    let mut quote: Option<char> = None;
2936
2937    while index < chars.len() {
2938        let ch = chars[index];
2939        if let Some(active_quote) = quote {
2940            if ch == '\\' && index + 1 < chars.len() {
2941                index += 2;
2942                continue;
2943            }
2944            if ch == active_quote {
2945                quote = None;
2946            }
2947            index += 1;
2948            continue;
2949        }
2950
2951        if ch == '"' || ch == '\'' {
2952            if !current.is_empty() {
2953                tokens.push(std::mem::take(&mut current));
2954            }
2955            quote = Some(ch);
2956            index += 1;
2957            continue;
2958        }
2959
2960        if ch.is_whitespace() || ch == ';' || ch == ',' {
2961            if !current.is_empty() {
2962                tokens.push(std::mem::take(&mut current));
2963            }
2964            index += 1;
2965            continue;
2966        }
2967
2968        current.push(ch);
2969        index += 1;
2970    }
2971
2972    if !current.is_empty() {
2973        tokens.push(current);
2974    }
2975
2976    tokens
2977        .windows(2)
2978        .find_map(|window| (window[0].eq_ignore_ascii_case("as")).then(|| window[1].clone()))
2979}
2980
2981fn default_sass_namespace_for_source(source: &str) -> Option<String> {
2982    let clean = source
2983        .split(['?', '#'])
2984        .next()
2985        .unwrap_or(source)
2986        .trim_end_matches('/');
2987    let segment = clean.rsplit('/').next().unwrap_or(clean);
2988    let package_segment = segment.rsplit(':').next().unwrap_or(segment);
2989    let stem = package_segment
2990        .rsplit_once('.')
2991        .map(|(stem, _)| stem)
2992        .unwrap_or(package_segment);
2993    let stem = stem.strip_prefix('_').unwrap_or(stem);
2994
2995    is_valid_sass_namespace(stem).then(|| stem.to_string())
2996}
2997
2998fn is_valid_sass_namespace(value: &str) -> bool {
2999    let mut chars = value.chars();
3000    let Some(first) = chars.next() else {
3001        return false;
3002    };
3003    is_sass_ident_start(first) && chars.all(is_sass_ident_continue)
3004}
3005
3006fn find_sass_variable_refs(raw: &str) -> Vec<String> {
3007    find_sass_variable_ref_spans(raw, 0)
3008        .into_iter()
3009        .map(|span| span.name)
3010        .collect()
3011}
3012
3013fn find_sass_variable_ref_spans(raw: &str, base_start: usize) -> Vec<SassNameSpan> {
3014    let mut refs = Vec::new();
3015    let mut chars = raw.char_indices().peekable();
3016    let mut quote: Option<char> = None;
3017
3018    while let Some((byte_index, ch)) = chars.next() {
3019        if let Some(active_quote) = quote {
3020            if ch == '\\' {
3021                chars.next();
3022                continue;
3023            }
3024            if ch == active_quote {
3025                quote = None;
3026            }
3027            continue;
3028        }
3029
3030        if ch == '"' || ch == '\'' {
3031            quote = Some(ch);
3032            continue;
3033        }
3034
3035        if ch == '$' {
3036            if is_sass_module_qualified_reference(raw, byte_index) {
3037                continue;
3038            }
3039            let name_start = byte_index + ch.len_utf8();
3040            let mut name_end = name_start;
3041            let mut name = String::new();
3042            while let Some(&(next_index, next_ch)) = chars.peek() {
3043                if !is_sass_ident_continue(next_ch) {
3044                    break;
3045                }
3046                name.push(next_ch);
3047                name_end = next_index + next_ch.len_utf8();
3048                chars.next();
3049            }
3050            if !name.is_empty() {
3051                refs.push(SassNameSpan {
3052                    name,
3053                    byte_span: ParserByteSpanV0 {
3054                        start: base_start + byte_index,
3055                        end: base_start + name_end,
3056                    },
3057                });
3058            }
3059        }
3060    }
3061
3062    refs
3063}
3064
3065fn find_sass_function_calls(raw: &str, known_function_names: &BTreeSet<String>) -> Vec<String> {
3066    find_sass_function_call_spans(raw, 0, known_function_names)
3067        .into_iter()
3068        .map(|span| span.name)
3069        .collect()
3070}
3071
3072fn find_sass_function_call_spans(
3073    raw: &str,
3074    base_start: usize,
3075    known_function_names: &BTreeSet<String>,
3076) -> Vec<SassNameSpan> {
3077    let mut calls = Vec::new();
3078    let mut chars = raw.char_indices().peekable();
3079    let mut quote: Option<char> = None;
3080
3081    while let Some((byte_index, ch)) = chars.next() {
3082        if let Some(active_quote) = quote {
3083            if ch == '\\' {
3084                chars.next();
3085                continue;
3086            }
3087            if ch == active_quote {
3088                quote = None;
3089            }
3090            continue;
3091        }
3092
3093        if ch == '"' || ch == '\'' {
3094            quote = Some(ch);
3095            continue;
3096        }
3097
3098        if is_sass_ident_start(ch) {
3099            if is_sass_module_qualified_reference(raw, byte_index) {
3100                continue;
3101            }
3102            let mut name = String::from(ch);
3103            let mut name_end = byte_index + ch.len_utf8();
3104            while let Some(&(next_index, next_ch)) = chars.peek() {
3105                if !is_sass_ident_continue(next_ch) {
3106                    break;
3107                }
3108                name.push(next_ch);
3109                name_end = next_index + next_ch.len_utf8();
3110                chars.next();
3111            }
3112            while let Some(&(_, next_ch)) = chars.peek() {
3113                if !next_ch.is_whitespace() {
3114                    break;
3115                }
3116                chars.next();
3117            }
3118            if matches!(chars.peek(), Some(&(_, '('))) && known_function_names.contains(&name) {
3119                calls.push(SassNameSpan {
3120                    name,
3121                    byte_span: ParserByteSpanV0 {
3122                        start: base_start + byte_index,
3123                        end: base_start + name_end,
3124                    },
3125                });
3126            }
3127        }
3128    }
3129
3130    calls
3131}
3132
3133fn is_sass_module_qualified_reference(raw: &str, start: usize) -> bool {
3134    if start <= 1 || !raw[..start].ends_with('.') {
3135        return false;
3136    }
3137    let namespace = raw[..start - 1]
3138        .rsplit(|ch: char| !is_sass_ident_continue(ch))
3139        .next()
3140        .unwrap_or("");
3141    is_valid_sass_namespace(namespace)
3142}
3143
3144fn find_identifier_matches(raw: &str, known_names: &BTreeSet<String>) -> Vec<String> {
3145    let mut matches = Vec::new();
3146    let chars: Vec<char> = raw.chars().collect();
3147    let mut index = 0usize;
3148    let mut quote: Option<char> = None;
3149    let mut identifier_start: Option<usize> = None;
3150
3151    let flush_identifier =
3152        |end: usize, identifier_start: &mut Option<usize>, matches: &mut Vec<String>| {
3153            if let Some(start) = *identifier_start {
3154                let candidate: String = chars[start..end].iter().collect();
3155                if known_names.contains(&candidate) {
3156                    matches.push(candidate);
3157                }
3158                *identifier_start = None;
3159            }
3160        };
3161
3162    while index < chars.len() {
3163        let ch = chars[index];
3164        if let Some(active_quote) = quote {
3165            if ch == '\\' && index + 1 < chars.len() {
3166                index += 2;
3167                continue;
3168            }
3169            if ch == active_quote {
3170                quote = None;
3171            }
3172            index += 1;
3173            continue;
3174        }
3175
3176        if ch == '"' || ch == '\'' {
3177            flush_identifier(index, &mut identifier_start, &mut matches);
3178            quote = Some(ch);
3179            index += 1;
3180            continue;
3181        }
3182
3183        if is_value_ident_continue(ch) {
3184            if identifier_start.is_none() {
3185                identifier_start = Some(index);
3186            }
3187            index += 1;
3188            continue;
3189        }
3190
3191        flush_identifier(index, &mut identifier_start, &mut matches);
3192        index += 1;
3193    }
3194
3195    flush_identifier(chars.len(), &mut identifier_start, &mut matches);
3196    matches
3197}
3198
3199fn is_value_ident_continue(ch: char) -> bool {
3200    ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-' | '$')
3201}
3202
3203fn find_css_var_ref_names(raw: &str) -> Vec<String> {
3204    let mut refs = Vec::new();
3205    let mut search_start = 0usize;
3206
3207    while let Some(relative_start) = raw[search_start..].find("var(") {
3208        let function_start = search_start + relative_start;
3209        if !is_css_function_boundary(raw, function_start) {
3210            search_start = function_start + "var(".len();
3211            continue;
3212        }
3213
3214        let mut cursor = function_start + "var(".len();
3215        while cursor < raw.len() {
3216            let Some(ch) = raw[cursor..].chars().next() else {
3217                break;
3218            };
3219            if !ch.is_whitespace() {
3220                break;
3221            }
3222            cursor += ch.len_utf8();
3223        }
3224
3225        if !raw[cursor..].starts_with("--") {
3226            search_start = cursor;
3227            continue;
3228        }
3229
3230        let name_start = cursor;
3231        let mut name_end = cursor;
3232        for (relative_index, ch) in raw[name_start..].char_indices() {
3233            let absolute_index = name_start + relative_index;
3234            if relative_index == 0 {
3235                name_end = absolute_index + ch.len_utf8();
3236                continue;
3237            }
3238            if !is_css_custom_property_ident_continue(ch) {
3239                break;
3240            }
3241            name_end = absolute_index + ch.len_utf8();
3242        }
3243
3244        let candidate = &raw[name_start..name_end];
3245        if is_css_custom_property_name(candidate) {
3246            refs.push(candidate.to_string());
3247        }
3248        search_start = name_end.max(function_start + "var(".len());
3249    }
3250
3251    refs
3252}
3253
3254fn is_css_function_boundary(raw: &str, function_start: usize) -> bool {
3255    raw[..function_start]
3256        .chars()
3257        .next_back()
3258        .is_none_or(|ch| !ch.is_ascii_alphanumeric() && ch != '_')
3259}
3260
3261fn is_css_custom_property_name(name: &str) -> bool {
3262    let Some(rest) = name.strip_prefix("--") else {
3263        return false;
3264    };
3265    let mut chars = rest.chars();
3266    let Some(first) = chars.next() else {
3267        return false;
3268    };
3269    is_css_custom_property_ident_start(first) && chars.all(is_css_custom_property_ident_continue)
3270}
3271
3272fn is_css_custom_property_ident_start(ch: char) -> bool {
3273    ch == '_' || ch == '-' || ch.is_ascii_alphabetic() || !ch.is_ascii()
3274}
3275
3276fn is_css_custom_property_ident_continue(ch: char) -> bool {
3277    is_css_custom_property_ident_start(ch) || ch.is_ascii_digit()
3278}
3279
3280fn is_sass_ident_start(ch: char) -> bool {
3281    ch.is_ascii_alphabetic() || ch == '_' || ch == '-' || !ch.is_ascii()
3282}
3283
3284fn is_sass_ident_continue(ch: char) -> bool {
3285    is_sass_ident_start(ch) || ch.is_ascii_digit()
3286}
3287
3288fn extract_simple_selector_name(prelude: &str) -> Option<String> {
3289    let trimmed = prelude.trim();
3290    let rest = trimmed.strip_prefix('.')?;
3291    if rest.is_empty() {
3292        return None;
3293    }
3294    if rest.contains([' ', ',', ':', '&', '#', '[', '>', '+', '~']) {
3295        return None;
3296    }
3297    Some(rest.to_string())
3298}
3299
3300fn resolve_rule_selector_branches(
3301    rule: &RulePayload,
3302    parent_branches: &[ResolvedSelectorBranch],
3303) -> Vec<ResolvedSelectorBranch> {
3304    let mut branches = Vec::new();
3305
3306    for group in &rule.selector_groups {
3307        let resolved = extract_group_selector_branches(group, parent_branches);
3308        if !resolved.is_empty() {
3309            branches.extend(resolved);
3310        } else if let Some(local_names) = extract_local_function_selector_names(&group.raw) {
3311            branches.extend(repeat_names_for_parent_branches(
3312                local_names,
3313                parent_branches,
3314                false,
3315            ));
3316        } else if let Some(name) = extract_simple_selector_name(&group.raw) {
3317            branches.extend(repeat_names_for_parent_branches(
3318                vec![name],
3319                parent_branches,
3320                true,
3321            ));
3322        }
3323    }
3324
3325    branches
3326}
3327
3328fn repeat_names_for_parent_branches(
3329    names: Vec<String>,
3330    parent_branches: &[ResolvedSelectorBranch],
3331    bare_when_top_level_single: bool,
3332) -> Vec<ResolvedSelectorBranch> {
3333    if names.is_empty() {
3334        return Vec::new();
3335    }
3336    if parent_branches.is_empty() {
3337        let bare_suffix_base = bare_when_top_level_single && names.len() == 1;
3338        return names
3339            .into_iter()
3340            .map(|name| ResolvedSelectorBranch {
3341                name,
3342                bare_suffix_base,
3343            })
3344            .collect();
3345    }
3346    if parent_branches.len() == 1 {
3347        return names
3348            .into_iter()
3349            .map(|name| ResolvedSelectorBranch {
3350                name,
3351                bare_suffix_base: false,
3352            })
3353            .collect();
3354    }
3355    let mut repeated = Vec::with_capacity(names.len() * parent_branches.len());
3356    for _ in parent_branches {
3357        repeated.extend(names.iter().cloned().map(|name| ResolvedSelectorBranch {
3358            name,
3359            bare_suffix_base: false,
3360        }));
3361    }
3362    repeated
3363}
3364
3365fn extract_group_selector_branches(
3366    group: &SelectorGroup,
3367    parent_branches: &[ResolvedSelectorBranch],
3368) -> Vec<ResolvedSelectorBranch> {
3369    match group.segments.as_slice() {
3370        [SelectorSegment::ClassName(name)] => {
3371            repeat_names_for_parent_branches(vec![name.clone()], parent_branches, true)
3372        }
3373        [
3374            SelectorSegment::Ampersand,
3375            SelectorSegment::BemSuffix(suffix),
3376        ] => parent_branches
3377            .iter()
3378            .filter(|parent| parent.bare_suffix_base)
3379            .map(|parent| ResolvedSelectorBranch {
3380                name: format!("{}{}", parent.name, suffix),
3381                bare_suffix_base: true,
3382            })
3383            .collect(),
3384        [
3385            SelectorSegment::Ampersand,
3386            SelectorSegment::AmpersandSuffix(suffix),
3387        ] => parent_branches
3388            .iter()
3389            .filter(|parent| parent.bare_suffix_base)
3390            .map(|parent| ResolvedSelectorBranch {
3391                name: format!("{}{}", parent.name, suffix),
3392                bare_suffix_base: true,
3393            })
3394            .collect(),
3395        [SelectorSegment::Ampersand, SelectorSegment::ClassName(name)] => {
3396            repeat_names_for_parent_branches(vec![name.clone()], parent_branches, false)
3397        }
3398        segments => {
3399            let last_combinator = segments
3400                .iter()
3401                .enumerate()
3402                .rev()
3403                .find_map(|(index, segment)| {
3404                    matches!(segment, SelectorSegment::Combinator(_)).then_some(index)
3405                });
3406            let tail = last_combinator
3407                .map(|index| &segments[index + 1..])
3408                .unwrap_or(segments);
3409            if let [
3410                SelectorSegment::Ampersand,
3411                SelectorSegment::BemSuffix(suffix) | SelectorSegment::AmpersandSuffix(suffix),
3412            ] = tail
3413            {
3414                return parent_branches
3415                    .iter()
3416                    .filter(|parent| parent.bare_suffix_base)
3417                    .map(|parent| ResolvedSelectorBranch {
3418                        name: format!("{}{}", parent.name, suffix),
3419                        bare_suffix_base: false,
3420                    })
3421                    .collect();
3422            }
3423            let names: Vec<String> = tail
3424                .iter()
3425                .filter_map(|segment| match segment {
3426                    SelectorSegment::ClassName(name) => Some(name.clone()),
3427                    _ => None,
3428                })
3429                .collect();
3430            repeat_names_for_parent_branches(names, parent_branches, false)
3431        }
3432    }
3433}
3434
3435fn extract_local_function_selector_names(raw: &str) -> Option<Vec<String>> {
3436    let trimmed = raw.trim();
3437    let inner = trimmed
3438        .strip_prefix(":local(")
3439        .and_then(|rest| rest.strip_suffix(')'))?;
3440    let mut names = Vec::new();
3441    let chars: Vec<char> = inner.chars().collect();
3442    let mut index = 0usize;
3443
3444    while index < chars.len() {
3445        if chars[index] == '.' {
3446            index += 1;
3447            let start = index;
3448            while index < chars.len() && is_selector_ident_continue(chars[index]) {
3449                index += 1;
3450            }
3451            if start < index {
3452                names.push(chars[start..index].iter().collect());
3453                continue;
3454            }
3455        }
3456        index += 1;
3457    }
3458
3459    (!names.is_empty()).then_some(names)
3460}
3461
3462fn parse_selector_groups(prelude: &str) -> Vec<SelectorGroup> {
3463    let mut groups = Vec::new();
3464    let mut depth_paren = 0usize;
3465    let mut depth_bracket = 0usize;
3466    let mut start = 0usize;
3467
3468    for (index, ch) in prelude.char_indices() {
3469        match ch {
3470            '(' => depth_paren += 1,
3471            ')' => depth_paren = depth_paren.saturating_sub(1),
3472            '[' => depth_bracket += 1,
3473            ']' => depth_bracket = depth_bracket.saturating_sub(1),
3474            ',' if depth_paren == 0 && depth_bracket == 0 => {
3475                let raw = prelude[start..index].trim();
3476                if !raw.is_empty() {
3477                    groups.push(SelectorGroup {
3478                        raw: raw.to_string(),
3479                        segments: parse_selector_segments(raw),
3480                    });
3481                }
3482                start = index + ch.len_utf8();
3483            }
3484            _ => {}
3485        }
3486    }
3487
3488    let raw = prelude[start..].trim();
3489    if !raw.is_empty() {
3490        groups.push(SelectorGroup {
3491            raw: raw.to_string(),
3492            segments: parse_selector_segments(raw),
3493        });
3494    }
3495
3496    groups
3497}
3498
3499fn parse_selector_segments(raw: &str) -> Vec<SelectorSegment> {
3500    let chars: Vec<char> = raw.chars().collect();
3501    let mut index = 0usize;
3502    let mut segments = Vec::new();
3503
3504    while index < chars.len() {
3505        match chars[index] {
3506            c if c.is_whitespace() => {
3507                let start = index;
3508                while index < chars.len() && chars[index].is_whitespace() {
3509                    index += 1;
3510                }
3511                let next = chars.get(index).copied();
3512                let has_prev = segments.last().is_some();
3513                let prev_is_combinator =
3514                    matches!(segments.last(), Some(SelectorSegment::Combinator(_)));
3515                let next_starts_selector = next.is_some_and(|ch| {
3516                    matches!(ch, '.' | '&' | ':' | '#' | '*' | '[') || ch.is_ascii_alphabetic()
3517                });
3518                if start > 0 && has_prev && !prev_is_combinator && next_starts_selector {
3519                    segments.push(SelectorSegment::Combinator(" ".to_string()));
3520                }
3521            }
3522            '.' => {
3523                index += 1;
3524                let start = index;
3525                while index < chars.len() && is_selector_ident_continue(chars[index]) {
3526                    index += 1;
3527                }
3528                if start < index {
3529                    segments.push(SelectorSegment::ClassName(
3530                        chars[start..index].iter().collect(),
3531                    ));
3532                } else {
3533                    segments.push(SelectorSegment::Other(".".to_string()));
3534                }
3535            }
3536            '&' => {
3537                segments.push(SelectorSegment::Ampersand);
3538                index += 1;
3539                if index + 1 < chars.len()
3540                    && ((chars[index] == '-' && chars[index + 1] == '-')
3541                        || (chars[index] == '_' && chars[index + 1] == '_'))
3542                {
3543                    let start = index;
3544                    index += 2;
3545                    while index < chars.len() && is_selector_ident_continue(chars[index]) {
3546                        index += 1;
3547                    }
3548                    segments.push(SelectorSegment::BemSuffix(
3549                        chars[start..index].iter().collect(),
3550                    ));
3551                } else if index < chars.len()
3552                    && !starts_selector_component_after_ampersand(chars[index])
3553                {
3554                    let start = index;
3555                    while index < chars.len() && is_selector_ident_continue(chars[index]) {
3556                        index += 1;
3557                    }
3558                    if start < index {
3559                        segments.push(SelectorSegment::AmpersandSuffix(
3560                            chars[start..index].iter().collect(),
3561                        ));
3562                    }
3563                }
3564            }
3565            ':' => {
3566                index += 1;
3567                let start = index;
3568                while index < chars.len() && is_selector_ident_continue(chars[index]) {
3569                    index += 1;
3570                }
3571                segments.push(SelectorSegment::Pseudo(
3572                    chars[start..index].iter().collect(),
3573                ));
3574                if index < chars.len() && chars[index] == '(' {
3575                    let mut depth = 1usize;
3576                    index += 1;
3577                    while index < chars.len() && depth > 0 {
3578                        match chars[index] {
3579                            '(' => depth += 1,
3580                            ')' => depth = depth.saturating_sub(1),
3581                            '\'' | '"' => {
3582                                let quote = chars[index];
3583                                index += 1;
3584                                while index < chars.len() {
3585                                    if chars[index] == '\\' {
3586                                        index += 2;
3587                                        continue;
3588                                    }
3589                                    if chars[index] == quote {
3590                                        break;
3591                                    }
3592                                    index += 1;
3593                                }
3594                            }
3595                            _ => {}
3596                        }
3597                        index += 1;
3598                    }
3599                }
3600            }
3601            '>' | '+' | '~' => {
3602                segments.push(SelectorSegment::Combinator(chars[index].to_string()));
3603                index += 1;
3604            }
3605            _ => {
3606                let start = index;
3607                index += 1;
3608                while index < chars.len()
3609                    && !chars[index].is_whitespace()
3610                    && !matches!(chars[index], '.' | '&' | ':' | '>' | '+' | '~' | ',')
3611                {
3612                    index += 1;
3613                }
3614                segments.push(SelectorSegment::Other(chars[start..index].iter().collect()));
3615            }
3616        }
3617    }
3618
3619    segments
3620}
3621
3622fn is_selector_ident_continue(ch: char) -> bool {
3623    ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-') || !ch.is_ascii()
3624}
3625
3626fn starts_selector_component_after_ampersand(ch: char) -> bool {
3627    matches!(
3628        ch,
3629        ':' | '.' | '[' | '#' | '>' | '+' | '~' | ',' | '*' | ')' | '}' | ']'
3630    ) || ch.is_whitespace()
3631}
3632
3633fn tokenize(language: StyleLanguage, source: &str) -> (Vec<Token>, Vec<ParseDiagnostic>) {
3634    let mut tokens = Vec::new();
3635    let mut diagnostics = Vec::new();
3636    let bytes = source.as_bytes();
3637    let mut i = 0;
3638
3639    while i < bytes.len() {
3640        let start = i;
3641        let byte = bytes[i];
3642
3643        if byte.is_ascii_whitespace() {
3644            i += 1;
3645            while i < bytes.len() && bytes[i].is_ascii_whitespace() {
3646                i += 1;
3647            }
3648            tokens.push(Token {
3649                kind: TokenKind::Whitespace,
3650                span: TextSpan::new(start, i),
3651            });
3652            continue;
3653        }
3654
3655        if language.supports_line_comments() && byte == b'/' && bytes.get(i + 1) == Some(&b'/') {
3656            i += 2;
3657            while i < bytes.len() && bytes[i] != b'\n' {
3658                i += 1;
3659            }
3660            tokens.push(Token {
3661                kind: TokenKind::LineComment,
3662                span: TextSpan::new(start, i),
3663            });
3664            continue;
3665        }
3666
3667        if byte == b'/' && bytes.get(i + 1) == Some(&b'*') {
3668            i += 2;
3669            let mut closed = false;
3670            while i + 1 < bytes.len() {
3671                if bytes[i] == b'*' && bytes[i + 1] == b'/' {
3672                    i += 2;
3673                    closed = true;
3674                    break;
3675                }
3676                i += 1;
3677            }
3678            if !closed {
3679                i = bytes.len();
3680                diagnostics.push(ParseDiagnostic {
3681                    message: "unterminated block comment".to_string(),
3682                    span: TextSpan::new(start, i),
3683                });
3684            }
3685            tokens.push(Token {
3686                kind: TokenKind::BlockComment,
3687                span: TextSpan::new(start, i),
3688            });
3689            continue;
3690        }
3691
3692        if byte == b'"' || byte == b'\'' {
3693            let quote = byte;
3694            i += 1;
3695            let mut closed = false;
3696            while i < bytes.len() {
3697                if bytes[i] == b'\\' {
3698                    i = (i + 2).min(bytes.len());
3699                    continue;
3700                }
3701                if bytes[i] == quote {
3702                    i += 1;
3703                    closed = true;
3704                    break;
3705                }
3706                i += 1;
3707            }
3708            if !closed {
3709                diagnostics.push(ParseDiagnostic {
3710                    message: "unterminated string literal".to_string(),
3711                    span: TextSpan::new(start, i),
3712                });
3713            }
3714            tokens.push(Token {
3715                kind: TokenKind::String,
3716                span: TextSpan::new(start, i),
3717            });
3718            continue;
3719        }
3720
3721        if byte == b'#' && bytes.get(i + 1) == Some(&b'{') {
3722            i += 2;
3723            tokens.push(Token {
3724                kind: TokenKind::InterpolationStart,
3725                span: TextSpan::new(start, i),
3726            });
3727            continue;
3728        }
3729
3730        if is_ident_start(byte) {
3731            i += 1;
3732            while i < bytes.len() && is_ident_continue(bytes[i]) {
3733                i += 1;
3734            }
3735            tokens.push(Token {
3736                kind: TokenKind::Ident,
3737                span: TextSpan::new(start, i),
3738            });
3739            continue;
3740        }
3741
3742        if byte.is_ascii_digit() {
3743            i += 1;
3744            while i < bytes.len() && bytes[i].is_ascii_digit() {
3745                i += 1;
3746            }
3747            tokens.push(Token {
3748                kind: TokenKind::Number,
3749                span: TextSpan::new(start, i),
3750            });
3751            continue;
3752        }
3753
3754        let kind = match byte {
3755            b'.' => TokenKind::Dot,
3756            b'&' => TokenKind::Ampersand,
3757            b'#' => TokenKind::Hash,
3758            b':' => TokenKind::Colon,
3759            b';' => TokenKind::Semicolon,
3760            b',' => TokenKind::Comma,
3761            b'@' => TokenKind::At,
3762            b'{' => TokenKind::OpenBrace,
3763            b'}' => TokenKind::CloseBrace,
3764            b'(' => TokenKind::OpenParen,
3765            b')' => TokenKind::CloseParen,
3766            b'[' => TokenKind::OpenBracket,
3767            b']' => TokenKind::CloseBracket,
3768            _ => TokenKind::Other,
3769        };
3770        i += 1;
3771        tokens.push(Token {
3772            kind,
3773            span: TextSpan::new(start, i),
3774        });
3775    }
3776
3777    (tokens, diagnostics)
3778}
3779
3780fn is_ident_start(byte: u8) -> bool {
3781    byte.is_ascii_alphabetic() || matches!(byte, b'_' | b'-') || byte >= 0x80
3782}
3783
3784fn is_ident_continue(byte: u8) -> bool {
3785    is_ident_start(byte) || byte.is_ascii_digit()
3786}
3787
3788struct Parser<'a> {
3789    source: &'a str,
3790    tokens: &'a [Token],
3791    diagnostics: &'a mut Vec<ParseDiagnostic>,
3792    cursor: usize,
3793}
3794
3795impl<'a> Parser<'a> {
3796    fn new(
3797        source: &'a str,
3798        tokens: &'a [Token],
3799        diagnostics: &'a mut Vec<ParseDiagnostic>,
3800    ) -> Self {
3801        Self {
3802            source,
3803            tokens,
3804            diagnostics,
3805            cursor: 0,
3806        }
3807    }
3808
3809    fn parse_root(&mut self) -> Vec<SyntaxNode> {
3810        self.parse_block(false)
3811    }
3812
3813    fn parse_block(&mut self, stop_at_close_brace: bool) -> Vec<SyntaxNode> {
3814        let mut nodes = Vec::new();
3815
3816        while self.cursor < self.tokens.len() {
3817            let token = &self.tokens[self.cursor];
3818
3819            match token.kind {
3820                TokenKind::Whitespace => {
3821                    self.cursor += 1;
3822                }
3823                TokenKind::LineComment | TokenKind::BlockComment => {
3824                    nodes.push(SyntaxNode {
3825                        kind: SyntaxNodeKind::Comment,
3826                        span: token.span,
3827                        header_span: None,
3828                        payload: Some(SyntaxNodePayload::Comment(CommentPayload {
3829                            text: self.slice(token.span).to_string(),
3830                        })),
3831                        children: Vec::new(),
3832                    });
3833                    self.cursor += 1;
3834                }
3835                TokenKind::CloseBrace if stop_at_close_brace => {
3836                    self.cursor += 1;
3837                    return nodes;
3838                }
3839                TokenKind::CloseBrace => {
3840                    self.diagnostics.push(ParseDiagnostic {
3841                        message: "unexpected closing brace".to_string(),
3842                        span: token.span,
3843                    });
3844                    self.cursor += 1;
3845                }
3846                _ => nodes.push(self.parse_statement()),
3847            }
3848        }
3849
3850        if stop_at_close_brace {
3851            let end = self.tokens.last().map_or(0, |token| token.span.end);
3852            self.diagnostics.push(ParseDiagnostic {
3853                message: "unterminated block".to_string(),
3854                span: TextSpan::new(end, end),
3855            });
3856        }
3857
3858        nodes
3859    }
3860
3861    fn parse_statement(&mut self) -> SyntaxNode {
3862        let start_index = self.cursor;
3863        let mut index = self.cursor;
3864        let mut saw_at = self.tokens[index].kind == TokenKind::At;
3865        let mut saw_colon = false;
3866        let mut first_colon_index = None;
3867        let mut paren_depth = 0usize;
3868        let mut bracket_depth = 0usize;
3869
3870        while index < self.tokens.len() {
3871            let token = &self.tokens[index];
3872            match token.kind {
3873                TokenKind::OpenParen => paren_depth += 1,
3874                TokenKind::CloseParen => paren_depth = paren_depth.saturating_sub(1),
3875                TokenKind::OpenBracket => bracket_depth += 1,
3876                TokenKind::CloseBracket => bracket_depth = bracket_depth.saturating_sub(1),
3877                TokenKind::Colon if paren_depth == 0 && bracket_depth == 0 => {
3878                    saw_colon = true;
3879                    if first_colon_index.is_none() {
3880                        first_colon_index = Some(index);
3881                    }
3882                }
3883                TokenKind::At if index == start_index => saw_at = true,
3884                TokenKind::Semicolon if paren_depth == 0 && bracket_depth == 0 => {
3885                    let span = TextSpan::new(
3886                        self.tokens[start_index].span.start,
3887                        self.tokens[index].span.end,
3888                    );
3889                    self.cursor = index + 1;
3890                    return SyntaxNode {
3891                        kind: classify_statement_kind(saw_at, saw_colon),
3892                        span,
3893                        header_span: Some(TextSpan::new(
3894                            self.tokens[start_index].span.start,
3895                            self.tokens[index].span.start,
3896                        )),
3897                        payload: self.build_inline_payload(
3898                            start_index,
3899                            index,
3900                            saw_at,
3901                            first_colon_index,
3902                        ),
3903                        children: Vec::new(),
3904                    };
3905                }
3906                TokenKind::OpenBrace if paren_depth == 0 && bracket_depth == 0 => {
3907                    let header_span = TextSpan::new(
3908                        self.tokens[start_index].span.start,
3909                        self.tokens[index].span.start,
3910                    );
3911                    self.cursor = index + 1;
3912                    let children = self.parse_block(true);
3913                    let end = self
3914                        .tokens
3915                        .get(self.cursor.saturating_sub(1))
3916                        .map_or(self.tokens[index].span.end, |token| token.span.end);
3917                    return SyntaxNode {
3918                        kind: if saw_at {
3919                            SyntaxNodeKind::AtRule
3920                        } else {
3921                            SyntaxNodeKind::Rule
3922                        },
3923                        span: TextSpan::new(self.tokens[start_index].span.start, end),
3924                        header_span: Some(header_span),
3925                        payload: Some(if saw_at {
3926                            SyntaxNodePayload::AtRule(
3927                                self.build_at_rule_payload(start_index, index),
3928                            )
3929                        } else {
3930                            let prelude = self.slice_trimmed(header_span).to_string();
3931                            SyntaxNodePayload::Rule(RulePayload {
3932                                selector_groups: parse_selector_groups(&prelude),
3933                                prelude,
3934                            })
3935                        }),
3936                        children,
3937                    };
3938                }
3939                TokenKind::CloseBrace => break,
3940                _ => {}
3941            }
3942            index += 1;
3943        }
3944
3945        let end = self
3946            .tokens
3947            .get(index.saturating_sub(1))
3948            .map_or(self.tokens[start_index].span.end, |token| token.span.end);
3949        self.cursor = index.max(start_index + 1);
3950        let span = TextSpan::new(self.tokens[start_index].span.start, end);
3951        SyntaxNode {
3952            kind: classify_statement_kind(saw_at, saw_colon),
3953            span,
3954            header_span: Some(span),
3955            payload: self.build_inline_payload(start_index, index, saw_at, first_colon_index),
3956            children: Vec::new(),
3957        }
3958    }
3959
3960    fn build_inline_payload(
3961        &self,
3962        start_index: usize,
3963        end_index: usize,
3964        saw_at: bool,
3965        first_colon_index: Option<usize>,
3966    ) -> Option<SyntaxNodePayload> {
3967        if saw_at {
3968            return Some(SyntaxNodePayload::AtRule(
3969                self.build_at_rule_payload(start_index, end_index),
3970            ));
3971        }
3972
3973        let colon_index = first_colon_index?;
3974        let property_span = TextSpan::new(
3975            self.tokens[start_index].span.start,
3976            self.tokens[colon_index].span.start,
3977        );
3978        let value_start = self.tokens[colon_index].span.end;
3979        let value_end = self
3980            .tokens
3981            .get(end_index.saturating_sub(1))
3982            .map_or(value_start, |token| token.span.end);
3983        Some(SyntaxNodePayload::Declaration(DeclarationPayload {
3984            property: self.slice_trimmed(property_span).to_string(),
3985            value: self
3986                .slice_trimmed(TextSpan::new(value_start, value_end))
3987                .to_string(),
3988        }))
3989    }
3990
3991    fn build_at_rule_payload(&self, start_index: usize, end_index: usize) -> AtRulePayload {
3992        let name = self
3993            .tokens
3994            .get(start_index + 1)
3995            .map(|token| self.slice(token.span))
3996            .unwrap_or_default()
3997            .trim()
3998            .to_string();
3999        let params_start = self
4000            .tokens
4001            .get(start_index + 2)
4002            .map_or(self.tokens[start_index].span.end, |token| token.span.start);
4003        let params_end = self
4004            .tokens
4005            .get(end_index.saturating_sub(1))
4006            .map_or(params_start, |token| token.span.end);
4007        AtRulePayload {
4008            kind: classify_at_rule_kind(&name),
4009            name,
4010            params: self
4011                .slice_trimmed(TextSpan::new(params_start, params_end))
4012                .to_string(),
4013        }
4014    }
4015
4016    fn slice(&self, span: TextSpan) -> &'a str {
4017        &self.source[span.start..span.end]
4018    }
4019
4020    fn slice_trimmed(&self, span: TextSpan) -> &'a str {
4021        self.slice(span).trim()
4022    }
4023}
4024
4025fn classify_statement_kind(saw_at: bool, saw_colon: bool) -> SyntaxNodeKind {
4026    if saw_at {
4027        SyntaxNodeKind::AtRule
4028    } else if saw_colon {
4029        SyntaxNodeKind::Declaration
4030    } else {
4031        SyntaxNodeKind::Unknown
4032    }
4033}
4034
4035fn classify_at_rule_kind(name: &str) -> AtRuleKind {
4036    match name {
4037        "media" => AtRuleKind::Media,
4038        "supports" => AtRuleKind::Supports,
4039        "layer" => AtRuleKind::Layer,
4040        "keyframes" | "-webkit-keyframes" => AtRuleKind::Keyframes,
4041        "value" => AtRuleKind::Value,
4042        "at-root" => AtRuleKind::AtRoot,
4043        "mixin" => AtRuleKind::Mixin,
4044        "include" => AtRuleKind::Include,
4045        "function" => AtRuleKind::Function,
4046        "use" => AtRuleKind::Use,
4047        "forward" => AtRuleKind::Forward,
4048        "import" => AtRuleKind::Import,
4049        _ => AtRuleKind::Generic,
4050    }
4051}
4052
4053#[cfg(test)]
4054mod tests {
4055    use super::{
4056        AtRuleKind, AtRulePayload, DeclarationPayload, ParserByteSpanV0,
4057        ParserIndexSassModuleUseFactV0, ParserIndexSassSelectorSymbolFactV0, ParserPositionV0,
4058        ParserRangeV0, RulePayload, SelectorGroup, SelectorSegment, StyleLanguage, SyntaxNodeKind,
4059        SyntaxNodePayload, TextSpan, TokenKind, parse_stylesheet,
4060    };
4061
4062    fn token_texts<'a>(source: &'a str, sheet: &super::Stylesheet) -> Vec<(TokenKind, &'a str)> {
4063        sheet
4064            .tokens
4065            .iter()
4066            .map(|token| (token.kind, &source[token.span.start..token.span.end]))
4067            .collect()
4068    }
4069
4070    fn span_after(source: &str, anchor: &str, needle: &str) -> Result<ParserByteSpanV0, String> {
4071        let anchor_start = source
4072            .find(anchor)
4073            .ok_or_else(|| format!("missing anchor {anchor:?}"))?;
4074        let relative_start = source[anchor_start..]
4075            .find(needle)
4076            .ok_or_else(|| format!("missing needle {needle:?} after {anchor:?}"))?;
4077        let start = anchor_start + relative_start;
4078        Ok(ParserByteSpanV0 {
4079            start,
4080            end: start + needle.len(),
4081        })
4082    }
4083
4084    fn range_after(source: &str, anchor: &str, needle: &str) -> Result<ParserRangeV0, String> {
4085        span_after(source, anchor, needle)
4086            .map(|span| super::source_range_for_byte_span(source, span))
4087    }
4088
4089    #[test]
4090    fn detects_style_language_from_module_path() {
4091        assert_eq!(
4092            StyleLanguage::from_module_path("/x/Button.module.css"),
4093            Some(StyleLanguage::Css)
4094        );
4095        assert_eq!(
4096            StyleLanguage::from_module_path("/x/Button.module.scss"),
4097            Some(StyleLanguage::Scss)
4098        );
4099        assert_eq!(
4100            StyleLanguage::from_module_path("/x/Button.module.less"),
4101            Some(StyleLanguage::Less)
4102        );
4103        assert_eq!(StyleLanguage::from_module_path("/x/Button.css"), None);
4104    }
4105
4106    #[test]
4107    fn tokenizes_basic_css_rule() {
4108        let source = ".button { color: red; }";
4109        let sheet = parse_stylesheet(StyleLanguage::Css, source);
4110        let tokens = token_texts(source, &sheet);
4111        assert!(tokens.contains(&(TokenKind::Dot, ".")));
4112        assert!(tokens.contains(&(TokenKind::Ident, "button")));
4113        assert!(tokens.contains(&(TokenKind::OpenBrace, "{")));
4114        assert!(tokens.contains(&(TokenKind::Semicolon, ";")));
4115        assert!(sheet.diagnostics.is_empty());
4116    }
4117
4118    #[test]
4119    fn keeps_css_double_slash_as_regular_tokens() {
4120        let source = ".button { // not-a-comment\n color: red; }";
4121        let sheet = parse_stylesheet(StyleLanguage::Css, source);
4122        assert!(
4123            !sheet
4124                .tokens
4125                .iter()
4126                .any(|token| matches!(token.kind, TokenKind::LineComment))
4127        );
4128    }
4129
4130    #[test]
4131    fn parses_scss_nested_rules_and_comments() {
4132        let source = ".button {\n  // note\n  &--primary { color: red; }\n}\n";
4133        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4134        assert_eq!(sheet.nodes.len(), 1);
4135        let root_rule = &sheet.nodes[0];
4136        assert_eq!(root_rule.kind, SyntaxNodeKind::Rule);
4137        assert_eq!(
4138            root_rule.payload,
4139            Some(SyntaxNodePayload::Rule(RulePayload {
4140                prelude: ".button".to_string(),
4141                selector_groups: vec![SelectorGroup {
4142                    raw: ".button".to_string(),
4143                    segments: vec![SelectorSegment::ClassName("button".to_string())],
4144                }],
4145            }))
4146        );
4147        assert_eq!(root_rule.children.len(), 2);
4148        assert_eq!(root_rule.children[0].kind, SyntaxNodeKind::Comment);
4149        assert_eq!(
4150            root_rule.children[0].payload,
4151            Some(SyntaxNodePayload::Comment(super::CommentPayload {
4152                text: "// note".to_string(),
4153            }))
4154        );
4155        assert_eq!(root_rule.children[1].kind, SyntaxNodeKind::Rule);
4156        assert_eq!(
4157            root_rule.children[1].payload,
4158            Some(SyntaxNodePayload::Rule(RulePayload {
4159                prelude: "&--primary".to_string(),
4160                selector_groups: vec![SelectorGroup {
4161                    raw: "&--primary".to_string(),
4162                    segments: vec![
4163                        SelectorSegment::Ampersand,
4164                        SelectorSegment::BemSuffix("--primary".to_string()),
4165                    ],
4166                }],
4167            }))
4168        );
4169        assert_eq!(
4170            root_rule.children[1].children[0].payload,
4171            Some(SyntaxNodePayload::Declaration(DeclarationPayload {
4172                property: "color".to_string(),
4173                value: "red".to_string(),
4174            }))
4175        );
4176        assert!(sheet.diagnostics.is_empty());
4177    }
4178
4179    #[test]
4180    fn parses_less_at_rule_like_variable_assignment() {
4181        let source = "@color: red;\n.button { color: @color; }";
4182        let sheet = parse_stylesheet(StyleLanguage::Less, source);
4183        assert_eq!(sheet.nodes[0].kind, SyntaxNodeKind::AtRule);
4184        assert_eq!(
4185            sheet.nodes[0].payload,
4186            Some(SyntaxNodePayload::AtRule(AtRulePayload {
4187                kind: AtRuleKind::Generic,
4188                name: "color".to_string(),
4189                params: ": red".to_string(),
4190            }))
4191        );
4192        assert_eq!(sheet.nodes[1].kind, SyntaxNodeKind::Rule);
4193    }
4194
4195    #[test]
4196    fn parses_at_rule_header_and_params() {
4197        let source = "@media screen and (min-width: 10px) { .button { color: red; } }";
4198        let sheet = parse_stylesheet(StyleLanguage::Css, source);
4199        assert_eq!(
4200            sheet.nodes[0].payload,
4201            Some(SyntaxNodePayload::AtRule(AtRulePayload {
4202                kind: AtRuleKind::Media,
4203                name: "media".to_string(),
4204                params: "screen and (min-width: 10px)".to_string(),
4205            }))
4206        );
4207    }
4208
4209    #[test]
4210    fn classifies_keyframes_and_value_at_rules() {
4211        let source = "@value brand: red;\n@keyframes fade { from { opacity: 0; } }\n";
4212        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4213        assert_eq!(
4214            sheet.nodes[0].payload,
4215            Some(SyntaxNodePayload::AtRule(AtRulePayload {
4216                kind: AtRuleKind::Value,
4217                name: "value".to_string(),
4218                params: "brand: red".to_string(),
4219            }))
4220        );
4221        assert_eq!(
4222            sheet.nodes[1].payload,
4223            Some(SyntaxNodePayload::AtRule(AtRulePayload {
4224                kind: AtRuleKind::Keyframes,
4225                name: "keyframes".to_string(),
4226                params: "fade".to_string(),
4227            }))
4228        );
4229    }
4230
4231    #[test]
4232    fn classifies_sass_symbol_at_rules() {
4233        let source = r#"@use "./tokens" as tokens;
4234@forward "./theme";
4235@import "./legacy";
4236@mixin raised($depth) { color: red; }
4237@include raised($gap);
4238@function tone($value) { @return $value; }
4239"#;
4240        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4241        let kinds: Vec<AtRuleKind> = sheet
4242            .nodes
4243            .iter()
4244            .filter_map(|node| match &node.payload {
4245                Some(SyntaxNodePayload::AtRule(at_rule)) => Some(at_rule.kind),
4246                _ => None,
4247            })
4248            .collect();
4249        assert_eq!(
4250            kinds,
4251            vec![
4252                AtRuleKind::Use,
4253                AtRuleKind::Forward,
4254                AtRuleKind::Import,
4255                AtRuleKind::Mixin,
4256                AtRuleKind::Include,
4257                AtRuleKind::Function,
4258            ]
4259        );
4260    }
4261
4262    #[test]
4263    fn records_unterminated_block_comment_diagnostic() {
4264        let source = "/* open";
4265        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4266        assert_eq!(sheet.diagnostics.len(), 1);
4267        assert_eq!(sheet.diagnostics[0].message, "unterminated block comment");
4268        assert_eq!(sheet.diagnostics[0].span, TextSpan::new(0, source.len()));
4269    }
4270
4271    #[test]
4272    fn records_unterminated_block_diagnostic() {
4273        let source = ".button { color: red;";
4274        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4275        assert_eq!(sheet.nodes.len(), 1);
4276        assert_eq!(sheet.nodes[0].kind, SyntaxNodeKind::Rule);
4277        assert!(
4278            sheet
4279                .diagnostics
4280                .iter()
4281                .any(|diagnostic| diagnostic.message == "unterminated block")
4282        );
4283    }
4284
4285    #[test]
4286    fn splits_grouped_selectors_into_groups_and_segments() {
4287        let source = ".a, .b { &--c { color: red; } }";
4288        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4289        assert_eq!(
4290            sheet.nodes[0].payload,
4291            Some(SyntaxNodePayload::Rule(RulePayload {
4292                prelude: ".a, .b".to_string(),
4293                selector_groups: vec![
4294                    SelectorGroup {
4295                        raw: ".a".to_string(),
4296                        segments: vec![SelectorSegment::ClassName("a".to_string())],
4297                    },
4298                    SelectorGroup {
4299                        raw: ".b".to_string(),
4300                        segments: vec![SelectorSegment::ClassName("b".to_string())],
4301                    },
4302                ],
4303            }))
4304        );
4305    }
4306
4307    #[test]
4308    fn parity_summary_reconstructs_bem_suffix_names() {
4309        let source = ".card { &__icon { &--small { color: red; } } }";
4310        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4311        let summary = super::summarize_parity_lite(&sheet);
4312        assert_eq!(
4313            summary.selector_names,
4314            vec!["card", "card__icon", "card__icon--small"]
4315        );
4316    }
4317
4318    #[test]
4319    fn parity_summary_expands_grouped_parent_bem_suffixes() {
4320        let source = ".a, .b { &--c { color: red; } }";
4321        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4322        let summary = super::summarize_parity_lite(&sheet);
4323        assert_eq!(summary.selector_names, vec!["a", "a--c", "b", "b--c"]);
4324    }
4325
4326    #[test]
4327    fn parity_summary_expands_grouped_parent_nested_bem_suffixes() {
4328        let source = ".a, .b { &__icon { &--small { color: red; } } }";
4329        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4330        let summary = super::summarize_parity_lite(&sheet);
4331        assert_eq!(
4332            summary.selector_names,
4333            vec![
4334                "a",
4335                "a__icon",
4336                "a__icon--small",
4337                "b",
4338                "b__icon",
4339                "b__icon--small"
4340            ]
4341        );
4342    }
4343
4344    #[test]
4345    fn parity_summary_keeps_ampersand_class_as_standalone_class() {
4346        let source = ".a, .b { &.active { color: red; } }";
4347        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4348        let summary = super::summarize_parity_lite(&sheet);
4349        assert_eq!(summary.selector_names, vec!["a", "active", "active", "b"]);
4350    }
4351
4352    #[test]
4353    fn index_summary_matches_nested_bem_safety_matrix() {
4354        let source = ".btn { &--primary {} &__icon {} &.active {} &-legacy {} &_legacy {} & + &--paired {} }";
4355        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4356        let summary = super::summarize_css_modules_intermediate(&sheet);
4357        assert_eq!(
4358            summary.selectors.names,
4359            vec![
4360                "active",
4361                "btn",
4362                "btn--paired",
4363                "btn--primary",
4364                "btn-legacy",
4365                "btn__icon",
4366                "btn_legacy"
4367            ]
4368        );
4369        assert_eq!(
4370            summary.selectors.bem_suffix_safe_names,
4371            vec!["btn--primary", "btn__icon"]
4372        );
4373        assert_eq!(
4374            summary.selectors.nested_unsafe_names,
4375            vec!["active", "btn--paired", "btn-legacy", "btn_legacy"]
4376        );
4377    }
4378
4379    #[test]
4380    fn index_summary_does_not_bem_expand_non_bare_parent_suffixes() {
4381        let source = ".card:hover { &--primary {} }";
4382        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4383        let summary = super::summarize_css_modules_intermediate(&sheet);
4384        assert_eq!(summary.selectors.names, vec!["card"]);
4385        assert_eq!(summary.selectors.bem_suffix_count, 0);
4386    }
4387
4388    #[test]
4389    fn index_summary_marks_plain_nested_selectors_unsafe() {
4390        let source = ".wrapper { .inner { &--mod {} } }";
4391        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4392        let summary = super::summarize_css_modules_intermediate(&sheet);
4393        assert_eq!(summary.selectors.names, vec!["inner", "wrapper"]);
4394        assert_eq!(summary.selectors.nested_unsafe_names, vec!["inner"]);
4395        assert_eq!(summary.selectors.nested_safety_counts.flat, 1);
4396        assert_eq!(summary.selectors.nested_safety_counts.nested_unsafe, 1);
4397    }
4398
4399    #[test]
4400    fn index_summary_allows_unsafe_suffix_to_become_deep_bem_base() {
4401        let source = ".btn { &-legacy { &--x {} } }";
4402        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4403        let summary = super::summarize_css_modules_intermediate(&sheet);
4404        assert_eq!(
4405            summary.selectors.names,
4406            vec!["btn", "btn-legacy", "btn-legacy--x"]
4407        );
4408        assert_eq!(
4409            summary.selectors.bem_suffix_safe_names,
4410            vec!["btn-legacy--x"]
4411        );
4412        assert_eq!(summary.selectors.nested_unsafe_names, vec!["btn-legacy"]);
4413    }
4414
4415    #[test]
4416    fn parity_summary_keeps_class_from_pseudo_selector() {
4417        let source = ".btn:hover { color: red; }";
4418        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4419        let summary = super::summarize_parity_lite(&sheet);
4420        assert_eq!(summary.selector_names, vec!["btn"]);
4421    }
4422
4423    #[test]
4424    fn parity_summary_keeps_multiple_classes_from_compound_selector() {
4425        let source = ".btn.active { color: red; }";
4426        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4427        let summary = super::summarize_parity_lite(&sheet);
4428        assert_eq!(summary.selector_names, vec!["active", "btn"]);
4429    }
4430
4431    #[test]
4432    fn parity_summary_prefers_rightmost_class_after_combinator() {
4433        let source = ".a > .b { color: red; }";
4434        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4435        let summary = super::summarize_parity_lite(&sheet);
4436        assert_eq!(summary.selector_names, vec!["b"]);
4437    }
4438
4439    #[test]
4440    fn parity_summary_collects_nested_layer_rule_selectors() {
4441        let source = "@layer ui { .btn:hover { color: red; } }";
4442        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4443        let summary = super::summarize_parity_lite(&sheet);
4444        assert_eq!(summary.selector_names, vec!["btn"]);
4445    }
4446
4447    #[test]
4448    fn parity_summary_prefers_rightmost_class_after_descendant_combinator() {
4449        let source = ".a .b { color: red; }";
4450        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4451        let summary = super::summarize_parity_lite(&sheet);
4452        assert_eq!(summary.selector_names, vec!["b"]);
4453    }
4454
4455    #[test]
4456    fn parity_summary_ignores_classes_inside_pseudo_functions() {
4457        let source = ".btn:is(.active, .primary) { color: red; }";
4458        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4459        let summary = super::summarize_parity_lite(&sheet);
4460        assert_eq!(summary.selector_names, vec!["btn"]);
4461    }
4462
4463    #[test]
4464    fn parity_summary_ignores_global_function_classes() {
4465        let source = ":global(.foo) { color: red; }";
4466        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4467        let summary = super::summarize_parity_lite(&sheet);
4468        assert!(summary.selector_names.is_empty());
4469    }
4470
4471    #[test]
4472    fn parity_summary_ignores_not_function_classes() {
4473        let source = ".btn:not(.disabled) { color: red; }";
4474        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4475        let summary = super::summarize_parity_lite(&sheet);
4476        assert_eq!(summary.selector_names, vec!["btn"]);
4477    }
4478
4479    #[test]
4480    fn parity_summary_keeps_local_function_class() {
4481        let source = ":local(.foo) { color: red; }";
4482        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4483        let summary = super::summarize_parity_lite(&sheet);
4484        assert_eq!(summary.selector_names, vec!["foo"]);
4485    }
4486
4487    #[test]
4488    fn index_summary_collects_sass_symbol_seed_facts() -> Result<(), String> {
4489        let source = r#"@use "./plain";
4490@use "./reset" as *;
4491@use "./tokens" as tokens;
4492@use "sass:color";
4493@forward "./theme";
4494@import "./legacy";
4495$gap: 1rem;
4496@mixin raised($depth) { box-shadow: 0 0 $depth black; }
4497@function tone($value) { @return $value; }
4498.btn { color: $gap; @include raised($gap); border-color: tone($gap); }
4499.ghost { color: $missing; @include absent($gap); }
4500"#;
4501        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4502        let summary = super::summarize_css_modules_intermediate(&sheet);
4503
4504        assert_eq!(summary.sass.variable_decl_names, vec!["gap"]);
4505        assert_eq!(
4506            summary.sass.variable_parameter_names,
4507            vec!["depth", "value"]
4508        );
4509        assert_eq!(
4510            summary.sass.variable_ref_names,
4511            vec!["depth", "gap", "missing", "value"]
4512        );
4513        assert_eq!(
4514            summary.sass.selectors_with_variable_refs_names,
4515            vec!["btn", "ghost"]
4516        );
4517        assert_eq!(
4518            summary.sass.selectors_with_resolved_variable_refs_names,
4519            vec!["btn", "ghost"]
4520        );
4521        assert_eq!(
4522            summary.sass.selectors_with_unresolved_variable_refs_names,
4523            vec!["ghost"]
4524        );
4525        assert_eq!(summary.sass.mixin_decl_names, vec!["raised"]);
4526        assert_eq!(summary.sass.mixin_include_names, vec!["absent", "raised"]);
4527        assert_eq!(
4528            summary.sass.selectors_with_mixin_includes_names,
4529            vec!["btn", "ghost"]
4530        );
4531        assert_eq!(
4532            summary.sass.selectors_with_resolved_mixin_includes_names,
4533            vec!["btn"]
4534        );
4535        assert_eq!(
4536            summary.sass.selectors_with_unresolved_mixin_includes_names,
4537            vec!["ghost"]
4538        );
4539        assert_eq!(summary.sass.function_decl_names, vec!["tone"]);
4540        assert_eq!(summary.sass.function_call_names, vec!["tone"]);
4541        assert_eq!(
4542            summary.sass.selectors_with_function_calls_names,
4543            vec!["btn"]
4544        );
4545        assert_eq!(
4546            summary.sass.selector_symbol_facts,
4547            vec![
4548                ParserIndexSassSelectorSymbolFactV0 {
4549                    selector_name: "btn".to_string(),
4550                    symbol_kind: "function",
4551                    name: "tone".to_string(),
4552                    role: "call",
4553                    resolution: "resolved",
4554                    byte_span: span_after(source, "border-color:", "tone")?,
4555                    range: range_after(source, "border-color:", "tone")?,
4556                },
4557                ParserIndexSassSelectorSymbolFactV0 {
4558                    selector_name: "btn".to_string(),
4559                    symbol_kind: "mixin",
4560                    name: "raised".to_string(),
4561                    role: "include",
4562                    resolution: "resolved",
4563                    byte_span: span_after(source, "@include", "raised")?,
4564                    range: range_after(source, "@include", "raised")?,
4565                },
4566                ParserIndexSassSelectorSymbolFactV0 {
4567                    selector_name: "btn".to_string(),
4568                    symbol_kind: "variable",
4569                    name: "gap".to_string(),
4570                    role: "reference",
4571                    resolution: "resolved",
4572                    byte_span: span_after(source, ".btn { color", "$gap")?,
4573                    range: range_after(source, ".btn { color", "$gap")?,
4574                },
4575                ParserIndexSassSelectorSymbolFactV0 {
4576                    selector_name: "btn".to_string(),
4577                    symbol_kind: "variable",
4578                    name: "gap".to_string(),
4579                    role: "reference",
4580                    resolution: "resolved",
4581                    byte_span: span_after(source, "@include raised(", "$gap")?,
4582                    range: range_after(source, "@include raised(", "$gap")?,
4583                },
4584                ParserIndexSassSelectorSymbolFactV0 {
4585                    selector_name: "btn".to_string(),
4586                    symbol_kind: "variable",
4587                    name: "gap".to_string(),
4588                    role: "reference",
4589                    resolution: "resolved",
4590                    byte_span: span_after(source, "border-color: tone(", "$gap")?,
4591                    range: range_after(source, "border-color: tone(", "$gap")?,
4592                },
4593                ParserIndexSassSelectorSymbolFactV0 {
4594                    selector_name: "ghost".to_string(),
4595                    symbol_kind: "mixin",
4596                    name: "absent".to_string(),
4597                    role: "include",
4598                    resolution: "unresolved",
4599                    byte_span: span_after(source, ".ghost", "absent")?,
4600                    range: range_after(source, ".ghost", "absent")?,
4601                },
4602                ParserIndexSassSelectorSymbolFactV0 {
4603                    selector_name: "ghost".to_string(),
4604                    symbol_kind: "variable",
4605                    name: "gap".to_string(),
4606                    role: "reference",
4607                    resolution: "resolved",
4608                    byte_span: span_after(source, "absent(", "$gap")?,
4609                    range: range_after(source, "absent(", "$gap")?,
4610                },
4611                ParserIndexSassSelectorSymbolFactV0 {
4612                    selector_name: "ghost".to_string(),
4613                    symbol_kind: "variable",
4614                    name: "missing".to_string(),
4615                    role: "reference",
4616                    resolution: "unresolved",
4617                    byte_span: span_after(source, ".ghost { color", "$missing")?,
4618                    range: range_after(source, ".ghost { color", "$missing")?,
4619                },
4620            ]
4621        );
4622        assert_eq!(
4623            summary.sass.module_use_sources,
4624            vec!["./legacy", "./plain", "./reset", "./tokens", "sass:color"]
4625        );
4626        assert_eq!(
4627            summary.sass.module_use_edges,
4628            vec![
4629                ParserIndexSassModuleUseFactV0 {
4630                    source: "./legacy".to_string(),
4631                    namespace_kind: "wildcard",
4632                    namespace: None,
4633                },
4634                ParserIndexSassModuleUseFactV0 {
4635                    source: "./plain".to_string(),
4636                    namespace_kind: "default",
4637                    namespace: Some("plain".to_string()),
4638                },
4639                ParserIndexSassModuleUseFactV0 {
4640                    source: "./reset".to_string(),
4641                    namespace_kind: "wildcard",
4642                    namespace: None,
4643                },
4644                ParserIndexSassModuleUseFactV0 {
4645                    source: "./tokens".to_string(),
4646                    namespace_kind: "alias",
4647                    namespace: Some("tokens".to_string()),
4648                },
4649                ParserIndexSassModuleUseFactV0 {
4650                    source: "sass:color".to_string(),
4651                    namespace_kind: "default",
4652                    namespace: Some("color".to_string()),
4653                },
4654            ]
4655        );
4656        assert_eq!(summary.sass.module_forward_sources, vec!["./theme"]);
4657        assert_eq!(summary.sass.module_import_sources, vec!["./legacy"]);
4658        assert_eq!(
4659            summary
4660                .sass
4661                .same_file_resolution
4662                .resolved_variable_ref_names,
4663            vec!["depth", "gap", "value"]
4664        );
4665        assert_eq!(
4666            summary
4667                .sass
4668                .same_file_resolution
4669                .unresolved_variable_ref_names,
4670            vec!["missing"]
4671        );
4672        assert_eq!(
4673            summary
4674                .sass
4675                .same_file_resolution
4676                .resolved_mixin_include_names,
4677            vec!["raised"]
4678        );
4679        assert_eq!(
4680            summary
4681                .sass
4682                .same_file_resolution
4683                .unresolved_mixin_include_names,
4684            vec!["absent"]
4685        );
4686        assert_eq!(
4687            summary
4688                .sass
4689                .same_file_resolution
4690                .resolved_function_call_names,
4691            vec!["tone"]
4692        );
4693        Ok(())
4694    }
4695
4696    #[test]
4697    fn index_summary_collects_css_custom_property_seed_facts() {
4698        let source = r#":root { --color-gray-700: #767678; }
4699@media (min-width: 1px) { .btn { color: var(--color-gray-700); } }
4700@supports (display: grid) { @layer ui { .card { color: var(--missing); } } }
4701"#;
4702        let sheet = parse_stylesheet(StyleLanguage::Css, source);
4703        let summary = super::summarize_css_modules_intermediate(&sheet);
4704        let semantic_boundary = super::summarize_semantic_boundary(&sheet);
4705
4706        assert_eq!(
4707            summary.custom_properties.decl_names,
4708            vec!["--color-gray-700"]
4709        );
4710        assert_eq!(
4711            summary.custom_properties.ref_names,
4712            vec!["--color-gray-700", "--missing"]
4713        );
4714        assert_eq!(
4715            summary.custom_properties.selectors_with_refs_names,
4716            vec!["btn", "card"]
4717        );
4718        assert_eq!(
4719            summary
4720                .custom_properties
4721                .selectors_with_refs_under_media_names,
4722            vec!["btn"]
4723        );
4724        assert_eq!(
4725            summary
4726                .custom_properties
4727                .selectors_with_refs_under_supports_names,
4728            vec!["card"]
4729        );
4730        assert_eq!(
4731            summary
4732                .custom_properties
4733                .selectors_with_refs_under_layer_names,
4734            vec!["card"]
4735        );
4736        assert_eq!(
4737            semantic_boundary
4738                .semantic_facts
4739                .custom_properties
4740                .resolved_ref_names,
4741            vec!["--color-gray-700"]
4742        );
4743        assert_eq!(
4744            semantic_boundary
4745                .semantic_facts
4746                .custom_properties
4747                .unresolved_ref_names,
4748            vec!["--missing"]
4749        );
4750    }
4751
4752    #[test]
4753    fn semantic_boundary_separates_parser_syntax_from_semantic_resolution() -> Result<(), String> {
4754        let source = r#"@use "./tokens" as tokens;
4755$gap: 1rem;
4756@mixin raised($depth) { box-shadow: 0 0 $depth black; }
4757.btn { color: $gap; @include raised($gap); }
4758.ghost { color: $missing; }
4759"#;
4760        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4761        let summary = super::summarize_semantic_boundary(&sheet);
4762
4763        assert_eq!(
4764            summary.parser_facts.sass.module_use_edges,
4765            vec![ParserIndexSassModuleUseFactV0 {
4766                source: "./tokens".to_string(),
4767                namespace_kind: "alias",
4768                namespace: Some("tokens".to_string()),
4769            }]
4770        );
4771        assert_eq!(
4772            summary.parser_facts.sass.variable_ref_names,
4773            vec!["depth", "gap", "missing"]
4774        );
4775        assert_eq!(
4776            summary
4777                .semantic_facts
4778                .sass
4779                .same_file_resolution
4780                .resolved_variable_ref_names,
4781            vec!["depth", "gap"]
4782        );
4783        assert_eq!(
4784            summary
4785                .semantic_facts
4786                .sass
4787                .same_file_resolution
4788                .unresolved_variable_ref_names,
4789            vec!["missing"]
4790        );
4791        assert_eq!(
4792            summary.semantic_facts.selector_identity.canonical_names,
4793            vec!["btn", "ghost"]
4794        );
4795        assert_eq!(
4796            summary.semantic_facts.sass.selector_symbol_facts,
4797            vec![
4798                ParserIndexSassSelectorSymbolFactV0 {
4799                    selector_name: "btn".to_string(),
4800                    symbol_kind: "mixin",
4801                    name: "raised".to_string(),
4802                    role: "include",
4803                    resolution: "resolved",
4804                    byte_span: span_after(source, "@include", "raised")?,
4805                    range: range_after(source, "@include", "raised")?,
4806                },
4807                ParserIndexSassSelectorSymbolFactV0 {
4808                    selector_name: "btn".to_string(),
4809                    symbol_kind: "variable",
4810                    name: "gap".to_string(),
4811                    role: "reference",
4812                    resolution: "resolved",
4813                    byte_span: span_after(source, ".btn { color", "$gap")?,
4814                    range: range_after(source, ".btn { color", "$gap")?,
4815                },
4816                ParserIndexSassSelectorSymbolFactV0 {
4817                    selector_name: "btn".to_string(),
4818                    symbol_kind: "variable",
4819                    name: "gap".to_string(),
4820                    role: "reference",
4821                    resolution: "resolved",
4822                    byte_span: span_after(source, "@include raised(", "$gap")?,
4823                    range: range_after(source, "@include raised(", "$gap")?,
4824                },
4825                ParserIndexSassSelectorSymbolFactV0 {
4826                    selector_name: "ghost".to_string(),
4827                    symbol_kind: "variable",
4828                    name: "missing".to_string(),
4829                    role: "reference",
4830                    resolution: "unresolved",
4831                    byte_span: span_after(source, ".ghost { color", "$missing")?,
4832                    range: range_after(source, ".ghost { color", "$missing")?,
4833                },
4834            ]
4835        );
4836        Ok(())
4837    }
4838
4839    #[test]
4840    fn semantic_boundary_reports_lossless_cst_span_contract() {
4841        let source = "// heading\n.card { &--primary { color: red; } }\n";
4842        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4843        let summary = super::summarize_semantic_boundary(&sheet);
4844
4845        assert_eq!(
4846            summary.parser_facts.lossless_cst.source_byte_len,
4847            source.len()
4848        );
4849        assert!(summary.parser_facts.lossless_cst.token_count > 0);
4850        assert_eq!(summary.parser_facts.lossless_cst.diagnostic_count, 0);
4851        assert!(
4852            summary
4853                .parser_facts
4854                .lossless_cst
4855                .all_token_spans_within_source
4856        );
4857        assert!(
4858            summary
4859                .parser_facts
4860                .lossless_cst
4861                .all_node_spans_within_source
4862        );
4863    }
4864
4865    #[test]
4866    fn index_summary_does_not_resolve_sass_variables_from_another_local_scope() -> Result<(), String>
4867    {
4868        let source = ".one { $gap: 1rem; }\n.two { color: $gap; }\n";
4869        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4870        let summary = super::summarize_css_modules_intermediate(&sheet);
4871
4872        assert_eq!(
4873            summary.sass.selectors_with_resolved_variable_refs_names,
4874            Vec::<String>::new()
4875        );
4876        assert_eq!(
4877            summary.sass.selectors_with_unresolved_variable_refs_names,
4878            vec!["two"]
4879        );
4880        assert_eq!(
4881            summary
4882                .sass
4883                .same_file_resolution
4884                .resolved_variable_ref_names,
4885            Vec::<String>::new()
4886        );
4887        assert_eq!(
4888            summary
4889                .sass
4890                .same_file_resolution
4891                .unresolved_variable_ref_names,
4892            vec!["gap"]
4893        );
4894        assert_eq!(
4895            summary.sass.selector_symbol_facts,
4896            vec![ParserIndexSassSelectorSymbolFactV0 {
4897                selector_name: "two".to_string(),
4898                symbol_kind: "variable",
4899                name: "gap".to_string(),
4900                role: "reference",
4901                resolution: "unresolved",
4902                byte_span: span_after(source, ".two", "$gap")?,
4903                range: range_after(source, ".two", "$gap")?,
4904            }]
4905        );
4906        Ok(())
4907    }
4908
4909    #[test]
4910    fn index_summary_skips_module_qualified_sass_refs_from_same_file_resolution() {
4911        let source = r#"@use "./tokens" as tokens;
4912@mixin raised { box-shadow: none; }
4913@function tone($value) { @return $value; }
4914.button {
4915  color: tokens.$gap;
4916  @include tokens.raised;
4917  border-color: tokens.tone(tokens.$gap);
4918}
4919"#;
4920        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4921        let summary = super::summarize_css_modules_intermediate(&sheet);
4922
4923        assert_eq!(summary.sass.variable_ref_names, vec!["value"]);
4924        assert_eq!(
4925            summary
4926                .sass
4927                .same_file_resolution
4928                .resolved_variable_ref_names,
4929            vec!["value"]
4930        );
4931        assert_eq!(
4932            summary
4933                .sass
4934                .same_file_resolution
4935                .unresolved_variable_ref_names,
4936            Vec::<String>::new()
4937        );
4938        assert_eq!(summary.sass.mixin_include_names, Vec::<String>::new());
4939        assert_eq!(summary.sass.function_call_names, Vec::<String>::new());
4940        assert_eq!(
4941            summary.sass.selectors_with_variable_refs_names,
4942            Vec::<String>::new()
4943        );
4944        assert_eq!(
4945            summary.sass.selectors_with_mixin_includes_names,
4946            Vec::<String>::new()
4947        );
4948        assert_eq!(
4949            summary.sass.selectors_with_function_calls_names,
4950            Vec::<String>::new()
4951        );
4952        assert_eq!(summary.sass.selector_symbol_facts, Vec::new());
4953        assert_eq!(
4954            summary.sass.module_use_edges,
4955            vec![ParserIndexSassModuleUseFactV0 {
4956                source: "./tokens".to_string(),
4957                namespace_kind: "alias",
4958                namespace: Some("tokens".to_string()),
4959            }]
4960        );
4961    }
4962
4963    #[test]
4964    fn index_summary_reports_sass_symbol_ranges_after_non_ascii_text() -> Result<(), String> {
4965        let source = "$gap: 1rem;\n.btn { content: \"한🙂\"; color: $gap; }\n";
4966        let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4967        let summary = super::summarize_css_modules_intermediate(&sheet);
4968
4969        assert_eq!(
4970            summary.sass.selector_symbol_facts,
4971            vec![ParserIndexSassSelectorSymbolFactV0 {
4972                selector_name: "btn".to_string(),
4973                symbol_kind: "variable",
4974                name: "gap".to_string(),
4975                role: "reference",
4976                resolution: "resolved",
4977                byte_span: ParserByteSpanV0 { start: 46, end: 50 },
4978                range: ParserRangeV0 {
4979                    start: ParserPositionV0 {
4980                        line: 1,
4981                        character: 30,
4982                    },
4983                    end: ParserPositionV0 {
4984                        line: 1,
4985                        character: 34,
4986                    },
4987                },
4988            }]
4989        );
4990
4991        Ok(())
4992    }
4993}