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