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