Skip to main content

omena_parser/
public_product.rs

1//! Product-facing parser summaries and compatibility signals.
2//!
3//! This module is the stable reporting layer above the raw parser. It exposes
4//! CSS Modules facts, canonical producer/candidate summaries, and evaluator
5//! readiness payloads used by cme gates while the parser migrates toward the
6//! standalone omena-css track.
7
8use std::collections::{BTreeMap, BTreeSet};
9
10use crate::{
11    ParsedAnimationFactKind, ParsedCssModuleComposesEdgeKind, ParsedCssModuleComposesFactKind,
12    ParsedCssModuleValueFactKind, ParsedSassModuleEdgeFactKind, ParsedSassSymbolFactKind,
13    ParsedSelectorFactKind, ParsedStyleFacts, ParsedVariableFactKind, ParsedVariableFactNameV0,
14    ParserByteSpanV0, ParserPositionV0, ParserRangeV0, StyleDialect, css_keyword, parse,
15    product_facts_from_cst, summarize_omena_parser_parity_lite,
16};
17use cstree::text::TextRange;
18use omena_syntax::ident::{
19    AuthoredPropertyTextV0, CanonicalCustomPropertyNameV0, CanonicalPropertyKeyV0, PropertyNameV0,
20    class_selector_names,
21};
22use serde::Serialize;
23use std::cmp::Ordering;
24
25mod style_blocks;
26mod syntax_index;
27
28pub use syntax_index::{
29    ParserDeclarationSelectorContextV0, ParserDeclarationSyntaxFactV0, ProductSyntaxIndexV0,
30    collect_parser_declaration_syntax_facts,
31};
32
33#[cfg(test)]
34mod tests;
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
37#[serde(rename_all = "camelCase")]
38pub struct ParserIndexSummaryV0 {
39    schema_version: &'static str,
40    language: &'static str,
41    selectors: ParserIndexSelectorFactsV0,
42    values: ParserIndexValueFactsV0,
43    custom_properties: ParserIndexCustomPropertyFactsV0,
44    sass: ParserIndexSassFactsV0,
45    keyframes: ParserIndexKeyframesFactsV0,
46    composes: ParserIndexComposesFactsV0,
47    wrappers: ParserIndexWrapperFactsV0,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
51#[serde(rename_all = "camelCase")]
52pub struct ParserCanonicalCandidateBundleV0 {
53    schema_version: &'static str,
54    language: &'static str,
55    parity_lite: crate::OmenaParserParityLiteSummaryV0,
56    css_modules_intermediate: ParserIndexSummaryV0,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
60#[serde(rename_all = "camelCase")]
61struct ParserEvaluatorCandidateV0 {
62    kind: &'static str,
63    selector_name: String,
64    nested_safety_kind: &'static str,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    bem_suffix_parent_name: Option<String>,
67    under_media: bool,
68    under_supports: bool,
69    under_layer: bool,
70    has_value_refs: bool,
71    has_local_value_refs: bool,
72    has_imported_value_refs: bool,
73    has_custom_property_refs: bool,
74    has_animation_ref: bool,
75    has_animation_name_ref: bool,
76    has_composes: bool,
77    has_local_composes: bool,
78    has_imported_composes: bool,
79    has_global_composes: bool,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
83#[serde(rename_all = "camelCase")]
84pub struct ParserEvaluatorCandidatesV0 {
85    schema_version: &'static str,
86    language: &'static str,
87    results: Vec<ParserEvaluatorCandidateV0>,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
91#[serde(rename_all = "camelCase")]
92pub struct ParserCanonicalProducerSignalV0 {
93    schema_version: &'static str,
94    language: &'static str,
95    canonical_candidate: ParserCanonicalCandidateBundleV0,
96    evaluator_candidates: ParserEvaluatorCandidatesV0,
97    public_product_gate: ParserPublicProductGateSignalV0,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
101#[serde(rename_all = "camelCase")]
102struct ParserPublicProductGateSignalV0 {
103    canonical_candidate_command: &'static str,
104    consumer_boundary_command: &'static str,
105    public_product_gate_command: &'static str,
106    included_in_parser_lane: bool,
107    included_in_rust_lane_bundle: bool,
108    included_in_rust_release_bundle: bool,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
112#[serde(rename_all = "camelCase")]
113struct ParserIndexSelectorFactsV0 {
114    names: Vec<String>,
115    definition_facts: Vec<ParserIndexSelectorDefinitionFactV0>,
116    bem_suffix_parent_names: Vec<String>,
117    bem_suffix_safe_names: Vec<String>,
118    nested_unsafe_names: Vec<String>,
119    selectors_with_value_refs_names: Vec<String>,
120    selectors_with_animation_ref_names: Vec<String>,
121    selectors_with_animation_name_ref_names: Vec<String>,
122    bem_suffix_count: usize,
123    nested_safety_counts: NestedSafetyCountsV0,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
127#[serde(rename_all = "camelCase")]
128struct ParserIndexSelectorDefinitionFactV0 {
129    name: String,
130    source_order: usize,
131    byte_span: ParserByteSpanV0,
132    range: ParserRangeV0,
133    rule_byte_span: ParserByteSpanV0,
134    rule_range: ParserRangeV0,
135    full_selector: String,
136    declarations: String,
137    nested_safety_kind: &'static str,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    bem_suffix_parent_name: Option<String>,
140    under_media: bool,
141    under_supports: bool,
142    under_layer: bool,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
146#[serde(rename_all = "camelCase")]
147struct ParserIndexValueFactsV0 {
148    decl_names: Vec<String>,
149    decl_facts: Vec<ParserIndexValueDeclFactV0>,
150    decl_names_with_local_refs: Vec<String>,
151    decl_names_with_imported_refs: Vec<String>,
152    import_names: Vec<String>,
153    import_facts: Vec<ParserIndexValueImportFactV0>,
154    import_sources: Vec<String>,
155    import_alias_count: usize,
156    ref_names: Vec<String>,
157    ref_facts: Vec<ParserIndexValueRefFactV0>,
158    local_ref_names: Vec<String>,
159    imported_ref_names: Vec<String>,
160    imported_ref_sources: Vec<String>,
161    declaration_ref_names: Vec<String>,
162    declaration_imported_ref_sources: Vec<String>,
163    value_decl_ref_names: Vec<String>,
164    value_decl_imported_ref_sources: Vec<String>,
165    selectors_with_refs_names: Vec<String>,
166    selectors_with_local_refs_names: Vec<String>,
167    selectors_with_imported_refs_names: Vec<String>,
168    selectors_with_refs_under_media_names: Vec<String>,
169    selectors_with_refs_under_supports_names: Vec<String>,
170    selectors_with_refs_under_layer_names: Vec<String>,
171    selectors_with_local_refs_under_media_names: Vec<String>,
172    selectors_with_local_refs_under_supports_names: Vec<String>,
173    selectors_with_local_refs_under_layer_names: Vec<String>,
174    selectors_with_imported_refs_under_media_names: Vec<String>,
175    selectors_with_imported_refs_under_supports_names: Vec<String>,
176    selectors_with_imported_refs_under_layer_names: Vec<String>,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
180#[serde(rename_all = "camelCase")]
181struct ParserIndexValueDeclFactV0 {
182    name: String,
183    value: String,
184    source_order: usize,
185    byte_span: ParserByteSpanV0,
186    range: ParserRangeV0,
187    rule_byte_span: ParserByteSpanV0,
188    rule_range: ParserRangeV0,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
192#[serde(rename_all = "camelCase")]
193struct ParserIndexValueImportFactV0 {
194    name: String,
195    imported_name: String,
196    from: String,
197    source_order: usize,
198    byte_span: ParserByteSpanV0,
199    range: ParserRangeV0,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    imported_name_byte_span: Option<ParserByteSpanV0>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    imported_name_range: Option<ParserRangeV0>,
204    rule_byte_span: ParserByteSpanV0,
205    rule_range: ParserRangeV0,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
209#[serde(rename_all = "camelCase")]
210struct ParserIndexValueRefFactV0 {
211    name: String,
212    source: &'static str,
213    source_order: usize,
214    byte_span: ParserByteSpanV0,
215    range: ParserRangeV0,
216}
217
218#[derive(Debug, Clone, Serialize, Default)]
219#[serde(rename_all = "camelCase")]
220struct ParserIndexCustomPropertyFactsV0 {
221    decl_names: Vec<AuthoredPropertyTextV0>,
222    decl_facts: Vec<ParserIndexCustomPropertyDeclFactV0>,
223    decl_context_selectors: Vec<String>,
224    decl_names_under_media: Vec<AuthoredPropertyTextV0>,
225    decl_names_under_supports: Vec<AuthoredPropertyTextV0>,
226    decl_names_under_layer: Vec<AuthoredPropertyTextV0>,
227    ref_names: Vec<AuthoredPropertyTextV0>,
228    ref_facts: Vec<ParserIndexCustomPropertyRefFactV0>,
229    selectors_with_refs_names: Vec<String>,
230    selectors_with_refs_under_media_names: Vec<String>,
231    selectors_with_refs_under_supports_names: Vec<String>,
232    selectors_with_refs_under_layer_names: Vec<String>,
233}
234
235impl PartialEq for ParserIndexCustomPropertyFactsV0 {
236    fn eq(&self, other: &Self) -> bool {
237        authored_custom_property_sequences_same(&self.decl_names, &other.decl_names)
238            && self.decl_facts == other.decl_facts
239            && self.decl_context_selectors == other.decl_context_selectors
240            && authored_custom_property_sequences_same(
241                &self.decl_names_under_media,
242                &other.decl_names_under_media,
243            )
244            && authored_custom_property_sequences_same(
245                &self.decl_names_under_supports,
246                &other.decl_names_under_supports,
247            )
248            && authored_custom_property_sequences_same(
249                &self.decl_names_under_layer,
250                &other.decl_names_under_layer,
251            )
252            && authored_custom_property_sequences_same(&self.ref_names, &other.ref_names)
253            && self.ref_facts == other.ref_facts
254            && self.selectors_with_refs_names == other.selectors_with_refs_names
255            && self.selectors_with_refs_under_media_names
256                == other.selectors_with_refs_under_media_names
257            && self.selectors_with_refs_under_supports_names
258                == other.selectors_with_refs_under_supports_names
259            && self.selectors_with_refs_under_layer_names
260                == other.selectors_with_refs_under_layer_names
261    }
262}
263
264impl Eq for ParserIndexCustomPropertyFactsV0 {}
265
266#[derive(Debug, Clone, Serialize)]
267#[serde(rename_all = "camelCase")]
268struct ParserIndexCustomPropertyDeclFactV0 {
269    name: AuthoredPropertyTextV0,
270    value: String,
271    source_order: usize,
272    byte_span: ParserByteSpanV0,
273    range: ParserRangeV0,
274    rule_byte_span: ParserByteSpanV0,
275    rule_range: ParserRangeV0,
276    selector_contexts: Vec<String>,
277    wrapper_at_rules: Vec<ParserIndexAtRuleContextV0>,
278    under_media: bool,
279    under_supports: bool,
280    under_layer: bool,
281    property_key: CanonicalCustomPropertyNameV0,
282}
283
284impl PartialEq for ParserIndexCustomPropertyDeclFactV0 {
285    fn eq(&self, other: &Self) -> bool {
286        self.property_key == other.property_key
287            && self.value == other.value
288            && self.source_order == other.source_order
289            && self.byte_span == other.byte_span
290            && self.range == other.range
291            && self.rule_byte_span == other.rule_byte_span
292            && self.rule_range == other.rule_range
293            && self.selector_contexts == other.selector_contexts
294            && self.wrapper_at_rules == other.wrapper_at_rules
295            && self.under_media == other.under_media
296            && self.under_supports == other.under_supports
297            && self.under_layer == other.under_layer
298    }
299}
300
301impl Eq for ParserIndexCustomPropertyDeclFactV0 {}
302
303impl PartialOrd for ParserIndexCustomPropertyDeclFactV0 {
304    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
305        Some(self.cmp(other))
306    }
307}
308
309impl Ord for ParserIndexCustomPropertyDeclFactV0 {
310    fn cmp(&self, other: &Self) -> Ordering {
311        (
312            &self.property_key,
313            &self.value,
314            self.source_order,
315            self.byte_span,
316            self.range,
317            self.rule_byte_span,
318            self.rule_range,
319            &self.selector_contexts,
320            &self.wrapper_at_rules,
321            self.under_media,
322            self.under_supports,
323            self.under_layer,
324        )
325            .cmp(&(
326                &other.property_key,
327                &other.value,
328                other.source_order,
329                other.byte_span,
330                other.range,
331                other.rule_byte_span,
332                other.rule_range,
333                &other.selector_contexts,
334                &other.wrapper_at_rules,
335                other.under_media,
336                other.under_supports,
337                other.under_layer,
338            ))
339    }
340}
341
342#[derive(Debug, Clone, Serialize)]
343#[serde(rename_all = "camelCase")]
344struct ParserIndexCustomPropertyRefFactV0 {
345    name: AuthoredPropertyTextV0,
346    source_order: usize,
347    byte_span: ParserByteSpanV0,
348    range: ParserRangeV0,
349    selector_contexts: Vec<String>,
350    wrapper_at_rules: Vec<ParserIndexAtRuleContextV0>,
351    under_media: bool,
352    under_supports: bool,
353    under_layer: bool,
354    property_key: CanonicalCustomPropertyNameV0,
355}
356
357impl PartialEq for ParserIndexCustomPropertyRefFactV0 {
358    fn eq(&self, other: &Self) -> bool {
359        self.property_key == other.property_key
360            && self.source_order == other.source_order
361            && self.byte_span == other.byte_span
362            && self.range == other.range
363            && self.selector_contexts == other.selector_contexts
364            && self.wrapper_at_rules == other.wrapper_at_rules
365            && self.under_media == other.under_media
366            && self.under_supports == other.under_supports
367            && self.under_layer == other.under_layer
368    }
369}
370
371impl Eq for ParserIndexCustomPropertyRefFactV0 {}
372
373impl PartialOrd for ParserIndexCustomPropertyRefFactV0 {
374    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
375        Some(self.cmp(other))
376    }
377}
378
379impl Ord for ParserIndexCustomPropertyRefFactV0 {
380    fn cmp(&self, other: &Self) -> Ordering {
381        (
382            &self.property_key,
383            self.source_order,
384            self.byte_span,
385            self.range,
386            &self.selector_contexts,
387            &self.wrapper_at_rules,
388            self.under_media,
389            self.under_supports,
390            self.under_layer,
391        )
392            .cmp(&(
393                &other.property_key,
394                other.source_order,
395                other.byte_span,
396                other.range,
397                &other.selector_contexts,
398                &other.wrapper_at_rules,
399                other.under_media,
400                other.under_supports,
401                other.under_layer,
402            ))
403    }
404}
405
406#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
407#[serde(rename_all = "camelCase")]
408struct ParserIndexAtRuleContextV0 {
409    name: String,
410    params: String,
411    byte_span: ParserByteSpanV0,
412    range: ParserRangeV0,
413}
414
415#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
416#[serde(rename_all = "camelCase")]
417struct ParserIndexSassFactsV0 {
418    variable_decl_names: Vec<String>,
419    symbol_decl_facts: Vec<ParserIndexSassSymbolDeclFactV0>,
420    variable_parameter_names: Vec<String>,
421    variable_ref_names: Vec<String>,
422    selectors_with_variable_refs_names: Vec<String>,
423    selectors_with_resolved_variable_refs_names: Vec<String>,
424    selectors_with_unresolved_variable_refs_names: Vec<String>,
425    mixin_decl_names: Vec<String>,
426    mixin_include_names: Vec<String>,
427    selectors_with_mixin_includes_names: Vec<String>,
428    selectors_with_resolved_mixin_includes_names: Vec<String>,
429    selectors_with_unresolved_mixin_includes_names: Vec<String>,
430    function_decl_names: Vec<String>,
431    function_call_names: Vec<String>,
432    selectors_with_function_calls_names: Vec<String>,
433    selector_symbol_facts: Vec<ParserIndexSassSelectorSymbolFactV0>,
434    module_use_sources: Vec<String>,
435    module_use_edges: Vec<ParserIndexSassModuleUseFactV0>,
436    module_forward_sources: Vec<String>,
437    module_forward_edges: Vec<ParserIndexSassModuleForwardFactV0>,
438    module_import_sources: Vec<String>,
439    same_file_resolution: ParserIndexSassSameFileResolutionFactsV0,
440}
441
442#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
443#[serde(rename_all = "camelCase")]
444struct ParserIndexSassSymbolDeclFactV0 {
445    symbol_kind: &'static str,
446    name: String,
447    role: &'static str,
448    byte_span: ParserByteSpanV0,
449    range: ParserRangeV0,
450}
451
452#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
453#[serde(rename_all = "camelCase")]
454struct ParserIndexSassModuleUseFactV0 {
455    source: String,
456    namespace_kind: &'static str,
457    namespace: Option<String>,
458    byte_span: ParserByteSpanV0,
459    range: ParserRangeV0,
460}
461
462#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
463#[serde(rename_all = "camelCase")]
464struct ParserIndexSassModuleForwardFactV0 {
465    source: String,
466    prefix: String,
467    visibility_kind: &'static str,
468    visibility_members: Vec<ParserIndexSassModuleForwardMemberV0>,
469    byte_span: ParserByteSpanV0,
470    range: ParserRangeV0,
471    rule_byte_span: ParserByteSpanV0,
472    rule_range: ParserRangeV0,
473}
474
475#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
476#[serde(rename_all = "camelCase")]
477struct ParserIndexSassModuleForwardMemberV0 {
478    name: String,
479    symbol_kind: Option<&'static str>,
480}
481
482#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
483#[serde(rename_all = "camelCase")]
484struct ParserIndexSassSameFileResolutionFactsV0 {
485    resolved_variable_ref_names: Vec<String>,
486    unresolved_variable_ref_names: Vec<String>,
487    resolved_mixin_include_names: Vec<String>,
488    unresolved_mixin_include_names: Vec<String>,
489    resolved_function_call_names: Vec<String>,
490}
491
492#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
493#[serde(rename_all = "camelCase")]
494struct ParserIndexSassSelectorSymbolFactV0 {
495    selector_name: String,
496    symbol_kind: &'static str,
497    name: String,
498    namespace: Option<String>,
499    role: &'static str,
500    resolution: &'static str,
501    byte_span: ParserByteSpanV0,
502    range: ParserRangeV0,
503}
504
505#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
506#[serde(rename_all = "camelCase")]
507struct ParserIndexKeyframesFactsV0 {
508    names: Vec<String>,
509    decl_facts: Vec<ParserIndexKeyframesDeclFactV0>,
510    names_under_media: Vec<String>,
511    names_under_supports: Vec<String>,
512    names_under_layer: Vec<String>,
513    animation_ref_names: Vec<String>,
514    animation_name_ref_names: Vec<String>,
515    ref_facts: Vec<ParserIndexAnimationNameRefFactV0>,
516    selectors_with_animation_ref_names: Vec<String>,
517    selectors_with_animation_name_ref_names: Vec<String>,
518    selectors_with_animation_refs_under_media_names: Vec<String>,
519    selectors_with_animation_refs_under_supports_names: Vec<String>,
520    selectors_with_animation_refs_under_layer_names: Vec<String>,
521    selectors_with_animation_name_refs_under_media_names: Vec<String>,
522    selectors_with_animation_name_refs_under_supports_names: Vec<String>,
523    selectors_with_animation_name_refs_under_layer_names: Vec<String>,
524}
525
526#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
527#[serde(rename_all = "camelCase")]
528struct ParserIndexKeyframesDeclFactV0 {
529    name: String,
530    source_order: usize,
531    byte_span: ParserByteSpanV0,
532    range: ParserRangeV0,
533    rule_byte_span: ParserByteSpanV0,
534    rule_range: ParserRangeV0,
535}
536
537#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
538#[serde(rename_all = "camelCase")]
539struct ParserIndexAnimationNameRefFactV0 {
540    name: String,
541    property: &'static str,
542    source_order: usize,
543    byte_span: ParserByteSpanV0,
544    range: ParserRangeV0,
545}
546
547#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
548#[serde(rename_all = "camelCase")]
549struct ParserIndexComposesFactsV0 {
550    edges: Vec<ParserIndexComposesEdgeFactV0>,
551    selectors_with_composes_names: Vec<String>,
552    selectors_with_composes_under_media_names: Vec<String>,
553    selectors_with_composes_under_supports_names: Vec<String>,
554    selectors_with_composes_under_layer_names: Vec<String>,
555    local_selector_names: Vec<String>,
556    imported_selector_names: Vec<String>,
557    global_selector_names: Vec<String>,
558    local_selector_names_under_media: Vec<String>,
559    local_selector_names_under_supports: Vec<String>,
560    local_selector_names_under_layer: Vec<String>,
561    imported_selector_names_under_media: Vec<String>,
562    imported_selector_names_under_supports: Vec<String>,
563    imported_selector_names_under_layer: Vec<String>,
564    global_selector_names_under_media: Vec<String>,
565    global_selector_names_under_supports: Vec<String>,
566    global_selector_names_under_layer: Vec<String>,
567    import_sources: Vec<String>,
568    import_sources_under_media: Vec<String>,
569    import_sources_under_supports: Vec<String>,
570    import_sources_under_layer: Vec<String>,
571    class_name_count: usize,
572    local_class_name_count: usize,
573    imported_class_name_count: usize,
574    global_class_name_count: usize,
575}
576
577#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
578#[serde(rename_all = "camelCase")]
579struct ParserIndexComposesEdgeFactV0 {
580    kind: &'static str,
581    owner_selector_names: Vec<String>,
582    target_names: Vec<String>,
583    import_source: Option<String>,
584    class_tokens: Vec<ParserIndexComposesClassTokenV0>,
585    byte_span: ParserByteSpanV0,
586    range: ParserRangeV0,
587}
588
589#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
590#[serde(rename_all = "camelCase")]
591struct ParserIndexComposesClassTokenV0 {
592    class_name: String,
593    byte_span: ParserByteSpanV0,
594    range: ParserRangeV0,
595}
596
597#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
598#[serde(rename_all = "camelCase")]
599struct ParserIndexWrapperFactsV0 {
600    selectors_under_media_names: Vec<String>,
601    selectors_under_supports_names: Vec<String>,
602    selectors_under_layer_names: Vec<String>,
603}
604
605#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
606#[serde(rename_all = "camelCase")]
607struct NestedSafetyCountsV0 {
608    flat: usize,
609    bem_suffix_safe: usize,
610    nested_unsafe: usize,
611}
612
613#[derive(Debug, Clone, PartialEq, Eq)]
614struct SelectorBranch {
615    name: String,
616    name_span: ParserByteSpanV0,
617    bare_suffix_base: bool,
618    amp_suffix_depth: usize,
619}
620
621#[derive(Debug, Clone, PartialEq, Eq)]
622struct SassVariableDeclScope {
623    name: String,
624    selector_names: Vec<String>,
625}
626
627#[derive(Debug, Clone, PartialEq, Eq)]
628struct StyleBlock {
629    names: Vec<String>,
630    context_text: Option<String>,
631    start: usize,
632    end: usize,
633    rule_start: usize,
634    rule_end: usize,
635    body_start: usize,
636    body_end: usize,
637    header_text: Option<String>,
638    under_media: bool,
639    under_supports: bool,
640    under_layer: bool,
641    wrapper_at_rules: Vec<ParserIndexAtRuleContextV0>,
642}
643
644#[derive(Debug, Clone, PartialEq, Eq, Default)]
645struct WrapperContext {
646    under_media: bool,
647    under_supports: bool,
648    under_layer: bool,
649    wrapper_at_rules: Vec<ParserIndexAtRuleContextV0>,
650}
651
652pub fn summarize_css_modules_intermediate(
653    source: &str,
654    dialect: StyleDialect,
655) -> ParserIndexSummaryV0 {
656    let line_index = SourceLineIndex::new(source);
657    let parsed = parse(source, dialect);
658    let facts = product_facts_from_cst(source, &parsed);
659    let blocks = style_blocks::collect_style_blocks_from_cst(source, &line_index, &parsed);
660    let syntax_index = ProductSyntaxIndexV0::new(source, &parsed);
661    let selectors = summarize_selectors(source, &line_index, &facts, &blocks);
662    let values = summarize_values(source, &line_index, &facts, &blocks, &syntax_index);
663    let custom_properties =
664        summarize_custom_properties(source, &line_index, &facts, &blocks, &syntax_index);
665    let sass = summarize_sass(source, &line_index, &facts, &blocks, &syntax_index);
666    let keyframes = summarize_keyframes(source, &line_index, &facts, &blocks, &syntax_index);
667    let composes = summarize_composes(source, &line_index, &facts, &blocks);
668    let wrappers = summarize_wrappers(&blocks);
669
670    ParserIndexSummaryV0 {
671        schema_version: "0",
672        language: dialect_label(dialect),
673        selectors: ParserIndexSelectorFactsV0 {
674            selectors_with_value_refs_names: values.selectors_with_refs_names.clone(),
675            selectors_with_animation_ref_names: keyframes
676                .selectors_with_animation_ref_names
677                .clone(),
678            selectors_with_animation_name_ref_names: keyframes
679                .selectors_with_animation_name_ref_names
680                .clone(),
681            ..selectors
682        },
683        values,
684        custom_properties,
685        sass,
686        keyframes,
687        composes,
688        wrappers,
689    }
690}
691
692pub fn summarize_parser_canonical_candidate(
693    source: &str,
694    dialect: StyleDialect,
695) -> ParserCanonicalCandidateBundleV0 {
696    let parity_lite = summarize_omena_parser_parity_lite(source, dialect);
697    let css_modules_intermediate = summarize_css_modules_intermediate(source, dialect);
698
699    ParserCanonicalCandidateBundleV0 {
700        schema_version: "0",
701        language: parity_lite.language,
702        parity_lite,
703        css_modules_intermediate,
704    }
705}
706
707pub fn summarize_parser_evaluator_candidates(
708    source: &str,
709    dialect: StyleDialect,
710) -> ParserEvaluatorCandidatesV0 {
711    let intermediate = summarize_css_modules_intermediate(source, dialect);
712    let bem_suffix_safe_names: BTreeSet<&str> = intermediate
713        .selectors
714        .bem_suffix_safe_names
715        .iter()
716        .map(String::as_str)
717        .collect();
718    let nested_unsafe_names: BTreeSet<&str> = intermediate
719        .selectors
720        .nested_unsafe_names
721        .iter()
722        .map(String::as_str)
723        .collect();
724    let selectors_under_media_names: BTreeSet<&str> = intermediate
725        .wrappers
726        .selectors_under_media_names
727        .iter()
728        .map(String::as_str)
729        .collect();
730    let selectors_under_supports_names: BTreeSet<&str> = intermediate
731        .wrappers
732        .selectors_under_supports_names
733        .iter()
734        .map(String::as_str)
735        .collect();
736    let selectors_under_layer_names: BTreeSet<&str> = intermediate
737        .wrappers
738        .selectors_under_layer_names
739        .iter()
740        .map(String::as_str)
741        .collect();
742    let selectors_with_refs_names: BTreeSet<&str> = intermediate
743        .values
744        .selectors_with_refs_names
745        .iter()
746        .map(String::as_str)
747        .collect();
748    let selectors_with_local_refs_names: BTreeSet<&str> = intermediate
749        .values
750        .selectors_with_local_refs_names
751        .iter()
752        .map(String::as_str)
753        .collect();
754    let selectors_with_imported_refs_names: BTreeSet<&str> = intermediate
755        .values
756        .selectors_with_imported_refs_names
757        .iter()
758        .map(String::as_str)
759        .collect();
760    let selectors_with_custom_property_refs_names: BTreeSet<&str> = intermediate
761        .custom_properties
762        .selectors_with_refs_names
763        .iter()
764        .map(String::as_str)
765        .collect();
766    let selectors_with_animation_ref_names: BTreeSet<&str> = intermediate
767        .keyframes
768        .selectors_with_animation_ref_names
769        .iter()
770        .map(String::as_str)
771        .collect();
772    let selectors_with_animation_name_ref_names: BTreeSet<&str> = intermediate
773        .keyframes
774        .selectors_with_animation_name_ref_names
775        .iter()
776        .map(String::as_str)
777        .collect();
778    let selectors_with_composes_names: BTreeSet<&str> = intermediate
779        .composes
780        .selectors_with_composes_names
781        .iter()
782        .map(String::as_str)
783        .collect();
784    let local_selector_names: BTreeSet<&str> = intermediate
785        .composes
786        .local_selector_names
787        .iter()
788        .map(String::as_str)
789        .collect();
790    let imported_selector_names: BTreeSet<&str> = intermediate
791        .composes
792        .imported_selector_names
793        .iter()
794        .map(String::as_str)
795        .collect();
796    let global_selector_names: BTreeSet<&str> = intermediate
797        .composes
798        .global_selector_names
799        .iter()
800        .map(String::as_str)
801        .collect();
802
803    let results = intermediate
804        .selectors
805        .names
806        .iter()
807        .map(|selector_name| {
808            let selector = selector_name.as_str();
809            let nested_safety_kind = if nested_unsafe_names.contains(selector) {
810                "nestedUnsafe"
811            } else if bem_suffix_safe_names.contains(selector) {
812                "bemSuffixSafe"
813            } else {
814                "flat"
815            };
816            ParserEvaluatorCandidateV0 {
817                kind: "selector-index-facts",
818                selector_name: selector_name.clone(),
819                nested_safety_kind,
820                bem_suffix_parent_name: if nested_safety_kind == "bemSuffixSafe" {
821                    bem_suffix_parent_name(selector)
822                } else {
823                    None
824                },
825                under_media: selectors_under_media_names.contains(selector),
826                under_supports: selectors_under_supports_names.contains(selector),
827                under_layer: selectors_under_layer_names.contains(selector),
828                has_value_refs: selectors_with_refs_names.contains(selector),
829                has_local_value_refs: selectors_with_local_refs_names.contains(selector),
830                has_imported_value_refs: selectors_with_imported_refs_names.contains(selector),
831                has_custom_property_refs: selectors_with_custom_property_refs_names
832                    .contains(selector),
833                has_animation_ref: selectors_with_animation_ref_names.contains(selector),
834                has_animation_name_ref: selectors_with_animation_name_ref_names.contains(selector),
835                has_composes: selectors_with_composes_names.contains(selector),
836                has_local_composes: local_selector_names.contains(selector),
837                has_imported_composes: imported_selector_names.contains(selector),
838                has_global_composes: global_selector_names.contains(selector),
839            }
840        })
841        .collect();
842
843    ParserEvaluatorCandidatesV0 {
844        schema_version: "0",
845        language: intermediate.language,
846        results,
847    }
848}
849
850pub fn summarize_parser_canonical_producer_signal(
851    source: &str,
852    dialect: StyleDialect,
853) -> ParserCanonicalProducerSignalV0 {
854    let canonical_candidate = summarize_parser_canonical_candidate(source, dialect);
855    let evaluator_candidates = summarize_parser_evaluator_candidates(source, dialect);
856
857    ParserCanonicalProducerSignalV0 {
858        schema_version: "0",
859        language: canonical_candidate.language,
860        canonical_candidate,
861        evaluator_candidates,
862        public_product_gate: ParserPublicProductGateSignalV0 {
863            canonical_candidate_command: "pnpm check:rust-parser-canonical-candidate",
864            consumer_boundary_command: "pnpm check:rust-parser-consumer-boundary",
865            public_product_gate_command: "pnpm check:rust-parser-public-product",
866            included_in_parser_lane: true,
867            included_in_rust_lane_bundle: true,
868            included_in_rust_release_bundle: true,
869        },
870    }
871}
872
873fn summarize_selectors(
874    source: &str,
875    line_index: &SourceLineIndex,
876    facts: &ParsedStyleFacts,
877    blocks: &[StyleBlock],
878) -> ParserIndexSelectorFactsV0 {
879    let mut names = Vec::new();
880    let mut definition_facts = Vec::new();
881    let mut bem_suffix_parent_names = Vec::new();
882    let mut bem_suffix_safe_names = Vec::new();
883    let mut nested_unsafe_names = Vec::new();
884    let mut nested_safety_counts = NestedSafetyCountsV0::default();
885
886    for selector in &facts.selectors {
887        if selector.kind != ParsedSelectorFactKind::Class {
888            continue;
889        }
890        let name = selector.name.clone();
891        names.push(name.clone());
892        let byte_span = byte_span_for_range(selector.range);
893        let nested_safety_kind = nested_safety_for_selector(blocks, &name).unwrap_or("flat");
894        let rule_block = selector_rule_block(blocks, &name, byte_span.start);
895        let rule_byte_span = rule_block
896            .map(|block| ParserByteSpanV0 {
897                start: block.rule_start,
898                end: block.rule_end,
899            })
900            .unwrap_or(byte_span);
901        let full_selector = rule_block
902            .and_then(|block| block.header_text.clone())
903            .unwrap_or_else(|| format!(".{name}"));
904        let declarations = rule_block
905            .and_then(|block| source.get(block.body_start..block.body_end))
906            .unwrap_or_default()
907            .trim()
908            .to_string();
909        let bem_suffix_parent_name = if nested_safety_kind == "bemSuffixSafe" {
910            bem_suffix_parent_name(&name)
911        } else {
912            None
913        };
914        match nested_safety_kind {
915            "bemSuffixSafe" => {
916                nested_safety_counts.bem_suffix_safe += 1;
917                bem_suffix_safe_names.push(name.clone());
918                if let Some(parent) = &bem_suffix_parent_name {
919                    bem_suffix_parent_names.push(parent.clone());
920                }
921            }
922            "nestedUnsafe" => {
923                nested_safety_counts.nested_unsafe += 1;
924                nested_unsafe_names.push(name.clone());
925            }
926            _ => nested_safety_counts.flat += 1,
927        }
928        let wrapper = wrapper_for_offset(blocks, byte_span.start);
929        definition_facts.push(ParserIndexSelectorDefinitionFactV0 {
930            name,
931            source_order: definition_facts.len(),
932            byte_span,
933            range: parser_range_for_byte_span(source, line_index, byte_span),
934            rule_byte_span,
935            rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
936            full_selector,
937            declarations,
938            nested_safety_kind,
939            bem_suffix_parent_name,
940            under_media: wrapper.under_media,
941            under_supports: wrapper.under_supports,
942            under_layer: wrapper.under_layer,
943        });
944    }
945
946    names.sort();
947    definition_facts.sort();
948    bem_suffix_parent_names.sort();
949    bem_suffix_safe_names.sort();
950    nested_unsafe_names.sort();
951
952    ParserIndexSelectorFactsV0 {
953        names,
954        definition_facts,
955        bem_suffix_count: bem_suffix_safe_names.len(),
956        bem_suffix_parent_names,
957        bem_suffix_safe_names,
958        nested_unsafe_names,
959        nested_safety_counts,
960        ..ParserIndexSelectorFactsV0::default()
961    }
962}
963
964fn summarize_values(
965    source: &str,
966    line_index: &SourceLineIndex,
967    facts: &ParsedStyleFacts,
968    blocks: &[StyleBlock],
969    syntax_index: &ProductSyntaxIndexV0,
970) -> ParserIndexValueFactsV0 {
971    let imported_sources_by_name = facts
972        .css_module_value_import_edges
973        .iter()
974        .map(|edge| (edge.local_name.clone(), edge.import_source.clone()))
975        .collect::<BTreeMap<_, _>>();
976    let imported_names = imported_sources_by_name
977        .keys()
978        .cloned()
979        .collect::<BTreeSet<_>>();
980    let local_decl_names = facts
981        .css_module_values
982        .iter()
983        .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
984        .map(|value| value.name.clone())
985        .filter(|name| !imported_names.contains(name))
986        .collect::<BTreeSet<_>>();
987    let mut decl_facts = Vec::new();
988    for value in &facts.css_module_values {
989        if value.kind != ParsedCssModuleValueFactKind::Definition
990            || !local_decl_names.contains(&value.name)
991        {
992            continue;
993        }
994        let byte_span = byte_span_for_range(value.range);
995        let rule_byte_span = syntax_index
996            .css_module_value_span_for_offset(byte_span.start)
997            .unwrap_or(byte_span);
998        decl_facts.push(ParserIndexValueDeclFactV0 {
999            name: value.name.clone(),
1000            value: syntax_index
1001                .css_module_value_text(source, byte_span.start)
1002                .unwrap_or_default(),
1003            source_order: decl_facts.len(),
1004            byte_span,
1005            range: parser_range_for_byte_span(source, line_index, byte_span),
1006            rule_byte_span,
1007            rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
1008        });
1009    }
1010    decl_facts.sort();
1011    decl_facts.dedup();
1012    let mut import_facts = Vec::new();
1013    for edge in &facts.css_module_value_import_edges {
1014        let byte_span = byte_span_for_range(edge.local_range);
1015        let remote_byte_span = byte_span_for_range(edge.remote_range);
1016        let imported_name_byte_span =
1017            (edge.remote_name != edge.local_name).then_some(remote_byte_span);
1018        let rule_byte_span = syntax_index
1019            .css_module_value_span_for_offset(byte_span.start)
1020            .unwrap_or(byte_span);
1021        import_facts.push(ParserIndexValueImportFactV0 {
1022            name: edge.local_name.clone(),
1023            imported_name: edge.remote_name.clone(),
1024            from: edge.import_source.clone(),
1025            source_order: import_facts.len(),
1026            byte_span,
1027            range: parser_range_for_byte_span(source, line_index, byte_span),
1028            imported_name_byte_span,
1029            imported_name_range: imported_name_byte_span
1030                .map(|span| parser_range_for_byte_span(source, line_index, span)),
1031            rule_byte_span,
1032            rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
1033        });
1034    }
1035    import_facts.sort();
1036    import_facts.dedup();
1037    let mut ref_facts = Vec::new();
1038    let value_decl_ref_names = facts
1039        .css_module_value_definition_edges
1040        .iter()
1041        .flat_map(|edge| edge.reference_names.iter().cloned())
1042        .collect::<Vec<_>>();
1043    let mut declaration_ref_names = Vec::new();
1044    let mut selectors_with_refs = BTreeSet::new();
1045    let mut selectors_with_local_refs = BTreeSet::new();
1046    let mut selectors_with_imported_refs = BTreeSet::new();
1047    let mut selectors_with_refs_under_media = BTreeSet::new();
1048    let mut selectors_with_refs_under_supports = BTreeSet::new();
1049    let mut selectors_with_refs_under_layer = BTreeSet::new();
1050    let mut selectors_with_local_refs_under_media = BTreeSet::new();
1051    let mut selectors_with_local_refs_under_supports = BTreeSet::new();
1052    let mut selectors_with_local_refs_under_layer = BTreeSet::new();
1053    let mut selectors_with_imported_refs_under_media = BTreeSet::new();
1054    let mut selectors_with_imported_refs_under_supports = BTreeSet::new();
1055    let mut selectors_with_imported_refs_under_layer = BTreeSet::new();
1056
1057    for value in &facts.css_module_values {
1058        if value.kind != ParsedCssModuleValueFactKind::Reference {
1059            continue;
1060        }
1061        if !local_decl_names.contains(&value.name) && !imported_names.contains(&value.name) {
1062            continue;
1063        }
1064        let offset = range_start(value.range);
1065        let selector_names = selector_names_for_offset(blocks, offset);
1066        if !selector_names.is_empty() {
1067            declaration_ref_names.push(value.name.clone());
1068            let byte_span = byte_span_for_range(value.range);
1069            ref_facts.push(ParserIndexValueRefFactV0 {
1070                name: value.name.clone(),
1071                source: "declaration",
1072                source_order: ref_facts.len(),
1073                byte_span,
1074                range: parser_range_for_byte_span(source, line_index, byte_span),
1075            });
1076            let wrapper = wrapper_for_offset(blocks, offset);
1077            for selector in selector_names {
1078                selectors_with_refs.insert(selector.clone());
1079                insert_by_wrapper(
1080                    &mut selectors_with_refs_under_media,
1081                    &mut selectors_with_refs_under_supports,
1082                    &mut selectors_with_refs_under_layer,
1083                    &selector,
1084                    &wrapper,
1085                );
1086                if local_decl_names.contains(&value.name) {
1087                    selectors_with_local_refs.insert(selector.clone());
1088                    insert_by_wrapper(
1089                        &mut selectors_with_local_refs_under_media,
1090                        &mut selectors_with_local_refs_under_supports,
1091                        &mut selectors_with_local_refs_under_layer,
1092                        &selector,
1093                        &wrapper,
1094                    );
1095                }
1096                if imported_names.contains(&value.name) {
1097                    selectors_with_imported_refs.insert(selector.clone());
1098                    insert_by_wrapper(
1099                        &mut selectors_with_imported_refs_under_media,
1100                        &mut selectors_with_imported_refs_under_supports,
1101                        &mut selectors_with_imported_refs_under_layer,
1102                        &selector,
1103                        &wrapper,
1104                    );
1105                }
1106            }
1107        } else {
1108            let byte_span = byte_span_for_range(value.range);
1109            ref_facts.push(ParserIndexValueRefFactV0 {
1110                name: value.name.clone(),
1111                source: "valueDecl",
1112                source_order: ref_facts.len(),
1113                byte_span,
1114                range: parser_range_for_byte_span(source, line_index, byte_span),
1115            });
1116        }
1117    }
1118    ref_facts.sort();
1119    ref_facts.dedup();
1120
1121    let mut value_decl_imported_ref_sources = Vec::new();
1122    for name in &value_decl_ref_names {
1123        if let Some(source) = imported_sources_by_name.get(name) {
1124            value_decl_imported_ref_sources.push(source.clone());
1125        }
1126    }
1127    let mut declaration_imported_ref_sources = Vec::new();
1128    for name in &declaration_ref_names {
1129        if let Some(source) = imported_sources_by_name.get(name) {
1130            declaration_imported_ref_sources.push(source.clone());
1131        }
1132    }
1133    let semantic_ref_names = declaration_ref_names
1134        .iter()
1135        .chain(value_decl_ref_names.iter())
1136        .cloned()
1137        .collect::<Vec<_>>();
1138
1139    ParserIndexValueFactsV0 {
1140        decl_names: sorted(local_decl_names.clone()),
1141        decl_facts,
1142        decl_names_with_local_refs: facts
1143            .css_module_value_definition_edges
1144            .iter()
1145            .filter(|edge| {
1146                edge.reference_names
1147                    .iter()
1148                    .any(|name| local_decl_names.contains(name))
1149            })
1150            .map(|edge| edge.definition_name.clone())
1151            .collect::<BTreeSet<_>>()
1152            .into_iter()
1153            .collect(),
1154        decl_names_with_imported_refs: facts
1155            .css_module_value_definition_edges
1156            .iter()
1157            .filter(|edge| {
1158                edge.reference_names
1159                    .iter()
1160                    .any(|name| imported_names.contains(name))
1161            })
1162            .map(|edge| edge.definition_name.clone())
1163            .collect::<BTreeSet<_>>()
1164            .into_iter()
1165            .collect(),
1166        import_names: facts
1167            .css_module_value_import_edges
1168            .iter()
1169            .map(|edge| edge.local_name.clone())
1170            .collect::<BTreeSet<_>>()
1171            .into_iter()
1172            .collect(),
1173        import_facts,
1174        import_sources: facts
1175            .css_module_value_import_edges
1176            .iter()
1177            .map(|edge| edge.import_source.clone())
1178            .collect::<Vec<_>>()
1179            .tap_sort(),
1180        import_alias_count: facts
1181            .css_module_value_import_edges
1182            .iter()
1183            .filter(|edge| edge.remote_name != edge.local_name)
1184            .count(),
1185        ref_names: semantic_ref_names.clone().tap_sort(),
1186        ref_facts,
1187        local_ref_names: semantic_ref_names
1188            .iter()
1189            .filter(|name| local_decl_names.contains(*name))
1190            .cloned()
1191            .collect::<Vec<_>>()
1192            .tap_sort(),
1193        imported_ref_names: semantic_ref_names
1194            .iter()
1195            .filter(|name| imported_names.contains(*name))
1196            .cloned()
1197            .collect::<Vec<_>>()
1198            .tap_sort(),
1199        imported_ref_sources: semantic_ref_names
1200            .iter()
1201            .filter_map(|name| imported_sources_by_name.get(name).cloned())
1202            .collect::<Vec<_>>()
1203            .tap_sort(),
1204        declaration_ref_names: declaration_ref_names.tap_sort(),
1205        declaration_imported_ref_sources: declaration_imported_ref_sources.tap_sort(),
1206        value_decl_ref_names: value_decl_ref_names.tap_sort(),
1207        value_decl_imported_ref_sources: value_decl_imported_ref_sources.tap_sort(),
1208        selectors_with_refs_names: sorted(selectors_with_refs),
1209        selectors_with_local_refs_names: sorted(selectors_with_local_refs),
1210        selectors_with_imported_refs_names: sorted(selectors_with_imported_refs),
1211        selectors_with_refs_under_media_names: sorted(selectors_with_refs_under_media),
1212        selectors_with_refs_under_supports_names: sorted(selectors_with_refs_under_supports),
1213        selectors_with_refs_under_layer_names: sorted(selectors_with_refs_under_layer),
1214        selectors_with_local_refs_under_media_names: sorted(selectors_with_local_refs_under_media),
1215        selectors_with_local_refs_under_supports_names: sorted(
1216            selectors_with_local_refs_under_supports,
1217        ),
1218        selectors_with_local_refs_under_layer_names: sorted(selectors_with_local_refs_under_layer),
1219        selectors_with_imported_refs_under_media_names: sorted(
1220            selectors_with_imported_refs_under_media,
1221        ),
1222        selectors_with_imported_refs_under_supports_names: sorted(
1223            selectors_with_imported_refs_under_supports,
1224        ),
1225        selectors_with_imported_refs_under_layer_names: sorted(
1226            selectors_with_imported_refs_under_layer,
1227        ),
1228    }
1229}
1230
1231fn summarize_custom_properties(
1232    source: &str,
1233    line_index: &SourceLineIndex,
1234    facts: &ParsedStyleFacts,
1235    blocks: &[StyleBlock],
1236    syntax_index: &ProductSyntaxIndexV0,
1237) -> ParserIndexCustomPropertyFactsV0 {
1238    let mut decl_facts = Vec::new();
1239    let mut ref_facts = Vec::new();
1240    for variable in &facts.variables {
1241        match variable.kind {
1242            ParsedVariableFactKind::CustomPropertyDeclaration => {
1243                let Some(property_key) = variable.property_key.clone() else {
1244                    continue;
1245                };
1246                let ParsedVariableFactNameV0::CustomProperty(name) = &variable.name else {
1247                    continue;
1248                };
1249                let byte_span = byte_span_for_range(variable.range);
1250                let wrapper = wrapper_for_offset(blocks, byte_span.start);
1251                let rule_byte_span = style_block_for_offset(blocks, byte_span.start)
1252                    .map(|block| ParserByteSpanV0 {
1253                        start: block.rule_start,
1254                        end: block.rule_end,
1255                    })
1256                    .or_else(|| syntax_index.declaration_span_for_offset(byte_span.start))
1257                    .unwrap_or(byte_span);
1258                decl_facts.push(ParserIndexCustomPropertyDeclFactV0 {
1259                    name: name.clone(),
1260                    property_key,
1261                    value: syntax_index
1262                        .declaration_value_text(source, byte_span.start)
1263                        .unwrap_or_default(),
1264                    source_order: decl_facts.len(),
1265                    byte_span,
1266                    range: parser_range_for_byte_span(source, line_index, byte_span),
1267                    rule_byte_span,
1268                    rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
1269                    selector_contexts: selector_contexts_for_offset(blocks, byte_span.start),
1270                    wrapper_at_rules: wrapper.wrapper_at_rules.clone(),
1271                    under_media: wrapper.under_media,
1272                    under_supports: wrapper.under_supports,
1273                    under_layer: wrapper.under_layer,
1274                });
1275            }
1276            ParsedVariableFactKind::CustomPropertyReference => {
1277                let Some(property_key) = variable.property_key.clone() else {
1278                    continue;
1279                };
1280                let ParsedVariableFactNameV0::CustomProperty(name) = &variable.name else {
1281                    continue;
1282                };
1283                let byte_span = byte_span_for_range(variable.range);
1284                let wrapper = wrapper_for_offset(blocks, byte_span.start);
1285                ref_facts.push(ParserIndexCustomPropertyRefFactV0 {
1286                    name: name.clone(),
1287                    property_key,
1288                    source_order: ref_facts.len(),
1289                    byte_span,
1290                    range: parser_range_for_byte_span(source, line_index, byte_span),
1291                    selector_contexts: selector_contexts_for_offset(blocks, byte_span.start),
1292                    wrapper_at_rules: wrapper.wrapper_at_rules.clone(),
1293                    under_media: wrapper.under_media,
1294                    under_supports: wrapper.under_supports,
1295                    under_layer: wrapper.under_layer,
1296                });
1297            }
1298            _ => {}
1299        }
1300    }
1301    decl_facts.sort();
1302    decl_facts.dedup();
1303    ref_facts.sort();
1304    ref_facts.dedup();
1305    ParserIndexCustomPropertyFactsV0 {
1306        decl_names: sorted_authored_custom_properties(
1307            decl_facts.iter().map(|fact| fact.name.clone()),
1308        ),
1309        decl_context_selectors: sorted(
1310            decl_facts
1311                .iter()
1312                .flat_map(|fact| fact.selector_contexts.iter().cloned())
1313                .collect(),
1314        ),
1315        decl_names_under_media: sorted_authored_custom_properties(
1316            decl_facts
1317                .iter()
1318                .filter(|fact| fact.under_media)
1319                .map(|fact| fact.name.clone()),
1320        ),
1321        decl_names_under_supports: sorted_authored_custom_properties(
1322            decl_facts
1323                .iter()
1324                .filter(|fact| fact.under_supports)
1325                .map(|fact| fact.name.clone()),
1326        ),
1327        decl_names_under_layer: sorted_authored_custom_properties(
1328            decl_facts
1329                .iter()
1330                .filter(|fact| fact.under_layer)
1331                .map(|fact| fact.name.clone()),
1332        ),
1333        ref_names: sorted_authored_custom_properties(
1334            ref_facts.iter().map(|fact| fact.name.clone()),
1335        ),
1336        selectors_with_refs_names: sorted(
1337            ref_facts
1338                .iter()
1339                .flat_map(|fact| selector_names_from_contexts(&fact.selector_contexts))
1340                .collect(),
1341        ),
1342        selectors_with_refs_under_media_names: sorted(
1343            ref_facts
1344                .iter()
1345                .filter(|fact| fact.under_media)
1346                .flat_map(|fact| selector_names_from_contexts(&fact.selector_contexts))
1347                .collect(),
1348        ),
1349        selectors_with_refs_under_supports_names: sorted(
1350            ref_facts
1351                .iter()
1352                .filter(|fact| fact.under_supports)
1353                .flat_map(|fact| selector_names_from_contexts(&fact.selector_contexts))
1354                .collect(),
1355        ),
1356        selectors_with_refs_under_layer_names: sorted(
1357            ref_facts
1358                .iter()
1359                .filter(|fact| fact.under_layer)
1360                .flat_map(|fact| selector_names_from_contexts(&fact.selector_contexts))
1361                .collect(),
1362        ),
1363        decl_facts,
1364        ref_facts,
1365    }
1366}
1367
1368fn summarize_sass(
1369    source: &str,
1370    line_index: &SourceLineIndex,
1371    facts: &ParsedStyleFacts,
1372    blocks: &[StyleBlock],
1373    syntax_index: &ProductSyntaxIndexV0,
1374) -> ParserIndexSassFactsV0 {
1375    let mut variable_decl_names = BTreeSet::new();
1376    let mut variable_parameter_names = BTreeSet::new();
1377    let mut variable_ref_names = BTreeSet::new();
1378    let mut mixin_decl_names = BTreeSet::new();
1379    let mut mixin_include_names = BTreeSet::new();
1380    let mut function_decl_names = BTreeSet::new();
1381    let mut function_call_names = BTreeSet::new();
1382    let mut symbol_decl_facts = Vec::new();
1383    let mut selector_symbol_facts = Vec::new();
1384    let mut global_variable_decl_names = BTreeSet::new();
1385    let mut variable_decl_scopes = Vec::new();
1386
1387    for symbol in &facts.sass_symbols {
1388        let byte_span = byte_span_for_range(symbol.range);
1389        let range = parser_range_for_byte_span(source, line_index, byte_span);
1390        match symbol.kind {
1391            ParsedSassSymbolFactKind::VariableDeclaration => {
1392                if symbol.role == "parameter"
1393                    || syntax_index.sass_parameter_list_contains(byte_span.start)
1394                {
1395                    variable_parameter_names.insert(symbol.name.clone());
1396                } else {
1397                    variable_decl_names.insert(symbol.name.clone());
1398                    let selector_names = selector_names_for_offset(blocks, byte_span.start);
1399                    if selector_names.is_empty() {
1400                        global_variable_decl_names.insert(symbol.name.clone());
1401                    }
1402                    variable_decl_scopes.push(SassVariableDeclScope {
1403                        name: symbol.name.clone(),
1404                        selector_names,
1405                    });
1406                }
1407                symbol_decl_facts.push(ParserIndexSassSymbolDeclFactV0 {
1408                    symbol_kind: symbol.symbol_kind,
1409                    name: symbol.name.clone(),
1410                    role: symbol.role,
1411                    byte_span,
1412                    range,
1413                });
1414            }
1415            ParsedSassSymbolFactKind::MixinDeclaration => {
1416                mixin_decl_names.insert(symbol.name.clone());
1417                symbol_decl_facts.push(ParserIndexSassSymbolDeclFactV0 {
1418                    symbol_kind: symbol.symbol_kind,
1419                    name: symbol.name.clone(),
1420                    role: symbol.role,
1421                    byte_span,
1422                    range,
1423                });
1424            }
1425            ParsedSassSymbolFactKind::FunctionDeclaration => {
1426                function_decl_names.insert(symbol.name.clone());
1427                symbol_decl_facts.push(ParserIndexSassSymbolDeclFactV0 {
1428                    symbol_kind: symbol.symbol_kind,
1429                    name: symbol.name.clone(),
1430                    role: symbol.role,
1431                    byte_span,
1432                    range,
1433                });
1434            }
1435            ParsedSassSymbolFactKind::VariableReference => {
1436                variable_ref_names.insert(symbol.name.clone());
1437            }
1438            ParsedSassSymbolFactKind::MixinInclude => {
1439                if symbol.namespace.is_none() {
1440                    mixin_include_names.insert(symbol.name.clone());
1441                }
1442            }
1443            ParsedSassSymbolFactKind::FunctionCall => {
1444                if symbol.namespace.is_none() {
1445                    function_call_names.insert(symbol.name.clone());
1446                }
1447            }
1448        }
1449    }
1450
1451    let mut resolved_variable_ref_names = BTreeSet::new();
1452    let mut unresolved_variable_ref_names = BTreeSet::new();
1453    for symbol in &facts.sass_symbols {
1454        if symbol.kind != ParsedSassSymbolFactKind::VariableReference || symbol.namespace.is_some()
1455        {
1456            continue;
1457        }
1458        if is_sass_variable_reference_resolved(
1459            &symbol.name,
1460            range_start(symbol.range),
1461            blocks,
1462            &global_variable_decl_names,
1463            &variable_parameter_names,
1464            &variable_decl_scopes,
1465        ) {
1466            resolved_variable_ref_names.insert(symbol.name.clone());
1467        } else {
1468            unresolved_variable_ref_names.insert(symbol.name.clone());
1469        }
1470    }
1471
1472    let same_file_resolution = ParserIndexSassSameFileResolutionFactsV0 {
1473        resolved_variable_ref_names: sorted(resolved_variable_ref_names),
1474        unresolved_variable_ref_names: sorted(unresolved_variable_ref_names),
1475        resolved_mixin_include_names: sorted(
1476            mixin_include_names
1477                .iter()
1478                .filter(|name| mixin_decl_names.contains(*name))
1479                .cloned()
1480                .collect(),
1481        ),
1482        unresolved_mixin_include_names: sorted(
1483            mixin_include_names
1484                .iter()
1485                .filter(|name| !mixin_decl_names.contains(*name))
1486                .cloned()
1487                .collect(),
1488        ),
1489        resolved_function_call_names: sorted(
1490            function_call_names
1491                .iter()
1492                .filter(|name| function_decl_names.contains(*name))
1493                .cloned()
1494                .collect(),
1495        ),
1496    };
1497
1498    for symbol in &facts.sass_symbols {
1499        if matches!(
1500            symbol.kind,
1501            ParsedSassSymbolFactKind::VariableDeclaration
1502                | ParsedSassSymbolFactKind::MixinDeclaration
1503                | ParsedSassSymbolFactKind::FunctionDeclaration
1504        ) {
1505            continue;
1506        }
1507        let offset = range_start(symbol.range);
1508        let byte_span = byte_span_for_range(symbol.range);
1509        for selector_name in selector_names_for_offset(blocks, offset) {
1510            let resolution = match symbol.kind {
1511                ParsedSassSymbolFactKind::VariableReference if symbol.namespace.is_some() => {
1512                    "external"
1513                }
1514                ParsedSassSymbolFactKind::VariableReference
1515                    if is_sass_variable_reference_resolved(
1516                        &symbol.name,
1517                        offset,
1518                        blocks,
1519                        &global_variable_decl_names,
1520                        &variable_parameter_names,
1521                        &variable_decl_scopes,
1522                    ) =>
1523                {
1524                    "resolved"
1525                }
1526                ParsedSassSymbolFactKind::MixinInclude if symbol.namespace.is_some() => "external",
1527                ParsedSassSymbolFactKind::MixinInclude
1528                    if same_file_resolution
1529                        .resolved_mixin_include_names
1530                        .contains(&symbol.name) =>
1531                {
1532                    "resolved"
1533                }
1534                ParsedSassSymbolFactKind::FunctionCall if symbol.namespace.is_some() => "external",
1535                ParsedSassSymbolFactKind::FunctionCall
1536                    if same_file_resolution
1537                        .resolved_function_call_names
1538                        .contains(&symbol.name) =>
1539                {
1540                    "resolved"
1541                }
1542                _ => "unresolved",
1543            };
1544            selector_symbol_facts.push(ParserIndexSassSelectorSymbolFactV0 {
1545                selector_name,
1546                symbol_kind: symbol.symbol_kind,
1547                name: symbol.name.clone(),
1548                namespace: symbol.namespace.clone(),
1549                role: symbol.role,
1550                resolution,
1551                byte_span,
1552                range: parser_range_for_byte_span(source, line_index, byte_span),
1553            });
1554        }
1555    }
1556    selector_symbol_facts.sort();
1557    selector_symbol_facts.dedup();
1558
1559    let mut module_use_sources = BTreeSet::new();
1560    let mut module_forward_sources = BTreeSet::new();
1561    let mut module_import_sources = BTreeSet::new();
1562    let mut module_use_edges = Vec::new();
1563    let mut module_forward_edges = Vec::new();
1564    for edge in &facts.sass_module_edges {
1565        match edge.kind {
1566            ParsedSassModuleEdgeFactKind::Use => {
1567                let byte_span = byte_span_for_range(edge.range);
1568                module_use_sources.insert(edge.source.clone());
1569                module_use_edges.push(ParserIndexSassModuleUseFactV0 {
1570                    source: edge.source.clone(),
1571                    namespace_kind: edge.namespace_kind.unwrap_or("default"),
1572                    namespace: edge.namespace.clone(),
1573                    byte_span,
1574                    range: parser_range_for_byte_span(source, line_index, byte_span),
1575                });
1576            }
1577            ParsedSassModuleEdgeFactKind::Forward => {
1578                let byte_span = byte_span_for_range(edge.range);
1579                let rule_byte_span = syntax_index
1580                    .scss_forward_span_for_offset(byte_span.start)
1581                    .unwrap_or(byte_span);
1582                module_forward_sources.insert(edge.source.clone());
1583                module_forward_edges.push(ParserIndexSassModuleForwardFactV0 {
1584                    source: edge.source.clone(),
1585                    prefix: sass_module_forward_prefix_from_statement(source, rule_byte_span),
1586                    visibility_kind: edge.visibility_filter_kind.unwrap_or("all"),
1587                    visibility_members: edge
1588                        .visibility_filter_names
1589                        .iter()
1590                        .map(|name| ParserIndexSassModuleForwardMemberV0 {
1591                            name: name.clone(),
1592                            symbol_kind: sass_module_forward_member_symbol_kind(
1593                                source,
1594                                rule_byte_span,
1595                                name,
1596                            ),
1597                        })
1598                        .collect(),
1599                    byte_span,
1600                    range: parser_range_for_byte_span(source, line_index, byte_span),
1601                    rule_byte_span,
1602                    rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
1603                });
1604            }
1605            ParsedSassModuleEdgeFactKind::Import => {
1606                let byte_span = byte_span_for_range(edge.range);
1607                module_use_sources.insert(edge.source.clone());
1608                module_import_sources.insert(edge.source.clone());
1609                module_use_edges.push(ParserIndexSassModuleUseFactV0 {
1610                    source: edge.source.clone(),
1611                    namespace_kind: "wildcard",
1612                    namespace: None,
1613                    byte_span,
1614                    range: parser_range_for_byte_span(source, line_index, byte_span),
1615                });
1616            }
1617        }
1618    }
1619    module_use_edges.sort();
1620    module_use_edges.dedup();
1621    module_forward_edges.sort();
1622    module_forward_edges.dedup();
1623
1624    ParserIndexSassFactsV0 {
1625        variable_decl_names: sorted(variable_decl_names),
1626        symbol_decl_facts,
1627        variable_parameter_names: sorted(variable_parameter_names.clone()),
1628        variable_ref_names: sorted(variable_ref_names),
1629        selectors_with_variable_refs_names: selector_names_for_variable_symbols(
1630            blocks,
1631            facts,
1632            &global_variable_decl_names,
1633            &variable_parameter_names,
1634            &variable_decl_scopes,
1635            None,
1636        ),
1637        selectors_with_resolved_variable_refs_names: selector_names_for_variable_symbols(
1638            blocks,
1639            facts,
1640            &global_variable_decl_names,
1641            &variable_parameter_names,
1642            &variable_decl_scopes,
1643            Some(true),
1644        ),
1645        selectors_with_unresolved_variable_refs_names: selector_names_for_variable_symbols(
1646            blocks,
1647            facts,
1648            &global_variable_decl_names,
1649            &variable_parameter_names,
1650            &variable_decl_scopes,
1651            Some(false),
1652        ),
1653        mixin_decl_names: sorted(mixin_decl_names),
1654        mixin_include_names: sorted(mixin_include_names),
1655        selectors_with_mixin_includes_names: selector_names_for_symbols(
1656            blocks,
1657            facts,
1658            ParsedSassSymbolFactKind::MixinInclude,
1659            None,
1660        ),
1661        selectors_with_resolved_mixin_includes_names: selector_names_for_symbols(
1662            blocks,
1663            facts,
1664            ParsedSassSymbolFactKind::MixinInclude,
1665            Some(&same_file_resolution.resolved_mixin_include_names),
1666        ),
1667        selectors_with_unresolved_mixin_includes_names: selector_names_for_symbols(
1668            blocks,
1669            facts,
1670            ParsedSassSymbolFactKind::MixinInclude,
1671            Some(&same_file_resolution.unresolved_mixin_include_names),
1672        ),
1673        function_decl_names: sorted(function_decl_names),
1674        function_call_names: sorted(function_call_names),
1675        selectors_with_function_calls_names: selector_names_for_symbols(
1676            blocks,
1677            facts,
1678            ParsedSassSymbolFactKind::FunctionCall,
1679            None,
1680        ),
1681        selector_symbol_facts,
1682        module_use_sources: sorted(module_use_sources),
1683        module_use_edges,
1684        module_forward_sources: sorted(module_forward_sources),
1685        module_forward_edges,
1686        module_import_sources: sorted(module_import_sources),
1687        same_file_resolution,
1688    }
1689}
1690
1691fn summarize_keyframes(
1692    source: &str,
1693    line_index: &SourceLineIndex,
1694    facts: &ParsedStyleFacts,
1695    blocks: &[StyleBlock],
1696    syntax_index: &ProductSyntaxIndexV0,
1697) -> ParserIndexKeyframesFactsV0 {
1698    let mut names = Vec::new();
1699    let mut decl_facts = Vec::new();
1700    let mut names_under_media = BTreeSet::new();
1701    let mut names_under_supports = BTreeSet::new();
1702    let mut names_under_layer = BTreeSet::new();
1703    let mut animation_ref_names = Vec::new();
1704    let mut animation_name_ref_names = Vec::new();
1705    let mut ref_facts = Vec::new();
1706    let mut selectors_with_animation_ref_names = BTreeSet::new();
1707    let mut selectors_with_animation_name_ref_names = BTreeSet::new();
1708    let mut selectors_with_animation_refs_under_media_names = BTreeSet::new();
1709    let mut selectors_with_animation_refs_under_supports_names = BTreeSet::new();
1710    let mut selectors_with_animation_refs_under_layer_names = BTreeSet::new();
1711    let mut selectors_with_animation_name_refs_under_media_names = BTreeSet::new();
1712    let mut selectors_with_animation_name_refs_under_supports_names = BTreeSet::new();
1713    let mut selectors_with_animation_name_refs_under_layer_names = BTreeSet::new();
1714    let declared_keyframes = facts
1715        .animations
1716        .iter()
1717        .filter(|animation| animation.kind == ParsedAnimationFactKind::KeyframesDeclaration)
1718        .map(|animation| animation.name.clone())
1719        .collect::<BTreeSet<_>>();
1720
1721    for animation in &facts.animations {
1722        let offset = range_start(animation.range);
1723        let wrapper = wrapper_for_offset(blocks, offset);
1724        match animation.kind {
1725            ParsedAnimationFactKind::KeyframesDeclaration => {
1726                let byte_span = byte_span_for_range(animation.range);
1727                let rule_byte_span = syntax_index
1728                    .keyframes_span_for_offset(byte_span.start)
1729                    .unwrap_or(byte_span);
1730                decl_facts.push(ParserIndexKeyframesDeclFactV0 {
1731                    name: animation.name.clone(),
1732                    source_order: decl_facts.len(),
1733                    byte_span,
1734                    range: parser_range_for_byte_span(source, line_index, byte_span),
1735                    rule_byte_span,
1736                    rule_range: parser_range_for_byte_span(source, line_index, rule_byte_span),
1737                });
1738                names.push(animation.name.clone());
1739                insert_by_wrapper(
1740                    &mut names_under_media,
1741                    &mut names_under_supports,
1742                    &mut names_under_layer,
1743                    &animation.name,
1744                    &wrapper,
1745                );
1746            }
1747            ParsedAnimationFactKind::AnimationNameReference => {
1748                let byte_span = byte_span_for_range(animation.range);
1749                let property = if syntax_index
1750                    .declaration_property_key_for_offset(offset)
1751                    .is_some_and(|key| {
1752                        key == CanonicalPropertyKeyV0::Standard(
1753                            PropertyNameV0::canonical_standard_key("animation-name"),
1754                        )
1755                    }) {
1756                    "animation-name"
1757                } else {
1758                    "animation"
1759                };
1760                ref_facts.push(ParserIndexAnimationNameRefFactV0 {
1761                    name: animation.name.clone(),
1762                    property,
1763                    source_order: ref_facts.len(),
1764                    byte_span,
1765                    range: parser_range_for_byte_span(source, line_index, byte_span),
1766                });
1767                if !declared_keyframes.contains(&animation.name) {
1768                    continue;
1769                }
1770                let selectors = selector_names_for_offset(blocks, offset);
1771                if property == "animation-name" {
1772                    animation_name_ref_names.push(animation.name.clone());
1773                    for selector in selectors {
1774                        selectors_with_animation_name_ref_names.insert(selector.clone());
1775                        insert_by_wrapper(
1776                            &mut selectors_with_animation_name_refs_under_media_names,
1777                            &mut selectors_with_animation_name_refs_under_supports_names,
1778                            &mut selectors_with_animation_name_refs_under_layer_names,
1779                            &selector,
1780                            &wrapper,
1781                        );
1782                    }
1783                } else {
1784                    animation_ref_names.push(animation.name.clone());
1785                    for selector in selectors {
1786                        selectors_with_animation_ref_names.insert(selector.clone());
1787                        insert_by_wrapper(
1788                            &mut selectors_with_animation_refs_under_media_names,
1789                            &mut selectors_with_animation_refs_under_supports_names,
1790                            &mut selectors_with_animation_refs_under_layer_names,
1791                            &selector,
1792                            &wrapper,
1793                        );
1794                    }
1795                }
1796            }
1797        }
1798    }
1799    decl_facts.sort();
1800    decl_facts.dedup();
1801    ref_facts.sort();
1802    ref_facts.dedup();
1803
1804    ParserIndexKeyframesFactsV0 {
1805        names: names.tap_sort_unique(),
1806        decl_facts,
1807        names_under_media: sorted(names_under_media),
1808        names_under_supports: sorted(names_under_supports),
1809        names_under_layer: sorted(names_under_layer),
1810        animation_ref_names: animation_ref_names.tap_sort_unique(),
1811        animation_name_ref_names: animation_name_ref_names.tap_sort_unique(),
1812        ref_facts,
1813        selectors_with_animation_ref_names: sorted(selectors_with_animation_ref_names),
1814        selectors_with_animation_name_ref_names: sorted(selectors_with_animation_name_ref_names),
1815        selectors_with_animation_refs_under_media_names: sorted(
1816            selectors_with_animation_refs_under_media_names,
1817        ),
1818        selectors_with_animation_refs_under_supports_names: sorted(
1819            selectors_with_animation_refs_under_supports_names,
1820        ),
1821        selectors_with_animation_refs_under_layer_names: sorted(
1822            selectors_with_animation_refs_under_layer_names,
1823        ),
1824        selectors_with_animation_name_refs_under_media_names: sorted(
1825            selectors_with_animation_name_refs_under_media_names,
1826        ),
1827        selectors_with_animation_name_refs_under_supports_names: sorted(
1828            selectors_with_animation_name_refs_under_supports_names,
1829        ),
1830        selectors_with_animation_name_refs_under_layer_names: sorted(
1831            selectors_with_animation_name_refs_under_layer_names,
1832        ),
1833    }
1834}
1835
1836fn summarize_composes(
1837    source: &str,
1838    line_index: &SourceLineIndex,
1839    facts: &ParsedStyleFacts,
1840    blocks: &[StyleBlock],
1841) -> ParserIndexComposesFactsV0 {
1842    let mut summary = ParserIndexComposesFactsV0::default();
1843    for edge in &facts.css_module_composes_edges {
1844        let byte_span = byte_span_for_range(edge.range);
1845        summary.edges.push(ParserIndexComposesEdgeFactV0 {
1846            kind: match edge.kind {
1847                ParsedCssModuleComposesEdgeKind::Local => "local",
1848                ParsedCssModuleComposesEdgeKind::External => "external",
1849                ParsedCssModuleComposesEdgeKind::Global => "global",
1850            },
1851            owner_selector_names: edge.owner_selector_names.clone(),
1852            target_names: edge.target_names.clone(),
1853            import_source: edge.import_source.clone(),
1854            class_tokens: composes_class_tokens_for_edge(source, line_index, facts, edge),
1855            byte_span,
1856            range: parser_range_for_byte_span(source, line_index, byte_span),
1857        });
1858        let wrapper = wrapper_for_offset(blocks, range_start(edge.range));
1859        let count = edge.owner_selector_names.len() * edge.target_names.len();
1860        summary.class_name_count += count;
1861        for owner in &edge.owner_selector_names {
1862            summary.selectors_with_composes_names.push(owner.clone());
1863            insert_vec_by_wrapper(
1864                &mut summary.selectors_with_composes_under_media_names,
1865                &mut summary.selectors_with_composes_under_supports_names,
1866                &mut summary.selectors_with_composes_under_layer_names,
1867                owner,
1868                &wrapper,
1869            );
1870        }
1871        match edge.kind {
1872            ParsedCssModuleComposesEdgeKind::Local => {
1873                summary.local_class_name_count += count;
1874                for owner in &edge.owner_selector_names {
1875                    summary.local_selector_names.push(owner.clone());
1876                    insert_vec_by_wrapper(
1877                        &mut summary.local_selector_names_under_media,
1878                        &mut summary.local_selector_names_under_supports,
1879                        &mut summary.local_selector_names_under_layer,
1880                        owner,
1881                        &wrapper,
1882                    );
1883                }
1884            }
1885            ParsedCssModuleComposesEdgeKind::External => {
1886                summary.imported_class_name_count += count;
1887                for owner in &edge.owner_selector_names {
1888                    summary.imported_selector_names.push(owner.clone());
1889                    insert_vec_by_wrapper(
1890                        &mut summary.imported_selector_names_under_media,
1891                        &mut summary.imported_selector_names_under_supports,
1892                        &mut summary.imported_selector_names_under_layer,
1893                        owner,
1894                        &wrapper,
1895                    );
1896                    if let Some(source) = &edge.import_source {
1897                        summary.import_sources.push(source.clone());
1898                        if wrapper.under_media {
1899                            summary.import_sources_under_media.push(source.clone());
1900                        }
1901                        if wrapper.under_supports {
1902                            summary.import_sources_under_supports.push(source.clone());
1903                        }
1904                        if wrapper.under_layer {
1905                            summary.import_sources_under_layer.push(source.clone());
1906                        }
1907                    }
1908                }
1909            }
1910            ParsedCssModuleComposesEdgeKind::Global => {
1911                summary.global_class_name_count += count;
1912                for owner in &edge.owner_selector_names {
1913                    summary.global_selector_names.push(owner.clone());
1914                    insert_vec_by_wrapper(
1915                        &mut summary.global_selector_names_under_media,
1916                        &mut summary.global_selector_names_under_supports,
1917                        &mut summary.global_selector_names_under_layer,
1918                        owner,
1919                        &wrapper,
1920                    );
1921                }
1922            }
1923        }
1924    }
1925    sort_all_composes(&mut summary);
1926    summary.edges.sort();
1927    summary.edges.dedup();
1928    summary
1929}
1930
1931fn composes_class_tokens_for_edge(
1932    source: &str,
1933    line_index: &SourceLineIndex,
1934    facts: &ParsedStyleFacts,
1935    edge: &crate::ParsedCssModuleComposesEdgeFact,
1936) -> Vec<ParserIndexComposesClassTokenV0> {
1937    let target_names = edge
1938        .target_names
1939        .iter()
1940        .map(String::as_str)
1941        .collect::<BTreeSet<_>>();
1942    let edge_start = range_start(edge.range);
1943    let edge_end = u32::from(edge.range.end()) as usize;
1944    let mut class_tokens = facts
1945        .css_module_composes
1946        .iter()
1947        .filter(|fact| fact.kind == ParsedCssModuleComposesFactKind::Target)
1948        .filter(|fact| target_names.contains(fact.name.as_str()))
1949        .filter(|fact| {
1950            let token_start = range_start(fact.range);
1951            let token_end = u32::from(fact.range.end()) as usize;
1952            token_start >= edge_start && token_end <= edge_end
1953        })
1954        .map(|fact| {
1955            let byte_span = byte_span_for_range(fact.range);
1956            ParserIndexComposesClassTokenV0 {
1957                class_name: fact.name.clone(),
1958                byte_span,
1959                range: parser_range_for_byte_span(source, line_index, byte_span),
1960            }
1961        })
1962        .collect::<Vec<_>>();
1963    class_tokens.sort();
1964    class_tokens.dedup();
1965    class_tokens
1966}
1967
1968fn summarize_wrappers(blocks: &[StyleBlock]) -> ParserIndexWrapperFactsV0 {
1969    ParserIndexWrapperFactsV0 {
1970        selectors_under_media_names: sorted(
1971            blocks
1972                .iter()
1973                .filter(|block| block.under_media)
1974                .flat_map(|block| {
1975                    block
1976                        .names
1977                        .iter()
1978                        .filter(|name| !name.starts_with("__selector_meta:"))
1979                        .cloned()
1980                })
1981                .collect(),
1982        ),
1983        selectors_under_supports_names: sorted(
1984            blocks
1985                .iter()
1986                .filter(|block| block.under_supports)
1987                .flat_map(|block| {
1988                    block
1989                        .names
1990                        .iter()
1991                        .filter(|name| !name.starts_with("__selector_meta:"))
1992                        .cloned()
1993                })
1994                .collect(),
1995        ),
1996        selectors_under_layer_names: sorted(
1997            blocks
1998                .iter()
1999                .filter(|block| block.under_layer)
2000                .flat_map(|block| {
2001                    block
2002                        .names
2003                        .iter()
2004                        .filter(|name| !name.starts_with("__selector_meta:"))
2005                        .cloned()
2006                })
2007                .collect(),
2008        ),
2009    }
2010}
2011
2012fn resolve_selector_header_text(
2013    source: &str,
2014    header: &str,
2015    parent_branches: &[SelectorBranch],
2016) -> Vec<SelectorBranch> {
2017    split_selector_groups_text(header)
2018        .into_iter()
2019        .flat_map(|group| resolve_selector_group_text(source, header, group, parent_branches))
2020        .collect()
2021}
2022
2023fn resolve_selector_group_text(
2024    source: &str,
2025    full_header: &str,
2026    group: &str,
2027    parent_branches: &[SelectorBranch],
2028) -> Vec<SelectorBranch> {
2029    let group = group.trim();
2030    if group.starts_with(":global") && !group.starts_with(":local") {
2031        return Vec::new();
2032    }
2033    let tail = selector_tail(group);
2034    if let Some(suffix) = tail.strip_prefix('&').map(str::trim)
2035        && is_ampersand_suffix_text(suffix)
2036    {
2037        let span = source_span_for_header_piece(source, full_header, suffix);
2038        return parent_branches
2039            .iter()
2040            .map(|parent| SelectorBranch {
2041                name: format!("{}{}", parent.name, suffix),
2042                name_span: span,
2043                bare_suffix_base: parent.bare_suffix_base,
2044                amp_suffix_depth: parent.amp_suffix_depth + 1,
2045            })
2046            .collect();
2047    }
2048    let names = class_names_in_selector(tail, source, full_header);
2049    let bare_suffix_base = parent_branches.is_empty() && names.len() == 1;
2050    names
2051        .into_iter()
2052        .map(|(name, name_span)| SelectorBranch {
2053            name,
2054            name_span,
2055            bare_suffix_base,
2056            amp_suffix_depth: 0,
2057        })
2058        .collect()
2059}
2060
2061fn is_ampersand_suffix_text(suffix: &str) -> bool {
2062    suffix
2063        .chars()
2064        .next()
2065        .is_some_and(|ch| ch == '-' || ch == '_' || ch.is_ascii_alphanumeric())
2066}
2067
2068fn classify_nested_safety(
2069    header: &str,
2070    branches: &[SelectorBranch],
2071    parent_branches: &[SelectorBranch],
2072    parent_is_grouped: bool,
2073) -> &'static str {
2074    if branches.is_empty() {
2075        return "flat";
2076    }
2077    let is_nested = !parent_branches.is_empty() || header.contains('&');
2078    if !is_nested {
2079        return "flat";
2080    }
2081    let header = header.trim();
2082    let bem_suffix_safe = branches.len() == 1
2083        && parent_branches.len() == 1
2084        && parent_branches[0].bare_suffix_base
2085        && !parent_is_grouped
2086        && header.starts_with('&')
2087        && (header[1..].trim_start().starts_with("__")
2088            || header[1..].trim_start().starts_with("--"));
2089    let chained_bem_modifier_safe = header.starts_with('&')
2090        && header[1..].trim_start().starts_with("--")
2091        && !parent_branches.is_empty()
2092        && parent_branches
2093            .iter()
2094            .all(|parent| parent.amp_suffix_depth > 0);
2095    if bem_suffix_safe || chained_bem_modifier_safe {
2096        "bemSuffixSafe"
2097    } else {
2098        "nestedUnsafe"
2099    }
2100}
2101
2102fn nested_safety_for_selector(blocks: &[StyleBlock], name: &str) -> Option<&'static str> {
2103    blocks.iter().find_map(|block| {
2104        block.names.iter().find_map(|entry| {
2105            entry
2106                .strip_prefix("__selector_meta:")
2107                .and_then(|rest| rest.rsplit_once(':'))
2108                .and_then(|(entry_name, kind)| {
2109                    (entry_name == name).then_some(match kind {
2110                        "bemSuffixSafe" => "bemSuffixSafe",
2111                        "nestedUnsafe" => "nestedUnsafe",
2112                        _ => "flat",
2113                    })
2114                })
2115        })
2116    })
2117}
2118
2119fn selector_rule_block<'a>(
2120    blocks: &'a [StyleBlock],
2121    name: &str,
2122    selector_offset: usize,
2123) -> Option<&'a StyleBlock> {
2124    blocks
2125        .iter()
2126        .filter(|block| block.start <= selector_offset && selector_offset < block.end)
2127        .filter(|block| {
2128            block.names.iter().any(|entry| {
2129                entry
2130                    .strip_prefix("__selector_meta:")
2131                    .and_then(|rest| rest.rsplit_once(':'))
2132                    .is_some_and(|(entry_name, _)| entry_name == name)
2133            })
2134        })
2135        .max_by_key(|block| block.rule_start)
2136}
2137
2138fn style_block_for_offset(blocks: &[StyleBlock], offset: usize) -> Option<&StyleBlock> {
2139    blocks
2140        .iter()
2141        .filter(|block| !block.names.is_empty())
2142        .filter(|block| block.start <= offset && offset < block.end)
2143        .max_by_key(|block| block.rule_start)
2144}
2145
2146fn split_selector_groups_text(header: &str) -> Vec<&str> {
2147    let mut groups = Vec::new();
2148    let mut start = 0usize;
2149    let mut paren_depth = 0usize;
2150    let mut bracket_depth = 0usize;
2151    for (index, byte) in header.bytes().enumerate() {
2152        match byte {
2153            b'(' => paren_depth += 1,
2154            b')' => paren_depth = paren_depth.saturating_sub(1),
2155            b'[' => bracket_depth += 1,
2156            b']' => bracket_depth = bracket_depth.saturating_sub(1),
2157            b',' if paren_depth == 0 && bracket_depth == 0 => {
2158                groups.push(&header[start..index]);
2159                start = index + 1;
2160            }
2161            _ => {}
2162        }
2163    }
2164    groups.push(&header[start..]);
2165    groups
2166}
2167
2168fn selector_tail(group: &str) -> &str {
2169    let mut tail_start = 0usize;
2170    let mut paren_depth = 0usize;
2171    let mut bracket_depth = 0usize;
2172    let bytes = group.as_bytes();
2173    let mut index = 0usize;
2174    while index < bytes.len() {
2175        match bytes[index] {
2176            b'(' => paren_depth += 1,
2177            b')' => paren_depth = paren_depth.saturating_sub(1),
2178            b'[' => bracket_depth += 1,
2179            b']' => bracket_depth = bracket_depth.saturating_sub(1),
2180            b'>' | b'+' | b'~' if paren_depth == 0 && bracket_depth == 0 => tail_start = index + 1,
2181            byte if byte.is_ascii_whitespace() && paren_depth == 0 && bracket_depth == 0 => {
2182                let previous = group[..index].trim_end().as_bytes().last().copied();
2183                let next = group[index + 1..].trim_start().as_bytes().first().copied();
2184                if previous.is_some()
2185                    && next.is_some_and(|value| value == b'.' || value == b':' || value == b'&')
2186                {
2187                    tail_start = index + 1;
2188                }
2189            }
2190            _ => {}
2191        }
2192        index += 1;
2193    }
2194    group[tail_start..].trim()
2195}
2196
2197fn class_names_in_selector(
2198    selector: &str,
2199    source: &str,
2200    full_header: &str,
2201) -> Vec<(String, ParserByteSpanV0)> {
2202    class_selector_names(selector)
2203        .into_iter()
2204        .map(|entry| {
2205            let name = entry.name.into_raw();
2206            (
2207                name.clone(),
2208                source_span_for_header_piece(source, full_header, &name),
2209            )
2210        })
2211        .collect()
2212}
2213
2214fn selector_names_for_offset(blocks: &[StyleBlock], offset: usize) -> Vec<String> {
2215    let Some(max_start) = blocks
2216        .iter()
2217        .filter(|block| block.start <= offset && offset < block.end && !block.names.is_empty())
2218        .map(|block| block.start)
2219        .max()
2220    else {
2221        return Vec::new();
2222    };
2223    blocks
2224        .iter()
2225        .filter(|block| block.start == max_start && block.start <= offset && offset < block.end)
2226        .flat_map(|block| {
2227            block
2228                .names
2229                .iter()
2230                .filter(|name| !name.starts_with("__selector_meta:"))
2231                .cloned()
2232        })
2233        .collect::<BTreeSet<_>>()
2234        .into_iter()
2235        .collect()
2236}
2237
2238fn selector_contexts_for_offset(blocks: &[StyleBlock], offset: usize) -> Vec<String> {
2239    let Some(max_start) = blocks
2240        .iter()
2241        .filter(|block| block.start <= offset && offset < block.end)
2242        .map(|block| block.start)
2243        .max()
2244    else {
2245        return Vec::new();
2246    };
2247    let mut contexts = BTreeSet::new();
2248    for block in blocks
2249        .iter()
2250        .filter(|block| block.start == max_start && block.start <= offset && offset < block.end)
2251    {
2252        if block.names.is_empty() {
2253            if let Some(context) = &block.context_text {
2254                contexts.insert(context.clone());
2255            }
2256        } else {
2257            for name in &block.names {
2258                if !name.starts_with("__selector_meta:") {
2259                    contexts.insert(format!(".{name}"));
2260                }
2261            }
2262        }
2263    }
2264    contexts.into_iter().collect()
2265}
2266
2267fn wrapper_for_offset(blocks: &[StyleBlock], offset: usize) -> WrapperContext {
2268    blocks
2269        .iter()
2270        .filter(|block| block.start <= offset && offset < block.end)
2271        .max_by_key(|block| block.start)
2272        .map(|block| WrapperContext {
2273            under_media: block.under_media,
2274            under_supports: block.under_supports,
2275            under_layer: block.under_layer,
2276            wrapper_at_rules: block.wrapper_at_rules.clone(),
2277        })
2278        .unwrap_or_default()
2279}
2280
2281fn insert_by_wrapper(
2282    media: &mut BTreeSet<String>,
2283    supports: &mut BTreeSet<String>,
2284    layer: &mut BTreeSet<String>,
2285    value: &str,
2286    wrapper: &WrapperContext,
2287) {
2288    if wrapper.under_media {
2289        media.insert(value.to_string());
2290    }
2291    if wrapper.under_supports {
2292        supports.insert(value.to_string());
2293    }
2294    if wrapper.under_layer {
2295        layer.insert(value.to_string());
2296    }
2297}
2298
2299fn insert_vec_by_wrapper(
2300    media: &mut Vec<String>,
2301    supports: &mut Vec<String>,
2302    layer: &mut Vec<String>,
2303    value: &str,
2304    wrapper: &WrapperContext,
2305) {
2306    if wrapper.under_media {
2307        media.push(value.to_string());
2308    }
2309    if wrapper.under_supports {
2310        supports.push(value.to_string());
2311    }
2312    if wrapper.under_layer {
2313        layer.push(value.to_string());
2314    }
2315}
2316
2317fn selector_names_from_contexts(contexts: &[String]) -> Vec<String> {
2318    contexts
2319        .iter()
2320        .filter_map(|context| context.strip_prefix('.').map(ToString::to_string))
2321        .collect()
2322}
2323
2324fn selector_names_for_variable_symbols(
2325    blocks: &[StyleBlock],
2326    facts: &ParsedStyleFacts,
2327    global_variable_decl_names: &BTreeSet<String>,
2328    variable_parameter_names: &BTreeSet<String>,
2329    variable_decl_scopes: &[SassVariableDeclScope],
2330    resolved_filter: Option<bool>,
2331) -> Vec<String> {
2332    facts
2333        .sass_symbols
2334        .iter()
2335        .filter(|symbol| symbol.kind == ParsedSassSymbolFactKind::VariableReference)
2336        .filter(|symbol| {
2337            let Some(expected_resolved) = resolved_filter else {
2338                return true;
2339            };
2340            if symbol.namespace.is_some() {
2341                return false;
2342            }
2343            is_sass_variable_reference_resolved(
2344                &symbol.name,
2345                range_start(symbol.range),
2346                blocks,
2347                global_variable_decl_names,
2348                variable_parameter_names,
2349                variable_decl_scopes,
2350            ) == expected_resolved
2351        })
2352        .flat_map(|symbol| selector_names_for_offset(blocks, range_start(symbol.range)))
2353        .collect::<BTreeSet<_>>()
2354        .into_iter()
2355        .collect()
2356}
2357
2358fn is_sass_variable_reference_resolved(
2359    name: &str,
2360    offset: usize,
2361    blocks: &[StyleBlock],
2362    global_variable_decl_names: &BTreeSet<String>,
2363    variable_parameter_names: &BTreeSet<String>,
2364    variable_decl_scopes: &[SassVariableDeclScope],
2365) -> bool {
2366    if global_variable_decl_names.contains(name) || variable_parameter_names.contains(name) {
2367        return true;
2368    }
2369    let reference_selectors = selector_names_for_offset(blocks, offset);
2370    !reference_selectors.is_empty()
2371        && variable_decl_scopes.iter().any(|scope| {
2372            scope.name == name
2373                && !scope.selector_names.is_empty()
2374                && scope
2375                    .selector_names
2376                    .iter()
2377                    .any(|selector| reference_selectors.contains(selector))
2378        })
2379}
2380
2381fn selector_names_for_symbols(
2382    blocks: &[StyleBlock],
2383    facts: &ParsedStyleFacts,
2384    kind: ParsedSassSymbolFactKind,
2385    names_filter: Option<&[String]>,
2386) -> Vec<String> {
2387    facts
2388        .sass_symbols
2389        .iter()
2390        .filter(|symbol| symbol.kind == kind)
2391        .filter(|symbol| {
2392            names_filter
2393                .map(|names| names.contains(&symbol.name))
2394                .unwrap_or(true)
2395        })
2396        .flat_map(|symbol| selector_names_for_offset(blocks, range_start(symbol.range)))
2397        .collect::<BTreeSet<_>>()
2398        .into_iter()
2399        .collect()
2400}
2401
2402fn sass_module_forward_prefix_from_statement(source: &str, span: ParserByteSpanV0) -> String {
2403    let Some(statement) = source.get(span.start..span.end) else {
2404        return String::new();
2405    };
2406    let Some(as_index) = css_keyword(statement).find(" as ") else {
2407        return String::new();
2408    };
2409    let after_as = &statement[as_index + 4..];
2410    let Some(star_index) = after_as.find('*') else {
2411        return String::new();
2412    };
2413    after_as[..star_index].trim().to_string()
2414}
2415
2416fn sass_module_forward_member_symbol_kind(
2417    source: &str,
2418    span: ParserByteSpanV0,
2419    name: &str,
2420) -> Option<&'static str> {
2421    let statement = source.get(span.start..span.end)?;
2422    statement
2423        .contains(&format!("${name}"))
2424        .then_some("variable")
2425}
2426
2427fn sort_all_composes(summary: &mut ParserIndexComposesFactsV0) {
2428    sort_unique(&mut summary.selectors_with_composes_names);
2429    sort_unique(&mut summary.selectors_with_composes_under_media_names);
2430    sort_unique(&mut summary.selectors_with_composes_under_supports_names);
2431    sort_unique(&mut summary.selectors_with_composes_under_layer_names);
2432    sort_unique(&mut summary.local_selector_names);
2433    sort_unique(&mut summary.imported_selector_names);
2434    sort_unique(&mut summary.global_selector_names);
2435    sort_unique(&mut summary.local_selector_names_under_media);
2436    sort_unique(&mut summary.local_selector_names_under_supports);
2437    sort_unique(&mut summary.local_selector_names_under_layer);
2438    sort_unique(&mut summary.imported_selector_names_under_media);
2439    sort_unique(&mut summary.imported_selector_names_under_supports);
2440    sort_unique(&mut summary.imported_selector_names_under_layer);
2441    sort_unique(&mut summary.global_selector_names_under_media);
2442    sort_unique(&mut summary.global_selector_names_under_supports);
2443    sort_unique(&mut summary.global_selector_names_under_layer);
2444    summary.import_sources.sort();
2445    summary.import_sources_under_media.sort();
2446    summary.import_sources_under_supports.sort();
2447    summary.import_sources_under_layer.sort();
2448}
2449
2450fn sort_unique(values: &mut Vec<String>) {
2451    values.sort();
2452    values.dedup();
2453}
2454
2455fn source_span_for_header_piece(source: &str, full_header: &str, piece: &str) -> ParserByteSpanV0 {
2456    if let Some(header_offset) = source.find(full_header)
2457        && let Some(piece_offset) = full_header.find(piece)
2458    {
2459        let start = header_offset + piece_offset;
2460        return ParserByteSpanV0 {
2461            start,
2462            end: start + piece.len(),
2463        };
2464    }
2465    ParserByteSpanV0 {
2466        start: 0,
2467        end: piece.len(),
2468    }
2469}
2470
2471fn byte_span_for_range(range: TextRange) -> ParserByteSpanV0 {
2472    ParserByteSpanV0 {
2473        start: range_start(range),
2474        end: u32::from(range.end()) as usize,
2475    }
2476}
2477
2478fn range_start(range: TextRange) -> usize {
2479    u32::from(range.start()) as usize
2480}
2481
2482struct SourceLineIndex {
2483    line_starts: Vec<usize>,
2484}
2485
2486impl SourceLineIndex {
2487    fn new(source: &str) -> Self {
2488        let mut line_starts = vec![0];
2489        for (index, byte) in source.as_bytes().iter().enumerate() {
2490            if *byte == b'\n' {
2491                line_starts.push(index + 1);
2492            }
2493        }
2494        Self { line_starts }
2495    }
2496
2497    fn position_for_byte_offset(&self, source: &str, byte_offset: usize) -> ParserPositionV0 {
2498        let offset = byte_offset.min(source.len());
2499        let line = self.line_starts.partition_point(|start| *start <= offset);
2500        let line_index = line.saturating_sub(1);
2501        let line_start = self.line_starts.get(line_index).copied().unwrap_or(0);
2502        ParserPositionV0 {
2503            line: line_index,
2504            character: source
2505                .get(line_start..offset)
2506                .map(|text| text.encode_utf16().count())
2507                .unwrap_or_else(|| offset.saturating_sub(line_start)),
2508        }
2509    }
2510}
2511
2512fn parser_range_for_byte_span(
2513    source: &str,
2514    line_index: &SourceLineIndex,
2515    span: ParserByteSpanV0,
2516) -> ParserRangeV0 {
2517    ParserRangeV0 {
2518        start: line_index.position_for_byte_offset(source, span.start),
2519        end: line_index.position_for_byte_offset(source, span.end),
2520    }
2521}
2522
2523fn bem_suffix_parent_name(name: &str) -> Option<String> {
2524    let marker = [name.rfind("__"), name.rfind("--")]
2525        .into_iter()
2526        .flatten()
2527        .max()?;
2528    (marker > 0).then(|| name[..marker].to_string())
2529}
2530
2531fn sorted(values: BTreeSet<String>) -> Vec<String> {
2532    values.into_iter().collect()
2533}
2534
2535fn sorted_authored_custom_properties(
2536    values: impl IntoIterator<Item = AuthoredPropertyTextV0>,
2537) -> Vec<AuthoredPropertyTextV0> {
2538    let mut by_key = BTreeMap::new();
2539    for value in values {
2540        by_key.entry(value.to_custom_key()).or_insert(value);
2541    }
2542    by_key.into_values().collect()
2543}
2544
2545fn authored_custom_property_sequences_same(
2546    left: &[AuthoredPropertyTextV0],
2547    right: &[AuthoredPropertyTextV0],
2548) -> bool {
2549    left.len() == right.len()
2550        && left
2551            .iter()
2552            .zip(right)
2553            .all(|(left, right)| left.to_custom_key() == right.to_custom_key())
2554}
2555
2556trait SortVec {
2557    fn tap_sort(self) -> Self;
2558    fn tap_sort_unique(self) -> Self;
2559}
2560
2561impl SortVec for Vec<String> {
2562    fn tap_sort(mut self) -> Self {
2563        self.sort();
2564        self
2565    }
2566
2567    fn tap_sort_unique(mut self) -> Self {
2568        self.sort();
2569        self.dedup();
2570        self
2571    }
2572}
2573
2574pub fn dialect_for_path(file_path: &str) -> StyleDialect {
2575    if file_path.ends_with(".sass") || file_path.ends_with(".module.sass") {
2576        StyleDialect::Sass
2577    } else if file_path.ends_with(".scss") || file_path.ends_with(".module.scss") {
2578        StyleDialect::Scss
2579    } else if file_path.ends_with(".less") || file_path.ends_with(".module.less") {
2580        StyleDialect::Less
2581    } else {
2582        StyleDialect::Css
2583    }
2584}
2585
2586fn dialect_label(dialect: StyleDialect) -> &'static str {
2587    match dialect {
2588        StyleDialect::Css => "css",
2589        StyleDialect::Scss => "scss",
2590        StyleDialect::Sass => "sass",
2591        StyleDialect::Less => "less",
2592    }
2593}