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