1use 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 } 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}