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