1use serde::Serialize;
2use std::collections::{BTreeMap, BTreeSet};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum StyleLanguage {
6 Css,
7 Scss,
8 Less,
9}
10
11impl StyleLanguage {
12 pub fn from_module_path(path: &str) -> Option<Self> {
13 if path.ends_with(".module.css") || path.ends_with(".css") {
14 Some(Self::Css)
15 } else if path.ends_with(".module.scss") || path.ends_with(".scss") {
16 Some(Self::Scss)
17 } else if path.ends_with(".module.less") || path.ends_with(".less") {
18 Some(Self::Less)
19 } else {
20 None
21 }
22 }
23
24 fn supports_line_comments(self) -> bool {
25 matches!(self, Self::Scss | Self::Less)
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct TextSpan {
31 pub start: usize,
32 pub end: usize,
33}
34
35impl TextSpan {
36 fn new(start: usize, end: usize) -> Self {
37 Self { start, end }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum TokenKind {
43 Whitespace,
44 Ident,
45 Number,
46 String,
47 LineComment,
48 BlockComment,
49 Dot,
50 Ampersand,
51 Hash,
52 Colon,
53 Semicolon,
54 Comma,
55 At,
56 OpenBrace,
57 CloseBrace,
58 OpenParen,
59 CloseParen,
60 OpenBracket,
61 CloseBracket,
62 InterpolationStart,
63 Other,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct Token {
68 pub kind: TokenKind,
69 pub span: TextSpan,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct ParseDiagnostic {
74 pub message: String,
75 pub span: TextSpan,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum SyntaxNodeKind {
80 Rule,
81 AtRule,
82 Declaration,
83 Comment,
84 Unknown,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct SyntaxNode {
89 pub kind: SyntaxNodeKind,
90 pub span: TextSpan,
91 pub header_span: Option<TextSpan>,
92 pub payload: Option<SyntaxNodePayload>,
93 pub children: Vec<SyntaxNode>,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub enum SyntaxNodePayload {
98 Rule(RulePayload),
99 AtRule(AtRulePayload),
100 Declaration(DeclarationPayload),
101 Comment(CommentPayload),
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct RulePayload {
106 pub prelude: String,
107 pub selector_groups: Vec<SelectorGroup>,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct SelectorGroup {
112 pub raw: String,
113 pub segments: Vec<SelectorSegment>,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub enum SelectorSegment {
118 ClassName(String),
119 Ampersand,
120 BemSuffix(String),
121 AmpersandSuffix(String),
122 Pseudo(String),
123 Combinator(String),
124 Other(String),
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct AtRulePayload {
129 pub kind: AtRuleKind,
130 pub name: String,
131 pub params: String,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub enum AtRuleKind {
136 Media,
137 Supports,
138 Layer,
139 Keyframes,
140 Value,
141 AtRoot,
142 Mixin,
143 Include,
144 Function,
145 Use,
146 Forward,
147 Import,
148 Generic,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct DeclarationPayload {
153 pub property: String,
154 pub value: String,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct CommentPayload {
159 pub text: String,
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct Stylesheet {
164 pub language: StyleLanguage,
165 pub source: String,
166 pub tokens: Vec<Token>,
167 pub nodes: Vec<SyntaxNode>,
168 pub diagnostics: Vec<ParseDiagnostic>,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
172#[serde(rename_all = "camelCase")]
173pub struct ParserParityLiteSummaryV0 {
174 pub schema_version: &'static str,
175 pub language: &'static str,
176 pub selector_names: Vec<String>,
177 pub keyframes_names: Vec<String>,
178 pub value_decl_names: Vec<String>,
179 pub diagnostic_count: usize,
180 pub rule_count: usize,
181 pub declaration_count: usize,
182 pub grouped_selector_count: usize,
183 pub max_nesting_depth: usize,
184 pub at_rule_kind_counts: AtRuleKindCountsV0,
185 pub declaration_kind_counts: DeclarationKindCountsV0,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
189#[serde(rename_all = "camelCase")]
190pub struct ParserIndexSummaryV0 {
191 pub schema_version: &'static str,
192 pub language: &'static str,
193 pub selectors: ParserIndexSelectorFactsV0,
194 pub values: ParserIndexValueFactsV0,
195 pub custom_properties: ParserIndexCustomPropertyFactsV0,
196 pub sass: ParserIndexSassFactsV0,
197 pub keyframes: ParserIndexKeyframesFactsV0,
198 pub composes: ParserIndexComposesFactsV0,
199 pub wrappers: ParserIndexWrapperFactsV0,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
203#[serde(rename_all = "camelCase")]
204pub struct ParserSemanticBoundarySummaryV0 {
205 pub schema_version: &'static str,
206 pub language: &'static str,
207 pub parser_facts: ParserBoundarySyntaxFactsV0,
208 pub semantic_facts: StyleSemanticFactsV0,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
212#[serde(rename_all = "camelCase")]
213pub struct ParserBoundarySyntaxFactsV0 {
214 pub lossless_cst: ParserLosslessCstFactsV0,
215 pub selectors: ParserIndexSelectorFactsV0,
216 pub values: ParserIndexValueFactsV0,
217 pub custom_properties: ParserIndexCustomPropertyFactsV0,
218 pub sass: ParserSassSyntaxFactsV0,
219 pub keyframes: ParserIndexKeyframesFactsV0,
220 pub composes: ParserIndexComposesFactsV0,
221 pub wrappers: ParserIndexWrapperFactsV0,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
225#[serde(rename_all = "camelCase")]
226pub struct ParserLosslessCstFactsV0 {
227 pub source_byte_len: usize,
228 pub token_count: usize,
229 pub root_node_count: usize,
230 pub diagnostic_count: usize,
231 pub all_token_spans_within_source: bool,
232 pub all_node_spans_within_source: bool,
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
236#[serde(rename_all = "camelCase")]
237pub struct ParserSassSyntaxFactsV0 {
238 pub variable_decl_names: Vec<String>,
239 pub variable_parameter_names: Vec<String>,
240 pub variable_ref_names: Vec<String>,
241 pub mixin_decl_names: Vec<String>,
242 pub mixin_include_names: Vec<String>,
243 pub function_decl_names: Vec<String>,
244 pub function_call_names: Vec<String>,
245 pub module_use_sources: Vec<String>,
246 pub module_use_edges: Vec<ParserIndexSassModuleUseFactV0>,
247 pub module_forward_sources: Vec<String>,
248 pub module_import_sources: Vec<String>,
249}
250
251#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
252#[serde(rename_all = "camelCase")]
253pub struct StyleSemanticFactsV0 {
254 pub selector_identity: StyleSelectorIdentityFactsV0,
255 pub custom_properties: StyleCustomPropertySemanticFactsV0,
256 pub sass: StyleSassSemanticFactsV0,
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
260#[serde(rename_all = "camelCase")]
261pub struct StyleSelectorIdentityFactsV0 {
262 pub canonical_names: Vec<String>,
263 pub bem_suffix_safe_names: Vec<String>,
264 pub bem_suffix_parent_names: Vec<String>,
265 pub nested_unsafe_names: Vec<String>,
266 pub nested_safety_counts: NestedSafetyCountsV0,
267}
268
269#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
270#[serde(rename_all = "camelCase")]
271pub struct StyleCustomPropertySemanticFactsV0 {
272 pub decl_names: Vec<String>,
273 pub ref_names: Vec<String>,
274 pub resolved_ref_names: Vec<String>,
275 pub unresolved_ref_names: Vec<String>,
276 pub selectors_with_refs_names: Vec<String>,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
280#[serde(rename_all = "camelCase")]
281pub struct StyleSassSemanticFactsV0 {
282 pub selector_symbol_facts: Vec<ParserIndexSassSelectorSymbolFactV0>,
283 pub selectors_with_resolved_variable_refs_names: Vec<String>,
284 pub selectors_with_unresolved_variable_refs_names: Vec<String>,
285 pub selectors_with_resolved_mixin_includes_names: Vec<String>,
286 pub selectors_with_unresolved_mixin_includes_names: Vec<String>,
287 pub selectors_with_function_calls_names: Vec<String>,
288 pub same_file_resolution: ParserIndexSassSameFileResolutionFactsV0,
289}
290
291#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
292#[serde(rename_all = "camelCase")]
293pub struct ParserCanonicalCandidateBundleV0 {
294 pub schema_version: &'static str,
295 pub language: &'static str,
296 pub parity_lite: ParserParityLiteSummaryV0,
297 pub css_modules_intermediate: ParserIndexSummaryV0,
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
301#[serde(rename_all = "camelCase")]
302pub struct ParserEvaluatorCandidateV0 {
303 pub kind: &'static str,
304 pub selector_name: String,
305 pub nested_safety_kind: &'static str,
306 #[serde(skip_serializing_if = "Option::is_none")]
307 pub bem_suffix_parent_name: Option<String>,
308 pub under_media: bool,
309 pub under_supports: bool,
310 pub under_layer: bool,
311 pub has_value_refs: bool,
312 pub has_local_value_refs: bool,
313 pub has_imported_value_refs: bool,
314 pub has_custom_property_refs: bool,
315 pub has_animation_ref: bool,
316 pub has_animation_name_ref: bool,
317 pub has_composes: bool,
318 pub has_local_composes: bool,
319 pub has_imported_composes: bool,
320 pub has_global_composes: bool,
321}
322
323#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
324#[serde(rename_all = "camelCase")]
325pub struct ParserEvaluatorCandidatesV0 {
326 pub schema_version: &'static str,
327 pub language: &'static str,
328 pub results: Vec<ParserEvaluatorCandidateV0>,
329}
330
331#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
332#[serde(rename_all = "camelCase")]
333pub struct ParserCanonicalProducerSignalV0 {
334 pub schema_version: &'static str,
335 pub language: &'static str,
336 pub canonical_candidate: ParserCanonicalCandidateBundleV0,
337 pub evaluator_candidates: ParserEvaluatorCandidatesV0,
338 pub public_product_gate: ParserPublicProductGateSignalV0,
339}
340
341#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
342#[serde(rename_all = "camelCase")]
343pub struct ParserPublicProductGateSignalV0 {
344 pub canonical_candidate_command: &'static str,
345 pub consumer_boundary_command: &'static str,
346 pub public_product_gate_command: &'static str,
347 pub included_in_parser_lane: bool,
348 pub included_in_rust_lane_bundle: bool,
349 pub included_in_rust_release_bundle: bool,
350}
351
352#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
353#[serde(rename_all = "camelCase")]
354pub struct ParserIndexSelectorFactsV0 {
355 pub names: Vec<String>,
356 pub definition_facts: Vec<ParserIndexSelectorDefinitionFactV0>,
357 pub bem_suffix_parent_names: Vec<String>,
358 pub bem_suffix_safe_names: Vec<String>,
359 pub nested_unsafe_names: Vec<String>,
360 pub selectors_with_value_refs_names: Vec<String>,
361 pub selectors_with_animation_ref_names: Vec<String>,
362 pub selectors_with_animation_name_ref_names: Vec<String>,
363 pub bem_suffix_count: usize,
364 pub nested_safety_counts: NestedSafetyCountsV0,
365}
366
367#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
368#[serde(rename_all = "camelCase")]
369pub struct ParserIndexSelectorDefinitionFactV0 {
370 pub name: String,
371 pub source_order: usize,
372 pub byte_span: ParserByteSpanV0,
373 pub range: ParserRangeV0,
374 pub nested_safety_kind: &'static str,
375 #[serde(skip_serializing_if = "Option::is_none")]
376 pub bem_suffix_parent_name: Option<String>,
377 pub under_media: bool,
378 pub under_supports: bool,
379 pub under_layer: bool,
380}
381
382#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
383#[serde(rename_all = "camelCase")]
384pub struct ParserIndexValueFactsV0 {
385 pub decl_names: Vec<String>,
386 pub decl_names_with_local_refs: Vec<String>,
387 pub decl_names_with_imported_refs: Vec<String>,
388 pub import_names: Vec<String>,
389 pub import_sources: Vec<String>,
390 pub import_alias_count: usize,
391 pub ref_names: Vec<String>,
392 pub local_ref_names: Vec<String>,
393 pub imported_ref_names: Vec<String>,
394 pub imported_ref_sources: Vec<String>,
395 pub declaration_ref_names: Vec<String>,
396 pub declaration_imported_ref_sources: Vec<String>,
397 pub value_decl_ref_names: Vec<String>,
398 pub value_decl_imported_ref_sources: Vec<String>,
399 pub selectors_with_refs_names: Vec<String>,
400 pub selectors_with_local_refs_names: Vec<String>,
401 pub selectors_with_imported_refs_names: Vec<String>,
402 pub selectors_with_refs_under_media_names: Vec<String>,
403 pub selectors_with_refs_under_supports_names: Vec<String>,
404 pub selectors_with_refs_under_layer_names: Vec<String>,
405 pub selectors_with_local_refs_under_media_names: Vec<String>,
406 pub selectors_with_local_refs_under_supports_names: Vec<String>,
407 pub selectors_with_local_refs_under_layer_names: Vec<String>,
408 pub selectors_with_imported_refs_under_media_names: Vec<String>,
409 pub selectors_with_imported_refs_under_supports_names: Vec<String>,
410 pub selectors_with_imported_refs_under_layer_names: Vec<String>,
411}
412
413#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
414#[serde(rename_all = "camelCase")]
415pub struct ParserIndexCustomPropertyFactsV0 {
416 pub decl_names: Vec<String>,
417 pub decl_facts: Vec<ParserIndexCustomPropertyDeclFactV0>,
418 pub decl_context_selectors: Vec<String>,
419 pub decl_names_under_media: Vec<String>,
420 pub decl_names_under_supports: Vec<String>,
421 pub decl_names_under_layer: Vec<String>,
422 pub ref_names: Vec<String>,
423 pub ref_facts: Vec<ParserIndexCustomPropertyRefFactV0>,
424 pub selectors_with_refs_names: Vec<String>,
425 pub selectors_with_refs_under_media_names: Vec<String>,
426 pub selectors_with_refs_under_supports_names: Vec<String>,
427 pub selectors_with_refs_under_layer_names: Vec<String>,
428}
429
430#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
431#[serde(rename_all = "camelCase")]
432pub struct ParserIndexCustomPropertyDeclFactV0 {
433 pub name: String,
434 pub source_order: usize,
435 pub byte_span: ParserByteSpanV0,
436 pub range: ParserRangeV0,
437 pub selector_contexts: Vec<String>,
438 pub under_media: bool,
439 pub under_supports: bool,
440 pub under_layer: bool,
441}
442
443#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
444#[serde(rename_all = "camelCase")]
445pub struct ParserIndexCustomPropertyRefFactV0 {
446 pub name: String,
447 pub source_order: usize,
448 pub selector_contexts: Vec<String>,
449 pub under_media: bool,
450 pub under_supports: bool,
451 pub under_layer: bool,
452}
453
454#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
455#[serde(rename_all = "camelCase")]
456pub struct ParserIndexSassFactsV0 {
457 pub variable_decl_names: Vec<String>,
458 pub symbol_decl_facts: Vec<ParserIndexSassSymbolDeclFactV0>,
459 pub variable_parameter_names: Vec<String>,
460 pub variable_ref_names: Vec<String>,
461 pub selectors_with_variable_refs_names: Vec<String>,
462 pub selectors_with_resolved_variable_refs_names: Vec<String>,
463 pub selectors_with_unresolved_variable_refs_names: Vec<String>,
464 pub mixin_decl_names: Vec<String>,
465 pub mixin_include_names: Vec<String>,
466 pub selectors_with_mixin_includes_names: Vec<String>,
467 pub selectors_with_resolved_mixin_includes_names: Vec<String>,
468 pub selectors_with_unresolved_mixin_includes_names: Vec<String>,
469 pub function_decl_names: Vec<String>,
470 pub function_call_names: Vec<String>,
471 pub selectors_with_function_calls_names: Vec<String>,
472 pub selector_symbol_facts: Vec<ParserIndexSassSelectorSymbolFactV0>,
473 pub module_use_sources: Vec<String>,
474 pub module_use_edges: Vec<ParserIndexSassModuleUseFactV0>,
475 pub module_forward_sources: Vec<String>,
476 pub module_import_sources: Vec<String>,
477 pub same_file_resolution: ParserIndexSassSameFileResolutionFactsV0,
478}
479
480#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
481#[serde(rename_all = "camelCase")]
482pub struct ParserIndexSassModuleUseFactV0 {
483 pub source: String,
484 pub namespace_kind: &'static str,
485 pub namespace: Option<String>,
486}
487
488#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
489#[serde(rename_all = "camelCase")]
490pub struct ParserIndexSassSameFileResolutionFactsV0 {
491 pub resolved_variable_ref_names: Vec<String>,
492 pub unresolved_variable_ref_names: Vec<String>,
493 pub resolved_mixin_include_names: Vec<String>,
494 pub unresolved_mixin_include_names: Vec<String>,
495 pub resolved_function_call_names: Vec<String>,
496}
497
498#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
499#[serde(rename_all = "camelCase")]
500pub struct ParserByteSpanV0 {
501 pub start: usize,
502 pub end: usize,
503}
504
505impl From<TextSpan> for ParserByteSpanV0 {
506 fn from(span: TextSpan) -> Self {
507 Self {
508 start: span.start,
509 end: span.end,
510 }
511 }
512}
513
514#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
515#[serde(rename_all = "camelCase")]
516pub struct ParserPositionV0 {
517 pub line: usize,
518 pub character: usize,
519}
520
521#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
522#[serde(rename_all = "camelCase")]
523pub struct ParserRangeV0 {
524 pub start: ParserPositionV0,
525 pub end: ParserPositionV0,
526}
527
528#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
529#[serde(rename_all = "camelCase")]
530pub struct ParserIndexSassSelectorSymbolFactV0 {
531 pub selector_name: String,
532 pub symbol_kind: &'static str,
533 pub name: String,
534 pub namespace: Option<String>,
535 pub role: &'static str,
536 pub resolution: &'static str,
537 pub byte_span: ParserByteSpanV0,
538 pub range: ParserRangeV0,
539}
540
541#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
542#[serde(rename_all = "camelCase")]
543pub struct ParserIndexSassSymbolDeclFactV0 {
544 pub symbol_kind: &'static str,
545 pub name: String,
546 pub role: &'static str,
547 pub byte_span: ParserByteSpanV0,
548 pub range: ParserRangeV0,
549}
550
551#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
552#[serde(rename_all = "camelCase")]
553pub struct ParserIndexKeyframesFactsV0 {
554 pub names: Vec<String>,
555 pub names_under_media: Vec<String>,
556 pub names_under_supports: Vec<String>,
557 pub names_under_layer: Vec<String>,
558 pub animation_ref_names: Vec<String>,
559 pub animation_name_ref_names: Vec<String>,
560 pub selectors_with_animation_ref_names: Vec<String>,
561 pub selectors_with_animation_name_ref_names: Vec<String>,
562 pub selectors_with_animation_refs_under_media_names: Vec<String>,
563 pub selectors_with_animation_refs_under_supports_names: Vec<String>,
564 pub selectors_with_animation_refs_under_layer_names: Vec<String>,
565 pub selectors_with_animation_name_refs_under_media_names: Vec<String>,
566 pub selectors_with_animation_name_refs_under_supports_names: Vec<String>,
567 pub selectors_with_animation_name_refs_under_layer_names: Vec<String>,
568}
569
570#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
571#[serde(rename_all = "camelCase")]
572pub struct ParserIndexComposesFactsV0 {
573 pub selectors_with_composes_names: Vec<String>,
574 pub selectors_with_composes_under_media_names: Vec<String>,
575 pub selectors_with_composes_under_supports_names: Vec<String>,
576 pub selectors_with_composes_under_layer_names: Vec<String>,
577 pub local_selector_names: Vec<String>,
578 pub imported_selector_names: Vec<String>,
579 pub global_selector_names: Vec<String>,
580 pub local_selector_names_under_media: Vec<String>,
581 pub local_selector_names_under_supports: Vec<String>,
582 pub local_selector_names_under_layer: Vec<String>,
583 pub imported_selector_names_under_media: Vec<String>,
584 pub imported_selector_names_under_supports: Vec<String>,
585 pub imported_selector_names_under_layer: Vec<String>,
586 pub global_selector_names_under_media: Vec<String>,
587 pub global_selector_names_under_supports: Vec<String>,
588 pub global_selector_names_under_layer: Vec<String>,
589 pub import_sources: Vec<String>,
590 pub import_sources_under_media: Vec<String>,
591 pub import_sources_under_supports: Vec<String>,
592 pub import_sources_under_layer: Vec<String>,
593 pub class_name_count: usize,
594 pub local_class_name_count: usize,
595 pub imported_class_name_count: usize,
596 pub global_class_name_count: usize,
597}
598
599#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
600#[serde(rename_all = "camelCase")]
601pub struct ParserIndexWrapperFactsV0 {
602 pub selectors_under_media_names: Vec<String>,
603 pub selectors_under_supports_names: Vec<String>,
604 pub selectors_under_layer_names: Vec<String>,
605}
606
607#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
608#[serde(rename_all = "camelCase")]
609pub struct AtRuleKindCountsV0 {
610 pub media: usize,
611 pub supports: usize,
612 pub layer: usize,
613 pub keyframes: usize,
614 pub value: usize,
615 pub at_root: usize,
616 pub generic: usize,
617}
618
619#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
620#[serde(rename_all = "camelCase")]
621pub struct DeclarationKindCountsV0 {
622 pub composes: usize,
623 pub animation: usize,
624 pub animation_name: usize,
625 pub generic: usize,
626}
627
628#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
629#[serde(rename_all = "camelCase")]
630pub struct NestedSafetyCountsV0 {
631 pub flat: usize,
632 pub bem_suffix_safe: usize,
633 pub nested_unsafe: usize,
634}
635
636#[derive(Debug, Default)]
637struct ParityLiteAcc {
638 selector_names: Vec<String>,
639 keyframes_names: Vec<String>,
640 value_decl_names: Vec<String>,
641 rule_count: usize,
642 declaration_count: usize,
643 grouped_selector_count: usize,
644 max_nesting_depth: usize,
645 at_rule_kind_counts: AtRuleKindCountsV0,
646 declaration_kind_counts: DeclarationKindCountsV0,
647}
648
649#[derive(Debug, Default)]
650struct IndexSummaryAcc {
651 selector_names: Vec<String>,
652 selector_definition_facts: Vec<ParserIndexSelectorDefinitionFactV0>,
653 bem_suffix_parent_names: Vec<String>,
654 bem_suffix_safe_selector_names: Vec<String>,
655 selectors_with_composes_names: Vec<String>,
656 selectors_with_composes_under_media_names: Vec<String>,
657 selectors_with_composes_under_supports_names: Vec<String>,
658 selectors_with_composes_under_layer_names: Vec<String>,
659 local_composes_selector_names: Vec<String>,
660 imported_composes_selector_names: Vec<String>,
661 global_composes_selector_names: Vec<String>,
662 local_composes_selector_names_under_media: Vec<String>,
663 local_composes_selector_names_under_supports: Vec<String>,
664 local_composes_selector_names_under_layer: Vec<String>,
665 imported_composes_selector_names_under_media: Vec<String>,
666 imported_composes_selector_names_under_supports: Vec<String>,
667 imported_composes_selector_names_under_layer: Vec<String>,
668 global_composes_selector_names_under_media: Vec<String>,
669 global_composes_selector_names_under_supports: Vec<String>,
670 global_composes_selector_names_under_layer: Vec<String>,
671 composes_import_sources: Vec<String>,
672 composes_import_sources_under_media: Vec<String>,
673 composes_import_sources_under_supports: Vec<String>,
674 composes_import_sources_under_layer: Vec<String>,
675 keyframes_names: Vec<String>,
676 nested_unsafe_selector_names: Vec<String>,
677 value_decl_names: Vec<String>,
678 value_decl_names_with_local_refs: Vec<String>,
679 value_decl_names_with_imported_refs: Vec<String>,
680 value_import_names: Vec<String>,
681 value_import_sources: Vec<String>,
682 value_import_source_by_name: BTreeMap<String, String>,
683 value_ref_names: Vec<String>,
684 local_value_ref_names: Vec<String>,
685 imported_value_ref_names: Vec<String>,
686 imported_value_ref_sources: Vec<String>,
687 declaration_value_ref_names: Vec<String>,
688 declaration_imported_value_ref_sources: Vec<String>,
689 value_decl_ref_names: Vec<String>,
690 value_decl_imported_value_ref_sources: Vec<String>,
691 custom_property_decl_names: Vec<String>,
692 custom_property_decl_facts: Vec<ParserIndexCustomPropertyDeclFactV0>,
693 custom_property_decl_context_selectors: Vec<String>,
694 custom_property_decl_names_under_media: Vec<String>,
695 custom_property_decl_names_under_supports: Vec<String>,
696 custom_property_decl_names_under_layer: Vec<String>,
697 custom_property_ref_names: Vec<String>,
698 custom_property_ref_facts: Vec<ParserIndexCustomPropertyRefFactV0>,
699 selectors_with_custom_property_refs_names: Vec<String>,
700 selectors_with_custom_property_refs_under_media_names: Vec<String>,
701 selectors_with_custom_property_refs_under_supports_names: Vec<String>,
702 selectors_with_custom_property_refs_under_layer_names: Vec<String>,
703 sass_variable_decl_names: Vec<String>,
704 sass_variable_decl_facts: Vec<SassVariableDeclFact>,
705 sass_symbol_decl_facts: Vec<ParserIndexSassSymbolDeclFactV0>,
706 sass_variable_parameter_names: Vec<String>,
707 sass_variable_ref_names: Vec<String>,
708 sass_variable_ref_facts: Vec<SassVariableRefFact>,
709 sass_selectors_with_variable_refs_names: Vec<String>,
710 sass_selectors_with_resolved_variable_refs_names: Vec<String>,
711 sass_selectors_with_unresolved_variable_refs_names: Vec<String>,
712 sass_mixin_decl_names: Vec<String>,
713 sass_mixin_include_names: Vec<String>,
714 sass_selectors_with_mixin_includes_names: Vec<String>,
715 sass_selectors_with_resolved_mixin_includes_names: Vec<String>,
716 sass_selectors_with_unresolved_mixin_includes_names: Vec<String>,
717 sass_function_decl_names: Vec<String>,
718 sass_function_call_names: Vec<String>,
719 sass_selectors_with_function_calls_names: Vec<String>,
720 sass_selector_symbol_facts: Vec<ParserIndexSassSelectorSymbolFactV0>,
721 sass_module_use_sources: Vec<String>,
722 sass_module_use_edges: Vec<ParserIndexSassModuleUseFactV0>,
723 sass_module_forward_sources: Vec<String>,
724 sass_module_import_sources: Vec<String>,
725 selectors_with_value_refs_names: Vec<String>,
726 selectors_with_local_value_refs_names: Vec<String>,
727 selectors_with_imported_value_refs_names: Vec<String>,
728 selectors_with_value_refs_under_media_names: Vec<String>,
729 selectors_with_value_refs_under_supports_names: Vec<String>,
730 selectors_with_value_refs_under_layer_names: Vec<String>,
731 selectors_with_local_value_refs_under_media_names: Vec<String>,
732 selectors_with_local_value_refs_under_supports_names: Vec<String>,
733 selectors_with_local_value_refs_under_layer_names: Vec<String>,
734 selectors_with_imported_value_refs_under_media_names: Vec<String>,
735 selectors_with_imported_value_refs_under_supports_names: Vec<String>,
736 selectors_with_imported_value_refs_under_layer_names: Vec<String>,
737 selectors_with_animation_ref_names: Vec<String>,
738 selectors_with_animation_refs_under_media_names: Vec<String>,
739 selectors_with_animation_refs_under_supports_names: Vec<String>,
740 selectors_with_animation_refs_under_layer_names: Vec<String>,
741 selectors_with_animation_name_ref_names: Vec<String>,
742 selectors_with_animation_name_refs_under_media_names: Vec<String>,
743 selectors_with_animation_name_refs_under_supports_names: Vec<String>,
744 selectors_with_animation_name_refs_under_layer_names: Vec<String>,
745 selectors_under_media_names: Vec<String>,
746 selectors_under_supports_names: Vec<String>,
747 selectors_under_layer_names: Vec<String>,
748 animation_ref_names: Vec<String>,
749 animation_name_ref_names: Vec<String>,
750 keyframes_names_under_media: Vec<String>,
751 keyframes_names_under_supports: Vec<String>,
752 keyframes_names_under_layer: Vec<String>,
753 value_import_alias_count: usize,
754 composes_class_name_count: usize,
755 local_composes_class_name_count: usize,
756 imported_composes_class_name_count: usize,
757 global_composes_class_name_count: usize,
758 bem_suffix_count: usize,
759 nested_safety_counts: NestedSafetyCountsV0,
760}
761
762#[derive(Debug, Clone, PartialEq, Eq)]
763struct ResolvedSelectorBranch {
764 name: String,
765 bare_suffix_base: bool,
766}
767
768pub fn parse_style_module(path: &str, source: &str) -> Option<Stylesheet> {
769 let language = StyleLanguage::from_module_path(path)?;
770 Some(parse_stylesheet(language, source))
771}
772
773pub fn parse_stylesheet(language: StyleLanguage, source: &str) -> Stylesheet {
774 let (tokens, mut diagnostics) = tokenize(language, source);
775 let mut parser = Parser::new(source, &tokens, &mut diagnostics);
776 let nodes = parser.parse_root();
777 Stylesheet {
778 language,
779 source: source.to_string(),
780 tokens,
781 nodes,
782 diagnostics,
783 }
784}
785
786pub fn summarize_parity_lite(sheet: &Stylesheet) -> ParserParityLiteSummaryV0 {
787 let mut acc = ParityLiteAcc::default();
788 collect_parity_names(&sheet.nodes, &mut acc);
789 acc.selector_names.sort();
790 acc.keyframes_names.sort();
791 acc.keyframes_names.dedup();
792 acc.value_decl_names.sort();
793 acc.value_decl_names.dedup();
794
795 ParserParityLiteSummaryV0 {
796 schema_version: "0",
797 language: match sheet.language {
798 StyleLanguage::Css => "css",
799 StyleLanguage::Scss => "scss",
800 StyleLanguage::Less => "less",
801 },
802 selector_names: acc.selector_names,
803 keyframes_names: acc.keyframes_names,
804 value_decl_names: acc.value_decl_names,
805 diagnostic_count: sheet.diagnostics.len(),
806 rule_count: acc.rule_count,
807 declaration_count: acc.declaration_count,
808 grouped_selector_count: acc.grouped_selector_count,
809 max_nesting_depth: acc.max_nesting_depth,
810 at_rule_kind_counts: acc.at_rule_kind_counts,
811 declaration_kind_counts: acc.declaration_kind_counts,
812 }
813}
814
815pub fn summarize_css_modules_intermediate(sheet: &Stylesheet) -> ParserIndexSummaryV0 {
816 let mut acc = IndexSummaryAcc::default();
817 collect_index_names(
818 &sheet.source,
819 &sheet.nodes,
820 &mut acc,
821 &[],
822 false,
823 None,
824 WrapperContext::default(),
825 );
826 let local_value_names: BTreeSet<String> = acc.value_decl_names.iter().cloned().collect();
827 let imported_value_names: BTreeSet<String> = acc.value_import_names.iter().cloned().collect();
828 let known_value_names: BTreeSet<String> = acc
829 .value_decl_names
830 .iter()
831 .chain(acc.value_import_names.iter())
832 .cloned()
833 .collect();
834 let value_ref_ctx = ValueRefContext {
835 known: &known_value_names,
836 local: &local_value_names,
837 imported: &imported_value_names,
838 };
839 let known_keyframe_names: BTreeSet<String> = acc.keyframes_names.iter().cloned().collect();
840 collect_index_refs_and_counts(&sheet.nodes, value_ref_ctx, &known_keyframe_names, &mut acc);
841 let known_sass_function_names: BTreeSet<String> =
842 acc.sass_function_decl_names.iter().cloned().collect();
843 collect_sass_ref_facts(
844 &sheet.nodes,
845 &sheet.source,
846 &known_sass_function_names,
847 &mut acc,
848 );
849 let sass_variable_decl_facts = acc.sass_variable_decl_facts.clone();
850 let sass_mixin_targets: BTreeSet<String> = acc.sass_mixin_decl_names.iter().cloned().collect();
851 let sass_ref_ctx = SassRefContext {
852 variable_decls: &sass_variable_decl_facts,
853 mixin_targets: &sass_mixin_targets,
854 function_targets: &known_sass_function_names,
855 };
856 let selector_attachment_ctx = SelectorAttachmentContext {
857 source: &sheet.source,
858 value_ref_ctx,
859 known_keyframe_names: &known_keyframe_names,
860 sass_ref_ctx,
861 };
862 collect_index_selector_attachment_facts(&sheet.nodes, selector_attachment_ctx, &mut acc, &[]);
863
864 acc.selector_names.sort();
865 acc.selector_definition_facts.sort();
866 acc.selector_definition_facts.dedup();
867 acc.bem_suffix_parent_names.sort();
868 acc.bem_suffix_safe_selector_names.sort();
869 acc.selectors_with_composes_names.sort();
870 acc.selectors_with_composes_under_media_names.sort();
871 acc.selectors_with_composes_under_supports_names.sort();
872 acc.selectors_with_composes_under_layer_names.sort();
873 acc.local_composes_selector_names.sort();
874 acc.imported_composes_selector_names.sort();
875 acc.global_composes_selector_names.sort();
876 acc.local_composes_selector_names_under_media.sort();
877 acc.local_composes_selector_names_under_supports.sort();
878 acc.local_composes_selector_names_under_layer.sort();
879 acc.imported_composes_selector_names_under_media.sort();
880 acc.imported_composes_selector_names_under_supports.sort();
881 acc.imported_composes_selector_names_under_layer.sort();
882 acc.global_composes_selector_names_under_media.sort();
883 acc.global_composes_selector_names_under_supports.sort();
884 acc.global_composes_selector_names_under_layer.sort();
885 acc.composes_import_sources.sort();
886 acc.composes_import_sources_under_media.sort();
887 acc.composes_import_sources_under_supports.sort();
888 acc.composes_import_sources_under_layer.sort();
889 acc.keyframes_names.sort();
890 acc.keyframes_names.dedup();
891 acc.nested_unsafe_selector_names.sort();
892 acc.value_decl_names.sort();
893 acc.value_decl_names.dedup();
894 acc.value_decl_names_with_local_refs.sort();
895 acc.value_decl_names_with_local_refs.dedup();
896 acc.value_decl_names_with_imported_refs.sort();
897 acc.value_decl_names_with_imported_refs.dedup();
898 acc.value_import_names.sort();
899 acc.value_import_names.dedup();
900 acc.value_import_sources.sort();
901 acc.value_ref_names.sort();
902 acc.value_ref_names.dedup();
903 acc.local_value_ref_names.sort();
904 acc.local_value_ref_names.dedup();
905 acc.imported_value_ref_names.sort();
906 acc.imported_value_ref_names.dedup();
907 acc.imported_value_ref_sources.sort();
908 acc.declaration_value_ref_names.sort();
909 acc.declaration_value_ref_names.dedup();
910 acc.declaration_imported_value_ref_sources.sort();
911 acc.value_decl_ref_names.sort();
912 acc.value_decl_ref_names.dedup();
913 acc.value_decl_imported_value_ref_sources.sort();
914 acc.custom_property_decl_names.sort();
915 acc.custom_property_decl_names.dedup();
916 acc.custom_property_decl_facts.sort();
917 acc.custom_property_decl_facts.dedup();
918 acc.custom_property_decl_context_selectors.sort();
919 acc.custom_property_decl_context_selectors.dedup();
920 acc.custom_property_decl_names_under_media.sort();
921 acc.custom_property_decl_names_under_media.dedup();
922 acc.custom_property_decl_names_under_supports.sort();
923 acc.custom_property_decl_names_under_supports.dedup();
924 acc.custom_property_decl_names_under_layer.sort();
925 acc.custom_property_decl_names_under_layer.dedup();
926 acc.custom_property_ref_names.sort();
927 acc.custom_property_ref_names.dedup();
928 acc.custom_property_ref_facts.sort();
929 acc.custom_property_ref_facts.dedup();
930 acc.selectors_with_custom_property_refs_names.sort();
931 acc.selectors_with_custom_property_refs_names.dedup();
932 acc.selectors_with_custom_property_refs_under_media_names
933 .sort();
934 acc.selectors_with_custom_property_refs_under_media_names
935 .dedup();
936 acc.selectors_with_custom_property_refs_under_supports_names
937 .sort();
938 acc.selectors_with_custom_property_refs_under_supports_names
939 .dedup();
940 acc.selectors_with_custom_property_refs_under_layer_names
941 .sort();
942 acc.selectors_with_custom_property_refs_under_layer_names
943 .dedup();
944 acc.sass_variable_decl_names.sort();
945 acc.sass_variable_decl_names.dedup();
946 acc.sass_symbol_decl_facts.sort();
947 acc.sass_symbol_decl_facts.dedup();
948 acc.sass_variable_parameter_names.sort();
949 acc.sass_variable_parameter_names.dedup();
950 acc.sass_variable_ref_names.sort();
951 acc.sass_variable_ref_names.dedup();
952 acc.sass_selectors_with_variable_refs_names.sort();
953 acc.sass_selectors_with_variable_refs_names.dedup();
954 acc.sass_selectors_with_resolved_variable_refs_names.sort();
955 acc.sass_selectors_with_resolved_variable_refs_names.dedup();
956 acc.sass_selectors_with_unresolved_variable_refs_names
957 .sort();
958 acc.sass_selectors_with_unresolved_variable_refs_names
959 .dedup();
960 acc.sass_mixin_decl_names.sort();
961 acc.sass_mixin_decl_names.dedup();
962 acc.sass_mixin_include_names.sort();
963 acc.sass_mixin_include_names.dedup();
964 acc.sass_selectors_with_mixin_includes_names.sort();
965 acc.sass_selectors_with_mixin_includes_names.dedup();
966 acc.sass_selectors_with_resolved_mixin_includes_names.sort();
967 acc.sass_selectors_with_resolved_mixin_includes_names
968 .dedup();
969 acc.sass_selectors_with_unresolved_mixin_includes_names
970 .sort();
971 acc.sass_selectors_with_unresolved_mixin_includes_names
972 .dedup();
973 acc.sass_function_decl_names.sort();
974 acc.sass_function_decl_names.dedup();
975 acc.sass_function_call_names.sort();
976 acc.sass_function_call_names.dedup();
977 acc.sass_selectors_with_function_calls_names.sort();
978 acc.sass_selectors_with_function_calls_names.dedup();
979 acc.sass_selector_symbol_facts.sort();
980 acc.sass_selector_symbol_facts.dedup();
981 acc.sass_module_use_sources.sort();
982 acc.sass_module_use_sources.dedup();
983 acc.sass_module_use_edges.sort();
984 acc.sass_module_use_edges.dedup();
985 acc.sass_module_forward_sources.sort();
986 acc.sass_module_forward_sources.dedup();
987 acc.sass_module_import_sources.sort();
988 acc.sass_module_import_sources.dedup();
989 acc.selectors_with_value_refs_names.sort();
990 acc.selectors_with_local_value_refs_names.sort();
991 acc.selectors_with_imported_value_refs_names.sort();
992 acc.selectors_with_value_refs_under_media_names.sort();
993 acc.selectors_with_value_refs_under_supports_names.sort();
994 acc.selectors_with_value_refs_under_layer_names.sort();
995 acc.selectors_with_local_value_refs_under_media_names.sort();
996 acc.selectors_with_local_value_refs_under_supports_names
997 .sort();
998 acc.selectors_with_local_value_refs_under_layer_names.sort();
999 acc.selectors_with_imported_value_refs_under_media_names
1000 .sort();
1001 acc.selectors_with_imported_value_refs_under_supports_names
1002 .sort();
1003 acc.selectors_with_imported_value_refs_under_layer_names
1004 .sort();
1005 acc.selectors_with_animation_ref_names.sort();
1006 acc.selectors_with_animation_refs_under_media_names.sort();
1007 acc.selectors_with_animation_refs_under_supports_names
1008 .sort();
1009 acc.selectors_with_animation_refs_under_layer_names.sort();
1010 acc.selectors_with_animation_name_ref_names.sort();
1011 acc.selectors_with_animation_name_refs_under_media_names
1012 .sort();
1013 acc.selectors_with_animation_name_refs_under_supports_names
1014 .sort();
1015 acc.selectors_with_animation_name_refs_under_layer_names
1016 .sort();
1017 acc.selectors_under_media_names.sort();
1018 acc.selectors_under_supports_names.sort();
1019 acc.selectors_under_layer_names.sort();
1020 acc.animation_ref_names.sort();
1021 acc.animation_ref_names.dedup();
1022 acc.animation_name_ref_names.sort();
1023 acc.animation_name_ref_names.dedup();
1024 acc.keyframes_names_under_media.sort();
1025 acc.keyframes_names_under_media.dedup();
1026 acc.keyframes_names_under_supports.sort();
1027 acc.keyframes_names_under_supports.dedup();
1028 acc.keyframes_names_under_layer.sort();
1029 acc.keyframes_names_under_layer.dedup();
1030 let selectors_with_value_refs_names = acc.selectors_with_value_refs_names.clone();
1031 let selectors_with_animation_ref_names = acc.selectors_with_animation_ref_names.clone();
1032 let selectors_with_animation_name_ref_names =
1033 acc.selectors_with_animation_name_ref_names.clone();
1034 let sass_same_file_resolution = summarize_sass_same_file_resolution(&acc);
1035
1036 ParserIndexSummaryV0 {
1037 schema_version: "0",
1038 language: match sheet.language {
1039 StyleLanguage::Css => "css",
1040 StyleLanguage::Scss => "scss",
1041 StyleLanguage::Less => "less",
1042 },
1043 selectors: ParserIndexSelectorFactsV0 {
1044 names: acc.selector_names,
1045 definition_facts: acc.selector_definition_facts,
1046 bem_suffix_parent_names: acc.bem_suffix_parent_names,
1047 bem_suffix_safe_names: acc.bem_suffix_safe_selector_names,
1048 nested_unsafe_names: acc.nested_unsafe_selector_names,
1049 selectors_with_value_refs_names,
1050 selectors_with_animation_ref_names,
1051 selectors_with_animation_name_ref_names,
1052 bem_suffix_count: acc.bem_suffix_count,
1053 nested_safety_counts: acc.nested_safety_counts,
1054 },
1055 values: ParserIndexValueFactsV0 {
1056 decl_names: acc.value_decl_names,
1057 decl_names_with_local_refs: acc.value_decl_names_with_local_refs,
1058 decl_names_with_imported_refs: acc.value_decl_names_with_imported_refs,
1059 import_names: acc.value_import_names,
1060 import_sources: acc.value_import_sources,
1061 import_alias_count: acc.value_import_alias_count,
1062 ref_names: acc.value_ref_names,
1063 local_ref_names: acc.local_value_ref_names,
1064 imported_ref_names: acc.imported_value_ref_names,
1065 imported_ref_sources: acc.imported_value_ref_sources,
1066 declaration_ref_names: acc.declaration_value_ref_names,
1067 declaration_imported_ref_sources: acc.declaration_imported_value_ref_sources,
1068 value_decl_ref_names: acc.value_decl_ref_names,
1069 value_decl_imported_ref_sources: acc.value_decl_imported_value_ref_sources,
1070 selectors_with_refs_names: acc.selectors_with_value_refs_names,
1071 selectors_with_local_refs_names: acc.selectors_with_local_value_refs_names,
1072 selectors_with_imported_refs_names: acc.selectors_with_imported_value_refs_names,
1073 selectors_with_refs_under_media_names: acc.selectors_with_value_refs_under_media_names,
1074 selectors_with_refs_under_supports_names: acc
1075 .selectors_with_value_refs_under_supports_names,
1076 selectors_with_refs_under_layer_names: acc.selectors_with_value_refs_under_layer_names,
1077 selectors_with_local_refs_under_media_names: acc
1078 .selectors_with_local_value_refs_under_media_names,
1079 selectors_with_local_refs_under_supports_names: acc
1080 .selectors_with_local_value_refs_under_supports_names,
1081 selectors_with_local_refs_under_layer_names: acc
1082 .selectors_with_local_value_refs_under_layer_names,
1083 selectors_with_imported_refs_under_media_names: acc
1084 .selectors_with_imported_value_refs_under_media_names,
1085 selectors_with_imported_refs_under_supports_names: acc
1086 .selectors_with_imported_value_refs_under_supports_names,
1087 selectors_with_imported_refs_under_layer_names: acc
1088 .selectors_with_imported_value_refs_under_layer_names,
1089 },
1090 custom_properties: ParserIndexCustomPropertyFactsV0 {
1091 decl_names: acc.custom_property_decl_names,
1092 decl_facts: acc.custom_property_decl_facts,
1093 decl_context_selectors: acc.custom_property_decl_context_selectors,
1094 decl_names_under_media: acc.custom_property_decl_names_under_media,
1095 decl_names_under_supports: acc.custom_property_decl_names_under_supports,
1096 decl_names_under_layer: acc.custom_property_decl_names_under_layer,
1097 ref_names: acc.custom_property_ref_names,
1098 ref_facts: acc.custom_property_ref_facts,
1099 selectors_with_refs_names: acc.selectors_with_custom_property_refs_names,
1100 selectors_with_refs_under_media_names: acc
1101 .selectors_with_custom_property_refs_under_media_names,
1102 selectors_with_refs_under_supports_names: acc
1103 .selectors_with_custom_property_refs_under_supports_names,
1104 selectors_with_refs_under_layer_names: acc
1105 .selectors_with_custom_property_refs_under_layer_names,
1106 },
1107 sass: ParserIndexSassFactsV0 {
1108 variable_decl_names: acc.sass_variable_decl_names,
1109 symbol_decl_facts: acc.sass_symbol_decl_facts,
1110 variable_parameter_names: acc.sass_variable_parameter_names,
1111 variable_ref_names: acc.sass_variable_ref_names,
1112 selectors_with_variable_refs_names: acc.sass_selectors_with_variable_refs_names,
1113 selectors_with_resolved_variable_refs_names: acc
1114 .sass_selectors_with_resolved_variable_refs_names,
1115 selectors_with_unresolved_variable_refs_names: acc
1116 .sass_selectors_with_unresolved_variable_refs_names,
1117 mixin_decl_names: acc.sass_mixin_decl_names,
1118 mixin_include_names: acc.sass_mixin_include_names,
1119 selectors_with_mixin_includes_names: acc.sass_selectors_with_mixin_includes_names,
1120 selectors_with_resolved_mixin_includes_names: acc
1121 .sass_selectors_with_resolved_mixin_includes_names,
1122 selectors_with_unresolved_mixin_includes_names: acc
1123 .sass_selectors_with_unresolved_mixin_includes_names,
1124 function_decl_names: acc.sass_function_decl_names,
1125 function_call_names: acc.sass_function_call_names,
1126 selectors_with_function_calls_names: acc.sass_selectors_with_function_calls_names,
1127 selector_symbol_facts: acc.sass_selector_symbol_facts,
1128 module_use_sources: acc.sass_module_use_sources,
1129 module_use_edges: acc.sass_module_use_edges,
1130 module_forward_sources: acc.sass_module_forward_sources,
1131 module_import_sources: acc.sass_module_import_sources,
1132 same_file_resolution: sass_same_file_resolution,
1133 },
1134 keyframes: ParserIndexKeyframesFactsV0 {
1135 names: acc.keyframes_names,
1136 names_under_media: acc.keyframes_names_under_media,
1137 names_under_supports: acc.keyframes_names_under_supports,
1138 names_under_layer: acc.keyframes_names_under_layer,
1139 animation_ref_names: acc.animation_ref_names,
1140 animation_name_ref_names: acc.animation_name_ref_names,
1141 selectors_with_animation_ref_names: acc.selectors_with_animation_ref_names,
1142 selectors_with_animation_name_ref_names: acc.selectors_with_animation_name_ref_names,
1143 selectors_with_animation_refs_under_media_names: acc
1144 .selectors_with_animation_refs_under_media_names,
1145 selectors_with_animation_refs_under_supports_names: acc
1146 .selectors_with_animation_refs_under_supports_names,
1147 selectors_with_animation_refs_under_layer_names: acc
1148 .selectors_with_animation_refs_under_layer_names,
1149 selectors_with_animation_name_refs_under_media_names: acc
1150 .selectors_with_animation_name_refs_under_media_names,
1151 selectors_with_animation_name_refs_under_supports_names: acc
1152 .selectors_with_animation_name_refs_under_supports_names,
1153 selectors_with_animation_name_refs_under_layer_names: acc
1154 .selectors_with_animation_name_refs_under_layer_names,
1155 },
1156 composes: ParserIndexComposesFactsV0 {
1157 selectors_with_composes_names: acc.selectors_with_composes_names,
1158 selectors_with_composes_under_media_names: acc
1159 .selectors_with_composes_under_media_names,
1160 selectors_with_composes_under_supports_names: acc
1161 .selectors_with_composes_under_supports_names,
1162 selectors_with_composes_under_layer_names: acc
1163 .selectors_with_composes_under_layer_names,
1164 local_selector_names: acc.local_composes_selector_names,
1165 imported_selector_names: acc.imported_composes_selector_names,
1166 global_selector_names: acc.global_composes_selector_names,
1167 local_selector_names_under_media: acc.local_composes_selector_names_under_media,
1168 local_selector_names_under_supports: acc.local_composes_selector_names_under_supports,
1169 local_selector_names_under_layer: acc.local_composes_selector_names_under_layer,
1170 imported_selector_names_under_media: acc.imported_composes_selector_names_under_media,
1171 imported_selector_names_under_supports: acc
1172 .imported_composes_selector_names_under_supports,
1173 imported_selector_names_under_layer: acc.imported_composes_selector_names_under_layer,
1174 global_selector_names_under_media: acc.global_composes_selector_names_under_media,
1175 global_selector_names_under_supports: acc.global_composes_selector_names_under_supports,
1176 global_selector_names_under_layer: acc.global_composes_selector_names_under_layer,
1177 import_sources: acc.composes_import_sources,
1178 import_sources_under_media: acc.composes_import_sources_under_media,
1179 import_sources_under_supports: acc.composes_import_sources_under_supports,
1180 import_sources_under_layer: acc.composes_import_sources_under_layer,
1181 class_name_count: acc.composes_class_name_count,
1182 local_class_name_count: acc.local_composes_class_name_count,
1183 imported_class_name_count: acc.imported_composes_class_name_count,
1184 global_class_name_count: acc.global_composes_class_name_count,
1185 },
1186 wrappers: ParserIndexWrapperFactsV0 {
1187 selectors_under_media_names: acc.selectors_under_media_names,
1188 selectors_under_supports_names: acc.selectors_under_supports_names,
1189 selectors_under_layer_names: acc.selectors_under_layer_names,
1190 },
1191 }
1192}
1193
1194pub fn summarize_parser_canonical_candidate(
1195 sheet: &Stylesheet,
1196) -> ParserCanonicalCandidateBundleV0 {
1197 let parity_lite = summarize_parity_lite(sheet);
1198 let css_modules_intermediate = summarize_css_modules_intermediate(sheet);
1199
1200 ParserCanonicalCandidateBundleV0 {
1201 schema_version: "0",
1202 language: parity_lite.language,
1203 parity_lite,
1204 css_modules_intermediate,
1205 }
1206}
1207
1208pub fn summarize_parser_evaluator_candidates(sheet: &Stylesheet) -> ParserEvaluatorCandidatesV0 {
1209 let intermediate = summarize_css_modules_intermediate(sheet);
1210 let bem_suffix_safe_names: BTreeSet<&str> = intermediate
1211 .selectors
1212 .bem_suffix_safe_names
1213 .iter()
1214 .map(String::as_str)
1215 .collect();
1216 let nested_unsafe_names: BTreeSet<&str> = intermediate
1217 .selectors
1218 .nested_unsafe_names
1219 .iter()
1220 .map(String::as_str)
1221 .collect();
1222 let selectors_under_media_names: BTreeSet<&str> = intermediate
1223 .wrappers
1224 .selectors_under_media_names
1225 .iter()
1226 .map(String::as_str)
1227 .collect();
1228 let selectors_under_supports_names: BTreeSet<&str> = intermediate
1229 .wrappers
1230 .selectors_under_supports_names
1231 .iter()
1232 .map(String::as_str)
1233 .collect();
1234 let selectors_under_layer_names: BTreeSet<&str> = intermediate
1235 .wrappers
1236 .selectors_under_layer_names
1237 .iter()
1238 .map(String::as_str)
1239 .collect();
1240 let selectors_with_refs_names: BTreeSet<&str> = intermediate
1241 .values
1242 .selectors_with_refs_names
1243 .iter()
1244 .map(String::as_str)
1245 .collect();
1246 let selectors_with_local_refs_names: BTreeSet<&str> = intermediate
1247 .values
1248 .selectors_with_local_refs_names
1249 .iter()
1250 .map(String::as_str)
1251 .collect();
1252 let selectors_with_imported_refs_names: BTreeSet<&str> = intermediate
1253 .values
1254 .selectors_with_imported_refs_names
1255 .iter()
1256 .map(String::as_str)
1257 .collect();
1258 let selectors_with_custom_property_refs_names: BTreeSet<&str> = intermediate
1259 .custom_properties
1260 .selectors_with_refs_names
1261 .iter()
1262 .map(String::as_str)
1263 .collect();
1264 let selectors_with_animation_ref_names: BTreeSet<&str> = intermediate
1265 .keyframes
1266 .selectors_with_animation_ref_names
1267 .iter()
1268 .map(String::as_str)
1269 .collect();
1270 let selectors_with_animation_name_ref_names: BTreeSet<&str> = intermediate
1271 .keyframes
1272 .selectors_with_animation_name_ref_names
1273 .iter()
1274 .map(String::as_str)
1275 .collect();
1276 let selectors_with_composes_names: BTreeSet<&str> = intermediate
1277 .composes
1278 .selectors_with_composes_names
1279 .iter()
1280 .map(String::as_str)
1281 .collect();
1282 let local_selector_names: BTreeSet<&str> = intermediate
1283 .composes
1284 .local_selector_names
1285 .iter()
1286 .map(String::as_str)
1287 .collect();
1288 let imported_selector_names: BTreeSet<&str> = intermediate
1289 .composes
1290 .imported_selector_names
1291 .iter()
1292 .map(String::as_str)
1293 .collect();
1294 let global_selector_names: BTreeSet<&str> = intermediate
1295 .composes
1296 .global_selector_names
1297 .iter()
1298 .map(String::as_str)
1299 .collect();
1300
1301 let results = intermediate
1302 .selectors
1303 .names
1304 .iter()
1305 .map(|selector_name| {
1306 let selector = selector_name.as_str();
1307 let nested_safety_kind = if nested_unsafe_names.contains(selector) {
1308 "nestedUnsafe"
1309 } else if bem_suffix_safe_names.contains(selector) {
1310 "bemSuffixSafe"
1311 } else {
1312 "flat"
1313 };
1314 let bem_suffix_parent_name = if nested_safety_kind == "bemSuffixSafe" {
1315 let suffix_split_index = [selector.rfind("__"), selector.rfind("--")]
1316 .into_iter()
1317 .flatten()
1318 .max();
1319 suffix_split_index.map(|index| selector[..index].to_string())
1320 } else {
1321 None
1322 };
1323
1324 ParserEvaluatorCandidateV0 {
1325 kind: "selector-index-facts",
1326 selector_name: selector_name.clone(),
1327 nested_safety_kind,
1328 bem_suffix_parent_name,
1329 under_media: selectors_under_media_names.contains(selector),
1330 under_supports: selectors_under_supports_names.contains(selector),
1331 under_layer: selectors_under_layer_names.contains(selector),
1332 has_value_refs: selectors_with_refs_names.contains(selector),
1333 has_local_value_refs: selectors_with_local_refs_names.contains(selector),
1334 has_imported_value_refs: selectors_with_imported_refs_names.contains(selector),
1335 has_custom_property_refs: selectors_with_custom_property_refs_names
1336 .contains(selector),
1337 has_animation_ref: selectors_with_animation_ref_names.contains(selector),
1338 has_animation_name_ref: selectors_with_animation_name_ref_names.contains(selector),
1339 has_composes: selectors_with_composes_names.contains(selector),
1340 has_local_composes: local_selector_names.contains(selector),
1341 has_imported_composes: imported_selector_names.contains(selector),
1342 has_global_composes: global_selector_names.contains(selector),
1343 }
1344 })
1345 .collect();
1346
1347 ParserEvaluatorCandidatesV0 {
1348 schema_version: "0",
1349 language: intermediate.language,
1350 results,
1351 }
1352}
1353
1354pub fn summarize_parser_canonical_producer_signal(
1355 sheet: &Stylesheet,
1356) -> ParserCanonicalProducerSignalV0 {
1357 let canonical_candidate = summarize_parser_canonical_candidate(sheet);
1358 let evaluator_candidates = summarize_parser_evaluator_candidates(sheet);
1359
1360 ParserCanonicalProducerSignalV0 {
1361 schema_version: "0",
1362 language: canonical_candidate.language,
1363 canonical_candidate,
1364 evaluator_candidates,
1365 public_product_gate: ParserPublicProductGateSignalV0 {
1366 canonical_candidate_command: "pnpm check:rust-parser-canonical-candidate",
1367 consumer_boundary_command: "pnpm check:rust-parser-consumer-boundary",
1368 public_product_gate_command: "pnpm check:rust-parser-public-product",
1369 included_in_parser_lane: true,
1370 included_in_rust_lane_bundle: true,
1371 included_in_rust_release_bundle: true,
1372 },
1373 }
1374}
1375
1376pub fn summarize_index_bridge(sheet: &Stylesheet) -> ParserIndexSummaryV0 {
1377 summarize_css_modules_intermediate(sheet)
1378}
1379
1380pub fn summarize_semantic_boundary(sheet: &Stylesheet) -> ParserSemanticBoundarySummaryV0 {
1381 let index = summarize_css_modules_intermediate(sheet);
1382 let ParserIndexSummaryV0 {
1383 schema_version: _,
1384 language,
1385 selectors,
1386 values,
1387 custom_properties,
1388 sass,
1389 keyframes,
1390 composes,
1391 wrappers,
1392 } = index;
1393 let ParserIndexSelectorFactsV0 {
1394 names,
1395 definition_facts,
1396 bem_suffix_parent_names,
1397 bem_suffix_safe_names,
1398 nested_unsafe_names,
1399 selectors_with_value_refs_names,
1400 selectors_with_animation_ref_names,
1401 selectors_with_animation_name_ref_names,
1402 bem_suffix_count,
1403 nested_safety_counts,
1404 } = selectors;
1405 let ParserIndexSassFactsV0 {
1406 variable_decl_names,
1407 symbol_decl_facts: _,
1408 variable_parameter_names,
1409 variable_ref_names,
1410 selectors_with_variable_refs_names: _,
1411 selectors_with_resolved_variable_refs_names,
1412 selectors_with_unresolved_variable_refs_names,
1413 mixin_decl_names,
1414 mixin_include_names,
1415 selectors_with_mixin_includes_names: _,
1416 selectors_with_resolved_mixin_includes_names,
1417 selectors_with_unresolved_mixin_includes_names,
1418 function_decl_names,
1419 function_call_names,
1420 selectors_with_function_calls_names,
1421 selector_symbol_facts,
1422 module_use_sources,
1423 module_use_edges,
1424 module_forward_sources,
1425 module_import_sources,
1426 same_file_resolution,
1427 } = sass;
1428 let custom_property_semantic_facts =
1429 summarize_custom_property_semantic_facts(&custom_properties);
1430
1431 ParserSemanticBoundarySummaryV0 {
1432 schema_version: "0",
1433 language,
1434 parser_facts: ParserBoundarySyntaxFactsV0 {
1435 lossless_cst: summarize_lossless_cst(sheet),
1436 selectors: ParserIndexSelectorFactsV0 {
1437 names: names.clone(),
1438 definition_facts,
1439 bem_suffix_parent_names: bem_suffix_parent_names.clone(),
1440 bem_suffix_safe_names: bem_suffix_safe_names.clone(),
1441 nested_unsafe_names: nested_unsafe_names.clone(),
1442 selectors_with_value_refs_names,
1443 selectors_with_animation_ref_names,
1444 selectors_with_animation_name_ref_names,
1445 bem_suffix_count,
1446 nested_safety_counts: nested_safety_counts.clone(),
1447 },
1448 values,
1449 custom_properties,
1450 sass: ParserSassSyntaxFactsV0 {
1451 variable_decl_names,
1452 variable_parameter_names,
1453 variable_ref_names,
1454 mixin_decl_names,
1455 mixin_include_names,
1456 function_decl_names,
1457 function_call_names,
1458 module_use_sources,
1459 module_use_edges,
1460 module_forward_sources,
1461 module_import_sources,
1462 },
1463 keyframes,
1464 composes,
1465 wrappers,
1466 },
1467 semantic_facts: StyleSemanticFactsV0 {
1468 selector_identity: StyleSelectorIdentityFactsV0 {
1469 canonical_names: names,
1470 bem_suffix_safe_names,
1471 bem_suffix_parent_names,
1472 nested_unsafe_names,
1473 nested_safety_counts,
1474 },
1475 custom_properties: custom_property_semantic_facts,
1476 sass: StyleSassSemanticFactsV0 {
1477 selector_symbol_facts,
1478 selectors_with_resolved_variable_refs_names,
1479 selectors_with_unresolved_variable_refs_names,
1480 selectors_with_resolved_mixin_includes_names,
1481 selectors_with_unresolved_mixin_includes_names,
1482 selectors_with_function_calls_names,
1483 same_file_resolution,
1484 },
1485 },
1486 }
1487}
1488
1489fn summarize_custom_property_semantic_facts(
1490 facts: &ParserIndexCustomPropertyFactsV0,
1491) -> StyleCustomPropertySemanticFactsV0 {
1492 let (resolved_ref_names, unresolved_ref_names) =
1493 summarize_custom_property_resolution_names(facts);
1494
1495 StyleCustomPropertySemanticFactsV0 {
1496 decl_names: facts.decl_names.clone(),
1497 ref_names: facts.ref_names.clone(),
1498 resolved_ref_names,
1499 unresolved_ref_names,
1500 selectors_with_refs_names: facts.selectors_with_refs_names.clone(),
1501 }
1502}
1503
1504fn summarize_custom_property_resolution_names(
1505 facts: &ParserIndexCustomPropertyFactsV0,
1506) -> (Vec<String>, Vec<String>) {
1507 if !facts.decl_facts.is_empty() && !facts.ref_facts.is_empty() {
1508 let mut resolved = BTreeSet::new();
1509 let mut unresolved = BTreeSet::new();
1510 for ref_fact in &facts.ref_facts {
1511 if facts
1512 .decl_facts
1513 .iter()
1514 .any(|decl_fact| custom_property_context_matches(decl_fact, ref_fact))
1515 {
1516 resolved.insert(ref_fact.name.clone());
1517 } else {
1518 unresolved.insert(ref_fact.name.clone());
1519 }
1520 }
1521 return (
1522 resolved.into_iter().collect(),
1523 unresolved.into_iter().collect(),
1524 );
1525 }
1526
1527 let decl_names: BTreeSet<&str> = facts.decl_names.iter().map(String::as_str).collect();
1528 let resolved_ref_names = facts
1529 .ref_names
1530 .iter()
1531 .filter(|name| decl_names.contains(name.as_str()))
1532 .cloned()
1533 .collect();
1534 let unresolved_ref_names = facts
1535 .ref_names
1536 .iter()
1537 .filter(|name| !decl_names.contains(name.as_str()))
1538 .cloned()
1539 .collect();
1540 (resolved_ref_names, unresolved_ref_names)
1541}
1542
1543fn custom_property_context_matches(
1544 decl: &ParserIndexCustomPropertyDeclFactV0,
1545 reference: &ParserIndexCustomPropertyRefFactV0,
1546) -> bool {
1547 if decl.name != reference.name {
1548 return false;
1549 }
1550 if decl.under_media && !reference.under_media {
1551 return false;
1552 }
1553 if decl.under_supports && !reference.under_supports {
1554 return false;
1555 }
1556 if decl.under_layer && !reference.under_layer {
1557 return false;
1558 }
1559 if decl.selector_contexts.is_empty() {
1560 return true;
1561 }
1562 decl.selector_contexts
1563 .iter()
1564 .any(|decl_selector| custom_property_selector_context_matches(decl_selector, reference))
1565}
1566
1567fn custom_property_selector_context_matches(
1568 decl_selector: &str,
1569 reference: &ParserIndexCustomPropertyRefFactV0,
1570) -> bool {
1571 decl_selector == ":root"
1572 || reference.selector_contexts.iter().any(|ref_selector| {
1573 ref_selector == decl_selector || ref_selector.contains(decl_selector)
1574 })
1575}
1576
1577fn summarize_lossless_cst(sheet: &Stylesheet) -> ParserLosslessCstFactsV0 {
1578 let source_byte_len = sheet.source.len();
1579 ParserLosslessCstFactsV0 {
1580 source_byte_len,
1581 token_count: sheet.tokens.len(),
1582 root_node_count: sheet.nodes.len(),
1583 diagnostic_count: sheet.diagnostics.len(),
1584 all_token_spans_within_source: sheet
1585 .tokens
1586 .iter()
1587 .all(|token| is_valid_span(token.span, source_byte_len)),
1588 all_node_spans_within_source: nodes_have_valid_spans(&sheet.nodes, source_byte_len),
1589 }
1590}
1591
1592fn nodes_have_valid_spans(nodes: &[SyntaxNode], source_byte_len: usize) -> bool {
1593 nodes.iter().all(|node| {
1594 is_valid_span(node.span, source_byte_len)
1595 && node
1596 .header_span
1597 .is_none_or(|span| is_valid_span(span, source_byte_len))
1598 && nodes_have_valid_spans(&node.children, source_byte_len)
1599 })
1600}
1601
1602fn is_valid_span(span: TextSpan, source_byte_len: usize) -> bool {
1603 span.start <= span.end && span.end <= source_byte_len
1604}
1605
1606fn collect_parity_names(nodes: &[SyntaxNode], acc: &mut ParityLiteAcc) {
1607 collect_parity_names_with_parent(nodes, acc, &[], 0);
1608}
1609
1610fn collect_parity_names_with_parent(
1611 nodes: &[SyntaxNode],
1612 acc: &mut ParityLiteAcc,
1613 parent_branches: &[ResolvedSelectorBranch],
1614 depth: usize,
1615) {
1616 for node in nodes {
1617 let mut next_parent_branches = parent_branches.to_vec();
1618 let mut next_depth = depth;
1619 match &node.payload {
1620 Some(SyntaxNodePayload::Rule(rule)) => {
1621 acc.rule_count += 1;
1622 next_depth = depth + 1;
1623 acc.max_nesting_depth = acc.max_nesting_depth.max(next_depth);
1624 if rule.selector_groups.len() > 1 {
1625 acc.grouped_selector_count += rule.selector_groups.len();
1626 }
1627 let resolved = resolve_rule_selector_branches(rule, parent_branches);
1628 if !resolved.is_empty() {
1629 acc.selector_names
1630 .extend(resolved.iter().map(|branch| branch.name.clone()));
1631 next_parent_branches = resolved;
1632 }
1633 }
1634 Some(SyntaxNodePayload::AtRule(at_rule)) => {
1635 next_depth = depth + 1;
1636 acc.max_nesting_depth = acc.max_nesting_depth.max(next_depth);
1637 increment_at_rule_kind_count(&mut acc.at_rule_kind_counts, at_rule.kind);
1638 match at_rule.kind {
1639 AtRuleKind::Keyframes if !at_rule.params.is_empty() => {
1640 acc.keyframes_names.push(at_rule.params.clone());
1641 }
1642 AtRuleKind::Keyframes => {}
1643 AtRuleKind::Value => {
1644 if let Some((name, _)) = at_rule.params.split_once(':') {
1645 let trimmed = name.trim();
1646 if !trimmed.is_empty() {
1647 acc.value_decl_names.push(trimmed.to_string());
1648 }
1649 }
1650 }
1651 _ => {}
1652 }
1653 }
1654 Some(SyntaxNodePayload::Declaration(declaration)) => {
1655 acc.declaration_count += 1;
1656 increment_declaration_kind_count(
1657 &mut acc.declaration_kind_counts,
1658 classify_declaration_kind(&declaration.property),
1659 );
1660 }
1661 _ => {}
1662 }
1663 collect_parity_names_with_parent(&node.children, acc, &next_parent_branches, next_depth);
1664 }
1665}
1666
1667fn increment_at_rule_kind_count(counts: &mut AtRuleKindCountsV0, kind: AtRuleKind) {
1668 match kind {
1669 AtRuleKind::Media => counts.media += 1,
1670 AtRuleKind::Supports => counts.supports += 1,
1671 AtRuleKind::Layer => counts.layer += 1,
1672 AtRuleKind::Keyframes => counts.keyframes += 1,
1673 AtRuleKind::Value => counts.value += 1,
1674 AtRuleKind::AtRoot => counts.at_root += 1,
1675 AtRuleKind::Mixin
1676 | AtRuleKind::Include
1677 | AtRuleKind::Function
1678 | AtRuleKind::Use
1679 | AtRuleKind::Forward
1680 | AtRuleKind::Import => counts.generic += 1,
1681 AtRuleKind::Generic => counts.generic += 1,
1682 }
1683}
1684
1685#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1686enum DeclarationKind {
1687 Composes,
1688 Animation,
1689 AnimationName,
1690 Generic,
1691}
1692
1693#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1694enum NestedSafetyKind {
1695 Flat,
1696 BemSuffixSafe,
1697 NestedUnsafe,
1698}
1699
1700struct RuleSelectorFacts {
1701 nested_safety: NestedSafetyKind,
1702 bem_suffix_count: usize,
1703}
1704
1705#[derive(Debug, Default)]
1706struct RuleComposesFacts {
1707 local_class_name_count: usize,
1708 imported_class_name_count: usize,
1709 global_class_name_count: usize,
1710 imported_sources: Vec<String>,
1711}
1712
1713#[derive(Debug, Default)]
1714struct RuleReferenceFacts {
1715 has_value_refs: bool,
1716 has_local_value_refs: bool,
1717 has_imported_value_refs: bool,
1718 has_animation_refs: bool,
1719 has_animation_name_refs: bool,
1720 has_custom_property_refs: bool,
1721 custom_property_ref_names: Vec<String>,
1722 has_sass_variable_refs: bool,
1723 has_resolved_sass_variable_refs: bool,
1724 has_unresolved_sass_variable_refs: bool,
1725 has_sass_mixin_includes: bool,
1726 has_resolved_sass_mixin_includes: bool,
1727 has_unresolved_sass_mixin_includes: bool,
1728 has_sass_function_calls: bool,
1729 sass_symbol_facts: Vec<RuleSassSymbolFact>,
1730}
1731
1732#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
1733struct RuleSassSymbolFact {
1734 symbol_kind: &'static str,
1735 name: String,
1736 namespace: Option<String>,
1737 role: &'static str,
1738 resolution: &'static str,
1739 byte_span: ParserByteSpanV0,
1740}
1741
1742#[derive(Debug, Clone, PartialEq, Eq)]
1743struct SassNameSpan {
1744 name: String,
1745 namespace: Option<String>,
1746 byte_span: ParserByteSpanV0,
1747}
1748
1749#[derive(Debug, Clone, PartialEq, Eq)]
1750struct CustomPropertyDeclNameSpan {
1751 name: String,
1752 byte_span: ParserByteSpanV0,
1753 range: ParserRangeV0,
1754}
1755
1756#[derive(Debug, Clone, PartialEq, Eq)]
1757struct SassVariableDeclFact {
1758 name: String,
1759 scope: SassVariableScope,
1760}
1761
1762#[derive(Debug, Clone, PartialEq, Eq)]
1763struct SassVariableRefFact {
1764 name: String,
1765 namespace: Option<String>,
1766 byte_span: ParserByteSpanV0,
1767}
1768
1769#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1770enum SassVariableScope {
1771 File,
1772 Span(TextSpan),
1773}
1774
1775#[derive(Debug, Clone, Copy, Default)]
1776struct WrapperContext {
1777 under_media: bool,
1778 under_supports: bool,
1779 under_layer: bool,
1780}
1781
1782#[derive(Debug, Clone, Copy)]
1783struct ValueRefContext<'a> {
1784 known: &'a BTreeSet<String>,
1785 local: &'a BTreeSet<String>,
1786 imported: &'a BTreeSet<String>,
1787}
1788
1789#[derive(Debug, Clone, Copy)]
1790struct SassRefContext<'a> {
1791 variable_decls: &'a [SassVariableDeclFact],
1792 mixin_targets: &'a BTreeSet<String>,
1793 function_targets: &'a BTreeSet<String>,
1794}
1795
1796#[derive(Debug, Clone, Copy)]
1797struct SelectorAttachmentContext<'a> {
1798 source: &'a str,
1799 value_ref_ctx: ValueRefContext<'a>,
1800 known_keyframe_names: &'a BTreeSet<String>,
1801 sass_ref_ctx: SassRefContext<'a>,
1802}
1803
1804#[derive(Debug, Clone, Copy)]
1805enum ValueRefOrigin {
1806 Declaration,
1807 ValueDecl,
1808}
1809
1810#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1811enum ComposesKind {
1812 Local,
1813 Imported,
1814 Global,
1815}
1816
1817#[derive(Debug)]
1818struct ComposesSpec {
1819 class_names: Vec<String>,
1820 kind: ComposesKind,
1821 from_source: Option<String>,
1822}
1823
1824fn classify_declaration_kind(property: &str) -> DeclarationKind {
1825 match property.trim().to_ascii_lowercase().as_str() {
1826 "composes" => DeclarationKind::Composes,
1827 "animation" => DeclarationKind::Animation,
1828 "animation-name" => DeclarationKind::AnimationName,
1829 _ => DeclarationKind::Generic,
1830 }
1831}
1832
1833fn increment_declaration_kind_count(counts: &mut DeclarationKindCountsV0, kind: DeclarationKind) {
1834 match kind {
1835 DeclarationKind::Composes => counts.composes += 1,
1836 DeclarationKind::Animation => counts.animation += 1,
1837 DeclarationKind::AnimationName => counts.animation_name += 1,
1838 DeclarationKind::Generic => counts.generic += 1,
1839 }
1840}
1841
1842fn classify_rule_selector_facts(
1843 rule: &RulePayload,
1844 parent_branches: &[ResolvedSelectorBranch],
1845 parent_is_grouped: bool,
1846) -> RuleSelectorFacts {
1847 let is_nested = !parent_branches.is_empty()
1848 || rule
1849 .selector_groups
1850 .iter()
1851 .any(|group| group.raw.contains('&'));
1852 if !is_nested {
1853 return RuleSelectorFacts {
1854 nested_safety: NestedSafetyKind::Flat,
1855 bem_suffix_count: 0,
1856 };
1857 }
1858
1859 let bem_suffix_safe = rule.selector_groups.len() == 1
1860 && parent_branches.len() == 1
1861 && parent_branches[0].bare_suffix_base
1862 && !parent_is_grouped
1863 && matches!(
1864 rule.selector_groups[0].segments.as_slice(),
1865 [SelectorSegment::Ampersand, SelectorSegment::BemSuffix(_)]
1866 );
1867
1868 if bem_suffix_safe {
1869 RuleSelectorFacts {
1870 nested_safety: NestedSafetyKind::BemSuffixSafe,
1871 bem_suffix_count: 1,
1872 }
1873 } else {
1874 RuleSelectorFacts {
1875 nested_safety: NestedSafetyKind::NestedUnsafe,
1876 bem_suffix_count: 0,
1877 }
1878 }
1879}
1880
1881fn increment_nested_safety_count(
1882 counts: &mut NestedSafetyCountsV0,
1883 kind: NestedSafetyKind,
1884 amount: usize,
1885) {
1886 match kind {
1887 NestedSafetyKind::Flat => counts.flat += amount,
1888 NestedSafetyKind::BemSuffixSafe => counts.bem_suffix_safe += amount,
1889 NestedSafetyKind::NestedUnsafe => counts.nested_unsafe += amount,
1890 }
1891}
1892
1893fn nested_safety_kind_label(kind: NestedSafetyKind) -> &'static str {
1894 match kind {
1895 NestedSafetyKind::Flat => "flat",
1896 NestedSafetyKind::BemSuffixSafe => "bemSuffixSafe",
1897 NestedSafetyKind::NestedUnsafe => "nestedUnsafe",
1898 }
1899}
1900
1901fn rule_selector_definition_spans(
1902 source: &str,
1903 header_span: TextSpan,
1904 rule: &RulePayload,
1905 resolved_names: &[String],
1906 parent_branches: &[ResolvedSelectorBranch],
1907) -> Vec<(String, ParserByteSpanV0)> {
1908 let resolved_names: BTreeSet<&str> = resolved_names.iter().map(String::as_str).collect();
1909 let mut seen = BTreeSet::new();
1910 let mut spans = Vec::new();
1911
1912 for group in &rule.selector_groups {
1913 let include_all_class_segments = selector_group_allows_all_class_definition_facts(group);
1914 for segment in &group.segments {
1915 let SelectorSegment::ClassName(name) = segment else {
1916 continue;
1917 };
1918 if !include_all_class_segments && !resolved_names.contains(name.as_str()) {
1919 continue;
1920 }
1921 for byte_span in class_name_byte_spans_in_header(source, header_span, name) {
1922 if seen.insert((byte_span.start, byte_span.end, name.clone())) {
1923 spans.push((name.clone(), byte_span));
1924 }
1925 }
1926 }
1927
1928 for (name, byte_span) in
1929 bem_suffix_selector_definition_spans(source, header_span, group, parent_branches)
1930 {
1931 if !resolved_names.contains(name.as_str()) {
1932 continue;
1933 }
1934 if seen.insert((byte_span.start, byte_span.end, name.clone())) {
1935 spans.push((name, byte_span));
1936 }
1937 }
1938 }
1939
1940 spans
1941}
1942
1943fn selector_group_allows_all_class_definition_facts(group: &SelectorGroup) -> bool {
1944 ![":global(", ":not(", ":is(", ":where(", ":has("]
1945 .iter()
1946 .any(|marker| group.raw.contains(marker))
1947}
1948
1949fn bem_suffix_selector_definition_spans(
1950 source: &str,
1951 header_span: TextSpan,
1952 group: &SelectorGroup,
1953 parent_branches: &[ResolvedSelectorBranch],
1954) -> Vec<(String, ParserByteSpanV0)> {
1955 let suffix = match group.segments.as_slice() {
1956 [
1957 SelectorSegment::Ampersand,
1958 SelectorSegment::BemSuffix(suffix),
1959 ]
1960 | [
1961 SelectorSegment::Ampersand,
1962 SelectorSegment::AmpersandSuffix(suffix),
1963 ] => suffix,
1964 segments => {
1965 let last_combinator = segments
1966 .iter()
1967 .enumerate()
1968 .rev()
1969 .find_map(|(index, segment)| {
1970 matches!(segment, SelectorSegment::Combinator(_)).then_some(index)
1971 });
1972 let tail = last_combinator
1973 .map(|index| &segments[index + 1..])
1974 .unwrap_or(segments);
1975 match tail {
1976 [
1977 SelectorSegment::Ampersand,
1978 SelectorSegment::BemSuffix(suffix),
1979 ]
1980 | [
1981 SelectorSegment::Ampersand,
1982 SelectorSegment::AmpersandSuffix(suffix),
1983 ] => suffix,
1984 _ => return Vec::new(),
1985 }
1986 }
1987 };
1988 let Some(suffix_span) = ampersand_suffix_byte_span_in_header(source, header_span, suffix)
1989 else {
1990 return Vec::new();
1991 };
1992
1993 parent_branches
1994 .iter()
1995 .filter(|parent| parent.bare_suffix_base)
1996 .map(|parent| (format!("{}{}", parent.name, suffix), suffix_span))
1997 .collect()
1998}
1999
2000fn ampersand_suffix_byte_span_in_header(
2001 source: &str,
2002 header_span: TextSpan,
2003 suffix: &str,
2004) -> Option<ParserByteSpanV0> {
2005 let header = &source[header_span.start..header_span.end];
2006 let needle = format!("&{suffix}");
2007 let relative_match = header.find(&needle)?;
2008 let suffix_start = header_span.start + relative_match + 1;
2009 let suffix_end = suffix_start + suffix.len();
2010 Some(ParserByteSpanV0 {
2011 start: suffix_start,
2012 end: suffix_end,
2013 })
2014}
2015
2016fn class_name_byte_spans_in_header(
2017 source: &str,
2018 header_span: TextSpan,
2019 name: &str,
2020) -> Vec<ParserByteSpanV0> {
2021 let header = &source[header_span.start..header_span.end];
2022 let needle = format!(".{name}");
2023 let mut spans = Vec::new();
2024 let mut search_offset = 0usize;
2025
2026 while let Some(relative_match) = header[search_offset..].find(&needle) {
2027 let dot_start = header_span.start + search_offset + relative_match;
2028 let name_start = dot_start + 1;
2029 let name_end = name_start + name.len();
2030 if is_selector_name_boundary(source, name_end) {
2031 spans.push(ParserByteSpanV0 {
2032 start: name_start,
2033 end: name_end,
2034 });
2035 }
2036 search_offset += relative_match + needle.len();
2037 }
2038
2039 spans
2040}
2041
2042fn is_selector_name_boundary(source: &str, byte_offset: usize) -> bool {
2043 source[byte_offset..]
2044 .chars()
2045 .next()
2046 .is_none_or(|ch| !is_selector_ident_continue(ch))
2047}
2048
2049fn collect_rule_composes_facts(children: &[SyntaxNode]) -> RuleComposesFacts {
2050 let mut facts = RuleComposesFacts::default();
2051 for child in children {
2052 if let Some(SyntaxNodePayload::Declaration(declaration)) = &child.payload
2053 && classify_declaration_kind(&declaration.property) == DeclarationKind::Composes
2054 && let Some(spec) = parse_composes_spec(&declaration.value)
2055 {
2056 match spec.kind {
2057 ComposesKind::Local => facts.local_class_name_count += spec.class_names.len(),
2058 ComposesKind::Imported => {
2059 facts.imported_class_name_count += spec.class_names.len();
2060 if let Some(source) = spec.from_source {
2061 facts.imported_sources.push(source);
2062 }
2063 }
2064 ComposesKind::Global => facts.global_class_name_count += spec.class_names.len(),
2065 }
2066 }
2067 }
2068 facts
2069}
2070
2071fn declaration_value_span(source: &str, node: &SyntaxNode) -> TextSpan {
2072 let header_span = node.header_span.unwrap_or(node.span);
2073 let raw = &source[header_span.start..header_span.end];
2074 let Some(colon_index) = raw.find(':') else {
2075 return TextSpan::new(header_span.end, header_span.end);
2076 };
2077 trim_source_span(
2078 source,
2079 TextSpan::new(header_span.start + colon_index + 1, header_span.end),
2080 )
2081}
2082
2083fn at_rule_params_span(source: &str, node: &SyntaxNode, at_rule: &AtRulePayload) -> TextSpan {
2084 let header_span = node.header_span.unwrap_or(node.span);
2085 let raw = &source[header_span.start..header_span.end];
2086 let search_start = raw.find('@').map_or(0, |index| index + 1);
2087 let Some(name_index) = raw[search_start..].find(&at_rule.name) else {
2088 return TextSpan::new(header_span.end, header_span.end);
2089 };
2090 let params_start = header_span.start + search_start + name_index + at_rule.name.len();
2091 trim_source_span(source, TextSpan::new(params_start, header_span.end))
2092}
2093
2094fn trim_source_span(source: &str, span: TextSpan) -> TextSpan {
2095 let raw = &source[span.start..span.end];
2096 let trimmed_start = raw.len() - raw.trim_start().len();
2097 let trimmed_len = raw.trim().len();
2098 let start = span.start + trimmed_start;
2099 TextSpan::new(start, start + trimmed_len)
2100}
2101
2102fn source_range_for_byte_span(source: &str, span: ParserByteSpanV0) -> ParserRangeV0 {
2103 ParserRangeV0 {
2104 start: source_position_for_byte_offset(source, span.start),
2105 end: source_position_for_byte_offset(source, span.end),
2106 }
2107}
2108
2109fn source_position_for_byte_offset(source: &str, offset: usize) -> ParserPositionV0 {
2110 let clamped_offset = offset.min(source.len());
2111 let mut line = 0usize;
2112 let mut character = 0usize;
2113
2114 for (byte_index, ch) in source.char_indices() {
2115 if byte_index >= clamped_offset {
2116 break;
2117 }
2118 if ch == '\n' {
2119 line += 1;
2120 character = 0;
2121 } else {
2122 character += ch.len_utf16();
2123 }
2124 }
2125
2126 ParserPositionV0 { line, character }
2127}
2128
2129fn collect_rule_reference_facts(
2130 children: &[SyntaxNode],
2131 source: &str,
2132 value_ref_ctx: ValueRefContext<'_>,
2133 known_keyframe_names: &BTreeSet<String>,
2134 sass_ref_ctx: SassRefContext<'_>,
2135) -> RuleReferenceFacts {
2136 let mut facts = RuleReferenceFacts::default();
2137 for child in children {
2138 match &child.payload {
2139 Some(SyntaxNodePayload::Declaration(declaration)) => {
2140 let value_span = declaration_value_span(source, child);
2141 match classify_declaration_kind(&declaration.property) {
2142 DeclarationKind::Composes => {}
2143 DeclarationKind::Animation => {
2144 if !find_identifier_matches(&declaration.value, known_keyframe_names)
2145 .is_empty()
2146 {
2147 facts.has_animation_refs = true;
2148 }
2149 extend_rule_value_ref_facts(&mut facts, &declaration.value, value_ref_ctx);
2150 }
2151 DeclarationKind::AnimationName => {
2152 if !find_identifier_matches(&declaration.value, known_keyframe_names)
2153 .is_empty()
2154 {
2155 facts.has_animation_name_refs = true;
2156 }
2157 extend_rule_value_ref_facts(&mut facts, &declaration.value, value_ref_ctx);
2158 }
2159 DeclarationKind::Generic => {
2160 extend_rule_value_ref_facts(&mut facts, &declaration.value, value_ref_ctx);
2161 }
2162 }
2163 extend_rule_sass_value_ref_facts(
2164 &mut facts,
2165 &declaration.value,
2166 value_span,
2167 sass_ref_ctx,
2168 );
2169 let custom_property_refs = find_css_var_ref_names(&declaration.value);
2170 if !custom_property_refs.is_empty() {
2171 facts.has_custom_property_refs = true;
2172 facts.custom_property_ref_names.extend(custom_property_refs);
2173 }
2174 }
2175 Some(SyntaxNodePayload::AtRule(at_rule)) => match at_rule.kind {
2176 AtRuleKind::Mixin | AtRuleKind::Function => {}
2177 AtRuleKind::Include => {
2178 let params_span = at_rule_params_span(source, child, at_rule);
2179 extend_rule_sass_value_ref_facts(
2180 &mut facts,
2181 &at_rule.params,
2182 params_span,
2183 sass_ref_ctx,
2184 );
2185 if let Some(name_span) =
2186 parse_sass_callable_name_with_span(&at_rule.params, params_span.start)
2187 {
2188 facts.has_sass_mixin_includes = true;
2189 let resolution = if name_span.namespace.is_some() {
2190 "external"
2191 } else if sass_ref_ctx.mixin_targets.contains(&name_span.name) {
2192 facts.has_resolved_sass_mixin_includes = true;
2193 "resolved"
2194 } else {
2195 facts.has_unresolved_sass_mixin_includes = true;
2196 "unresolved"
2197 };
2198 facts.sass_symbol_facts.push(RuleSassSymbolFact {
2199 symbol_kind: "mixin",
2200 name: name_span.name,
2201 namespace: name_span.namespace,
2202 role: "include",
2203 resolution,
2204 byte_span: name_span.byte_span,
2205 });
2206 }
2207 }
2208 _ => {
2209 let params_span = at_rule_params_span(source, child, at_rule);
2210 extend_rule_sass_value_ref_facts(
2211 &mut facts,
2212 &at_rule.params,
2213 params_span,
2214 sass_ref_ctx,
2215 );
2216 }
2217 },
2218 _ => {}
2219 }
2220 }
2221 facts
2222}
2223
2224fn extend_rule_value_ref_facts(
2225 facts: &mut RuleReferenceFacts,
2226 value: &str,
2227 value_ref_ctx: ValueRefContext<'_>,
2228) {
2229 let value_refs = find_identifier_matches(value, value_ref_ctx.known);
2230 if !value_refs.is_empty() {
2231 facts.has_value_refs = true;
2232 facts.has_local_value_refs |= value_refs
2233 .iter()
2234 .any(|name| value_ref_ctx.local.contains(name));
2235 facts.has_imported_value_refs |= value_refs
2236 .iter()
2237 .any(|name| value_ref_ctx.imported.contains(name));
2238 }
2239}
2240
2241fn extend_rule_sass_value_ref_facts(
2242 facts: &mut RuleReferenceFacts,
2243 value: &str,
2244 value_span: TextSpan,
2245 sass_ref_ctx: SassRefContext<'_>,
2246) {
2247 let variable_refs = find_sass_variable_ref_spans(value, value_span.start);
2248 if !variable_refs.is_empty() {
2249 facts.has_sass_variable_refs = true;
2250 for name_span in variable_refs {
2251 let resolution = if name_span.namespace.is_some() {
2252 "external"
2253 } else if resolve_sass_variable_ref(
2254 &name_span.name,
2255 name_span.byte_span,
2256 sass_ref_ctx.variable_decls,
2257 ) {
2258 facts.has_resolved_sass_variable_refs = true;
2259 "resolved"
2260 } else {
2261 facts.has_unresolved_sass_variable_refs = true;
2262 "unresolved"
2263 };
2264 facts.sass_symbol_facts.push(RuleSassSymbolFact {
2265 symbol_kind: "variable",
2266 name: name_span.name,
2267 namespace: name_span.namespace,
2268 role: "reference",
2269 resolution,
2270 byte_span: name_span.byte_span,
2271 });
2272 }
2273 }
2274
2275 let function_calls =
2276 find_sass_function_call_spans(value, value_span.start, sass_ref_ctx.function_targets);
2277 if !function_calls.is_empty() {
2278 facts.has_sass_function_calls = true;
2279 facts
2280 .sass_symbol_facts
2281 .extend(
2282 function_calls
2283 .into_iter()
2284 .map(|name_span| RuleSassSymbolFact {
2285 symbol_kind: "function",
2286 name: name_span.name,
2287 namespace: name_span.namespace.clone(),
2288 role: "call",
2289 resolution: if name_span.namespace.is_some() {
2290 "external"
2291 } else {
2292 "resolved"
2293 },
2294 byte_span: name_span.byte_span,
2295 }),
2296 );
2297 }
2298}
2299
2300fn resolve_sass_variable_ref(
2301 name: &str,
2302 byte_span: ParserByteSpanV0,
2303 variable_decls: &[SassVariableDeclFact],
2304) -> bool {
2305 variable_decls
2306 .iter()
2307 .any(|decl| decl.name == name && sass_variable_scope_contains(decl.scope, byte_span))
2308}
2309
2310fn sass_variable_scope_contains(scope: SassVariableScope, byte_span: ParserByteSpanV0) -> bool {
2311 match scope {
2312 SassVariableScope::File => true,
2313 SassVariableScope::Span(scope_span) => {
2314 scope_span.start <= byte_span.start && scope_span.end >= byte_span.end
2315 }
2316 }
2317}
2318
2319fn collect_index_names(
2320 source: &str,
2321 nodes: &[SyntaxNode],
2322 acc: &mut IndexSummaryAcc,
2323 parent_branches: &[ResolvedSelectorBranch],
2324 parent_is_grouped: bool,
2325 current_sass_scope: Option<TextSpan>,
2326 wrapper_ctx: WrapperContext,
2327) {
2328 for node in nodes {
2329 let mut next_parent_branches = parent_branches.to_vec();
2330 let mut next_parent_is_grouped = false;
2331 let mut split_child_branches = false;
2332 let mut next_sass_scope = current_sass_scope;
2333 let mut child_wrapper_ctx = wrapper_ctx;
2334 match &node.payload {
2335 Some(SyntaxNodePayload::Rule(rule)) => {
2336 next_sass_scope = Some(node.span);
2337 let resolved_branches = resolve_rule_selector_branches(rule, parent_branches);
2338 let custom_property_decl_name_spans =
2339 custom_property_decl_name_spans_in_rule(&node.children, source);
2340 if !custom_property_decl_name_spans.is_empty() {
2341 let selector_contexts = selector_contexts_for_rule(rule, &resolved_branches);
2342 let source_order_offset = acc.custom_property_decl_facts.len();
2343 acc.custom_property_decl_context_selectors
2344 .extend(selector_contexts.iter().cloned());
2345 acc.custom_property_decl_facts.extend(
2346 custom_property_decl_name_spans.into_iter().enumerate().map(
2347 |(index, fact)| ParserIndexCustomPropertyDeclFactV0 {
2348 name: fact.name,
2349 source_order: source_order_offset + index,
2350 byte_span: fact.byte_span,
2351 range: fact.range,
2352 selector_contexts: selector_contexts.clone(),
2353 under_media: wrapper_ctx.under_media,
2354 under_supports: wrapper_ctx.under_supports,
2355 under_layer: wrapper_ctx.under_layer,
2356 },
2357 ),
2358 );
2359 }
2360 if !resolved_branches.is_empty() {
2361 let resolved: Vec<String> = resolved_branches
2362 .iter()
2363 .map(|branch| branch.name.clone())
2364 .collect();
2365 let selector_facts =
2366 classify_rule_selector_facts(rule, parent_branches, parent_is_grouped);
2367 let selector_definition_source_order_offset =
2368 acc.selector_definition_facts.len();
2369 let selector_definition_spans = rule_selector_definition_spans(
2370 source,
2371 node.header_span.unwrap_or(node.span),
2372 rule,
2373 &resolved,
2374 parent_branches,
2375 );
2376 acc.selector_definition_facts.extend(
2377 selector_definition_spans.into_iter().enumerate().map(
2378 |(index, (name, byte_span))| ParserIndexSelectorDefinitionFactV0 {
2379 name,
2380 source_order: selector_definition_source_order_offset + index,
2381 byte_span,
2382 range: source_range_for_byte_span(source, byte_span),
2383 nested_safety_kind: nested_safety_kind_label(
2384 selector_facts.nested_safety,
2385 ),
2386 bem_suffix_parent_name: match selector_facts.nested_safety {
2387 NestedSafetyKind::BemSuffixSafe => {
2388 parent_branches.first().map(|parent| parent.name.clone())
2389 }
2390 NestedSafetyKind::Flat | NestedSafetyKind::NestedUnsafe => None,
2391 },
2392 under_media: wrapper_ctx.under_media,
2393 under_supports: wrapper_ctx.under_supports,
2394 under_layer: wrapper_ctx.under_layer,
2395 },
2396 ),
2397 );
2398 acc.bem_suffix_count += selector_facts.bem_suffix_count;
2399 increment_nested_safety_count(
2400 &mut acc.nested_safety_counts,
2401 selector_facts.nested_safety,
2402 resolved.len(),
2403 );
2404 acc.selector_names.extend(resolved.iter().cloned());
2405 let composes_facts = collect_rule_composes_facts(&node.children);
2406 if composes_facts.local_class_name_count > 0
2407 || composes_facts.imported_class_name_count > 0
2408 || composes_facts.global_class_name_count > 0
2409 {
2410 acc.selectors_with_composes_names
2411 .extend(resolved.iter().cloned());
2412 let selector_multiplier = resolved.len();
2413 if composes_facts.local_class_name_count > 0 {
2414 acc.local_composes_selector_names
2415 .extend(resolved.iter().cloned());
2416 acc.local_composes_class_name_count +=
2417 composes_facts.local_class_name_count * selector_multiplier;
2418 }
2419 if composes_facts.imported_class_name_count > 0 {
2420 acc.imported_composes_selector_names
2421 .extend(resolved.iter().cloned());
2422 acc.imported_composes_class_name_count +=
2423 composes_facts.imported_class_name_count * selector_multiplier;
2424 acc.composes_import_sources.extend(
2425 composes_facts.imported_sources.iter().flat_map(|source| {
2426 std::iter::repeat_n(source.clone(), selector_multiplier)
2427 }),
2428 );
2429 }
2430 if composes_facts.global_class_name_count > 0 {
2431 acc.global_composes_selector_names
2432 .extend(resolved.iter().cloned());
2433 acc.global_composes_class_name_count +=
2434 composes_facts.global_class_name_count * selector_multiplier;
2435 }
2436 acc.composes_class_name_count += (composes_facts.local_class_name_count
2437 + composes_facts.imported_class_name_count
2438 + composes_facts.global_class_name_count)
2439 * selector_multiplier;
2440 }
2441 match selector_facts.nested_safety {
2442 NestedSafetyKind::BemSuffixSafe => {
2443 acc.bem_suffix_safe_selector_names
2444 .extend(resolved.iter().cloned());
2445 if let Some(parent) = parent_branches.first() {
2446 acc.bem_suffix_parent_names.push(parent.name.clone());
2447 }
2448 }
2449 NestedSafetyKind::NestedUnsafe => {
2450 acc.nested_unsafe_selector_names
2451 .extend(resolved.iter().cloned());
2452 }
2453 NestedSafetyKind::Flat => {}
2454 }
2455 next_parent_is_grouped = resolved.len() > 1;
2456 next_parent_branches = resolved_branches;
2457 split_child_branches = true;
2458 }
2459 }
2460 Some(SyntaxNodePayload::AtRule(at_rule)) => match at_rule.kind {
2461 AtRuleKind::Media => {
2462 child_wrapper_ctx.under_media = true;
2463 next_sass_scope = Some(node.span);
2464 }
2465 AtRuleKind::Supports => {
2466 child_wrapper_ctx.under_supports = true;
2467 next_sass_scope = Some(node.span);
2468 }
2469 AtRuleKind::Layer => {
2470 child_wrapper_ctx.under_layer = true;
2471 next_sass_scope = Some(node.span);
2472 }
2473 AtRuleKind::AtRoot
2474 | AtRuleKind::Mixin
2475 | AtRuleKind::Function
2476 | AtRuleKind::Generic => {
2477 next_sass_scope = Some(node.span);
2478 }
2479 AtRuleKind::Keyframes
2480 | AtRuleKind::Value
2481 | AtRuleKind::Include
2482 | AtRuleKind::Use
2483 | AtRuleKind::Forward
2484 | AtRuleKind::Import => {}
2485 },
2486 _ => {}
2487 }
2488
2489 match &node.payload {
2490 Some(SyntaxNodePayload::AtRule(at_rule)) => match at_rule.kind {
2491 AtRuleKind::Keyframes if !at_rule.params.is_empty() => {
2492 acc.keyframes_names.push(at_rule.params.clone());
2493 }
2494 AtRuleKind::Keyframes => {}
2495 AtRuleKind::Value => {
2496 if let Some(import_specs) = parse_value_import_specs(&at_rule.params) {
2497 acc.value_import_alias_count += import_specs
2498 .iter()
2499 .filter(|spec| spec.imported_name != spec.local_name)
2500 .count();
2501 acc.value_import_names
2502 .extend(import_specs.iter().map(|spec| spec.local_name.clone()));
2503 acc.value_import_source_by_name
2504 .extend(import_specs.iter().filter_map(|spec| {
2505 spec.from_source
2506 .as_ref()
2507 .map(|source| (spec.local_name.clone(), source.clone()))
2508 }));
2509 acc.value_import_sources
2510 .extend(import_specs.into_iter().filter_map(|spec| spec.from_source));
2511 } else if let Some((name, _)) = parse_local_value_decl_parts(&at_rule.params) {
2512 acc.value_decl_names.push(name.to_string());
2513 }
2514 }
2515 AtRuleKind::Mixin => {
2516 let params_span = at_rule_params_span(source, node, at_rule);
2517 if let Some(name_span) =
2518 parse_sass_callable_name_with_span(&at_rule.params, params_span.start)
2519 {
2520 acc.sass_mixin_decl_names.push(name_span.name.clone());
2521 acc.sass_symbol_decl_facts
2522 .push(ParserIndexSassSymbolDeclFactV0 {
2523 symbol_kind: "mixin",
2524 name: name_span.name,
2525 role: "declaration",
2526 byte_span: name_span.byte_span,
2527 range: source_range_for_byte_span(source, name_span.byte_span),
2528 });
2529 }
2530 let parameter_names = parse_sass_parameter_names(&at_rule.params);
2531 acc.sass_variable_decl_facts
2532 .extend(parameter_names.iter().map(|name| SassVariableDeclFact {
2533 name: name.clone(),
2534 scope: SassVariableScope::Span(node.span),
2535 }));
2536 acc.sass_variable_parameter_names.extend(parameter_names);
2537 }
2538 AtRuleKind::Include => {
2539 if let Some(name_span) = parse_sass_callable_name_with_span(
2540 &at_rule.params,
2541 at_rule_params_span(source, node, at_rule).start,
2542 ) && name_span.namespace.is_none()
2543 {
2544 acc.sass_mixin_include_names.push(name_span.name);
2545 }
2546 }
2547 AtRuleKind::Function => {
2548 let params_span = at_rule_params_span(source, node, at_rule);
2549 if let Some(name_span) =
2550 parse_sass_callable_name_with_span(&at_rule.params, params_span.start)
2551 {
2552 acc.sass_function_decl_names.push(name_span.name.clone());
2553 acc.sass_symbol_decl_facts
2554 .push(ParserIndexSassSymbolDeclFactV0 {
2555 symbol_kind: "function",
2556 name: name_span.name,
2557 role: "declaration",
2558 byte_span: name_span.byte_span,
2559 range: source_range_for_byte_span(source, name_span.byte_span),
2560 });
2561 }
2562 let parameter_names = parse_sass_parameter_names(&at_rule.params);
2563 acc.sass_variable_decl_facts
2564 .extend(parameter_names.iter().map(|name| SassVariableDeclFact {
2565 name: name.clone(),
2566 scope: SassVariableScope::Span(node.span),
2567 }));
2568 acc.sass_variable_parameter_names.extend(parameter_names);
2569 }
2570 AtRuleKind::Use => {
2571 acc.sass_module_use_sources
2572 .extend(parse_sass_module_sources(&at_rule.params));
2573 acc.sass_module_use_edges
2574 .extend(parse_sass_module_use_edges(&at_rule.params));
2575 }
2576 AtRuleKind::Forward => {
2577 acc.sass_module_forward_sources
2578 .extend(parse_sass_module_sources(&at_rule.params));
2579 }
2580 AtRuleKind::Import => {
2581 acc.sass_module_import_sources
2582 .extend(parse_sass_module_sources(&at_rule.params));
2583 acc.sass_module_use_sources
2584 .extend(parse_sass_module_sources(&at_rule.params));
2585 acc.sass_module_use_edges
2586 .extend(parse_sass_module_import_use_edges(&at_rule.params));
2587 }
2588 _ => {}
2589 },
2590 Some(SyntaxNodePayload::Declaration(declaration)) => {
2591 if is_css_custom_property_name(&declaration.property) {
2592 acc.custom_property_decl_names
2593 .push(declaration.property.clone());
2594 if wrapper_ctx.under_media {
2595 acc.custom_property_decl_names_under_media
2596 .push(declaration.property.clone());
2597 }
2598 if wrapper_ctx.under_supports {
2599 acc.custom_property_decl_names_under_supports
2600 .push(declaration.property.clone());
2601 }
2602 if wrapper_ctx.under_layer {
2603 acc.custom_property_decl_names_under_layer
2604 .push(declaration.property.clone());
2605 }
2606 }
2607 if let Some(name) = parse_sass_variable_decl_name(&declaration.property) {
2608 let byte_span = ParserByteSpanV0 {
2609 start: node.span.start + 1,
2610 end: node.span.start + declaration.property.len(),
2611 };
2612 acc.sass_variable_decl_facts.push(SassVariableDeclFact {
2613 name: name.clone(),
2614 scope: current_sass_scope
2615 .map(SassVariableScope::Span)
2616 .unwrap_or(SassVariableScope::File),
2617 });
2618 acc.sass_symbol_decl_facts
2619 .push(ParserIndexSassSymbolDeclFactV0 {
2620 symbol_kind: "variable",
2621 name: name.clone(),
2622 role: "declaration",
2623 byte_span,
2624 range: source_range_for_byte_span(source, byte_span),
2625 });
2626 acc.sass_variable_decl_names.push(name);
2627 }
2628 }
2629 _ => {}
2630 }
2631 if split_child_branches {
2632 for parent_branch in &next_parent_branches {
2633 collect_index_names(
2634 source,
2635 &node.children,
2636 acc,
2637 std::slice::from_ref(parent_branch),
2638 next_parent_is_grouped,
2639 next_sass_scope,
2640 child_wrapper_ctx,
2641 );
2642 }
2643 } else {
2644 collect_index_names(
2645 source,
2646 &node.children,
2647 acc,
2648 &next_parent_branches,
2649 next_parent_is_grouped,
2650 next_sass_scope,
2651 child_wrapper_ctx,
2652 );
2653 }
2654 }
2655}
2656
2657fn custom_property_decl_name_spans_in_rule(
2658 children: &[SyntaxNode],
2659 source: &str,
2660) -> Vec<CustomPropertyDeclNameSpan> {
2661 children
2662 .iter()
2663 .filter_map(|child| match &child.payload {
2664 Some(SyntaxNodePayload::Declaration(declaration))
2665 if is_css_custom_property_name(&declaration.property) =>
2666 {
2667 let byte_span = ParserByteSpanV0 {
2668 start: child.span.start,
2669 end: child.span.start + declaration.property.len(),
2670 };
2671 Some(CustomPropertyDeclNameSpan {
2672 name: declaration.property.clone(),
2673 byte_span,
2674 range: source_range_for_byte_span(source, byte_span),
2675 })
2676 }
2677 _ => None,
2678 })
2679 .collect()
2680}
2681
2682fn selector_contexts_for_rule(
2683 rule: &RulePayload,
2684 resolved_branches: &[ResolvedSelectorBranch],
2685) -> Vec<String> {
2686 let mut contexts: Vec<String> = rule
2687 .selector_groups
2688 .iter()
2689 .map(|group| group.raw.clone())
2690 .chain(
2691 resolved_branches
2692 .iter()
2693 .map(|branch| format!(".{}", branch.name)),
2694 )
2695 .collect();
2696 contexts.sort();
2697 contexts.dedup();
2698 contexts
2699}
2700
2701fn collect_index_refs_and_counts(
2702 nodes: &[SyntaxNode],
2703 value_ref_ctx: ValueRefContext<'_>,
2704 known_keyframe_names: &BTreeSet<String>,
2705 acc: &mut IndexSummaryAcc,
2706) {
2707 for node in nodes {
2708 match &node.payload {
2709 Some(SyntaxNodePayload::Declaration(declaration)) => {
2710 acc.custom_property_ref_names
2711 .extend(find_css_var_ref_names(&declaration.value));
2712 match classify_declaration_kind(&declaration.property) {
2713 DeclarationKind::Composes => {}
2714 DeclarationKind::Animation => {
2715 acc.animation_ref_names.extend(find_identifier_matches(
2716 &declaration.value,
2717 known_keyframe_names,
2718 ));
2719 extend_value_ref_facts(
2720 acc,
2721 find_identifier_matches(&declaration.value, value_ref_ctx.known),
2722 value_ref_ctx,
2723 ValueRefOrigin::Declaration,
2724 );
2725 }
2726 DeclarationKind::AnimationName => {
2727 acc.animation_name_ref_names.extend(find_identifier_matches(
2728 &declaration.value,
2729 known_keyframe_names,
2730 ));
2731 extend_value_ref_facts(
2732 acc,
2733 find_identifier_matches(&declaration.value, value_ref_ctx.known),
2734 value_ref_ctx,
2735 ValueRefOrigin::Declaration,
2736 );
2737 }
2738 DeclarationKind::Generic => {
2739 extend_value_ref_facts(
2740 acc,
2741 find_identifier_matches(&declaration.value, value_ref_ctx.known),
2742 value_ref_ctx,
2743 ValueRefOrigin::Declaration,
2744 );
2745 }
2746 }
2747 }
2748 Some(SyntaxNodePayload::AtRule(at_rule)) if at_rule.kind == AtRuleKind::Value => {
2749 if let Some((name, value)) = parse_local_value_decl_parts(&at_rule.params) {
2750 let value_refs: Vec<String> =
2751 find_identifier_matches(value, value_ref_ctx.known)
2752 .into_iter()
2753 .filter(|candidate| candidate != name)
2754 .collect();
2755 if value_refs
2756 .iter()
2757 .any(|candidate| value_ref_ctx.local.contains(candidate))
2758 {
2759 acc.value_decl_names_with_local_refs.push(name.to_string());
2760 }
2761 if value_refs
2762 .iter()
2763 .any(|candidate| value_ref_ctx.imported.contains(candidate))
2764 {
2765 acc.value_decl_names_with_imported_refs
2766 .push(name.to_string());
2767 }
2768 extend_value_ref_facts(
2769 acc,
2770 value_refs,
2771 value_ref_ctx,
2772 ValueRefOrigin::ValueDecl,
2773 );
2774 }
2775 }
2776 _ => {}
2777 }
2778 collect_index_refs_and_counts(&node.children, value_ref_ctx, known_keyframe_names, acc);
2779 }
2780}
2781
2782fn collect_sass_ref_facts(
2783 nodes: &[SyntaxNode],
2784 source: &str,
2785 known_function_names: &BTreeSet<String>,
2786 acc: &mut IndexSummaryAcc,
2787) {
2788 for node in nodes {
2789 match &node.payload {
2790 Some(SyntaxNodePayload::Declaration(declaration)) => {
2791 let value_span = declaration_value_span(source, node);
2792 let variable_refs =
2793 find_sass_variable_ref_spans(&declaration.value, value_span.start);
2794 acc.sass_variable_ref_names
2795 .extend(variable_refs.iter().map(|span| span.name.clone()));
2796 acc.sass_variable_ref_facts
2797 .extend(variable_refs.into_iter().map(|span| SassVariableRefFact {
2798 name: span.name,
2799 namespace: span.namespace,
2800 byte_span: span.byte_span,
2801 }));
2802 acc.sass_function_call_names
2803 .extend(find_sass_function_calls(
2804 &declaration.value,
2805 known_function_names,
2806 ));
2807 }
2808 Some(SyntaxNodePayload::AtRule(at_rule)) => match at_rule.kind {
2809 AtRuleKind::Mixin | AtRuleKind::Function => {}
2810 _ => {
2811 let params_span = at_rule_params_span(source, node, at_rule);
2812 let variable_refs =
2813 find_sass_variable_ref_spans(&at_rule.params, params_span.start);
2814 acc.sass_variable_ref_names
2815 .extend(variable_refs.iter().map(|span| span.name.clone()));
2816 acc.sass_variable_ref_facts
2817 .extend(variable_refs.into_iter().map(|span| SassVariableRefFact {
2818 name: span.name,
2819 namespace: span.namespace,
2820 byte_span: span.byte_span,
2821 }));
2822 acc.sass_function_call_names
2823 .extend(find_sass_function_calls(
2824 &at_rule.params,
2825 known_function_names,
2826 ));
2827 }
2828 },
2829 _ => {}
2830 }
2831 collect_sass_ref_facts(&node.children, source, known_function_names, acc);
2832 }
2833}
2834
2835fn summarize_sass_same_file_resolution(
2836 acc: &IndexSummaryAcc,
2837) -> ParserIndexSassSameFileResolutionFactsV0 {
2838 let mixin_targets: BTreeSet<&str> = acc
2839 .sass_mixin_decl_names
2840 .iter()
2841 .map(String::as_str)
2842 .collect();
2843 let function_targets: BTreeSet<&str> = acc
2844 .sass_function_decl_names
2845 .iter()
2846 .map(String::as_str)
2847 .collect();
2848
2849 let mut resolved_variable_ref_names = BTreeSet::new();
2850 let mut unresolved_variable_ref_names = BTreeSet::new();
2851 for fact in &acc.sass_variable_ref_facts {
2852 if fact.namespace.is_some() {
2853 continue;
2854 }
2855 if resolve_sass_variable_ref(&fact.name, fact.byte_span, &acc.sass_variable_decl_facts) {
2856 resolved_variable_ref_names.insert(fact.name.clone());
2857 } else {
2858 unresolved_variable_ref_names.insert(fact.name.clone());
2859 }
2860 }
2861
2862 ParserIndexSassSameFileResolutionFactsV0 {
2863 resolved_variable_ref_names: resolved_variable_ref_names.into_iter().collect(),
2864 unresolved_variable_ref_names: unresolved_variable_ref_names.into_iter().collect(),
2865 resolved_mixin_include_names: names_matching(&acc.sass_mixin_include_names, &mixin_targets),
2866 unresolved_mixin_include_names: names_not_matching(
2867 &acc.sass_mixin_include_names,
2868 &mixin_targets,
2869 ),
2870 resolved_function_call_names: names_matching(
2871 &acc.sass_function_call_names,
2872 &function_targets,
2873 ),
2874 }
2875}
2876
2877fn names_matching(names: &[String], targets: &BTreeSet<&str>) -> Vec<String> {
2878 names
2879 .iter()
2880 .filter(|name| targets.contains(name.as_str()))
2881 .cloned()
2882 .collect()
2883}
2884
2885fn names_not_matching(names: &[String], targets: &BTreeSet<&str>) -> Vec<String> {
2886 names
2887 .iter()
2888 .filter(|name| !targets.contains(name.as_str()))
2889 .cloned()
2890 .collect()
2891}
2892
2893fn extend_value_ref_facts(
2894 acc: &mut IndexSummaryAcc,
2895 value_refs: Vec<String>,
2896 value_ref_ctx: ValueRefContext<'_>,
2897 origin: ValueRefOrigin,
2898) {
2899 acc.value_ref_names.extend(value_refs.iter().cloned());
2900
2901 let local_refs: Vec<String> = value_refs
2902 .iter()
2903 .filter(|name| value_ref_ctx.local.contains(*name))
2904 .cloned()
2905 .collect();
2906 acc.local_value_ref_names.extend(local_refs);
2907
2908 let imported_refs: Vec<String> = value_refs
2909 .iter()
2910 .filter(|name| value_ref_ctx.imported.contains(*name))
2911 .cloned()
2912 .collect();
2913 acc.imported_value_ref_names
2914 .extend(imported_refs.iter().cloned());
2915
2916 let imported_ref_sources: Vec<String> = imported_refs
2917 .iter()
2918 .filter_map(|name| acc.value_import_source_by_name.get(name))
2919 .cloned()
2920 .collect();
2921 acc.imported_value_ref_sources
2922 .extend(imported_ref_sources.iter().cloned());
2923
2924 match origin {
2925 ValueRefOrigin::Declaration => {
2926 acc.declaration_value_ref_names.extend(value_refs);
2927 acc.declaration_imported_value_ref_sources
2928 .extend(imported_ref_sources);
2929 }
2930 ValueRefOrigin::ValueDecl => {
2931 acc.value_decl_ref_names.extend(value_refs);
2932 acc.value_decl_imported_value_ref_sources
2933 .extend(imported_ref_sources);
2934 }
2935 }
2936}
2937
2938fn collect_index_selector_attachment_facts(
2939 nodes: &[SyntaxNode],
2940 ctx: SelectorAttachmentContext<'_>,
2941 acc: &mut IndexSummaryAcc,
2942 parent_branches: &[ResolvedSelectorBranch],
2943) {
2944 collect_index_selector_attachment_facts_with_context(
2945 nodes,
2946 ctx,
2947 acc,
2948 parent_branches,
2949 WrapperContext::default(),
2950 );
2951}
2952
2953fn collect_index_selector_attachment_facts_with_context(
2954 nodes: &[SyntaxNode],
2955 ctx: SelectorAttachmentContext<'_>,
2956 acc: &mut IndexSummaryAcc,
2957 parent_branches: &[ResolvedSelectorBranch],
2958 wrapper_ctx: WrapperContext,
2959) {
2960 for node in nodes {
2961 let mut next_parent_branches = parent_branches.to_vec();
2962 let mut split_child_branches = false;
2963 let mut child_wrapper_ctx = wrapper_ctx;
2964 if let Some(SyntaxNodePayload::Rule(rule)) = &node.payload {
2965 let resolved_branches = resolve_rule_selector_branches(rule, parent_branches);
2966 if !resolved_branches.is_empty() {
2967 let resolved: Vec<String> = resolved_branches
2968 .iter()
2969 .map(|branch| branch.name.clone())
2970 .collect();
2971 let ref_facts = collect_rule_reference_facts(
2972 &node.children,
2973 ctx.source,
2974 ctx.value_ref_ctx,
2975 ctx.known_keyframe_names,
2976 ctx.sass_ref_ctx,
2977 );
2978 if ref_facts.has_value_refs {
2979 acc.selectors_with_value_refs_names
2980 .extend(resolved.iter().cloned());
2981 if wrapper_ctx.under_media {
2982 acc.selectors_with_value_refs_under_media_names
2983 .extend(resolved.iter().cloned());
2984 }
2985 if wrapper_ctx.under_supports {
2986 acc.selectors_with_value_refs_under_supports_names
2987 .extend(resolved.iter().cloned());
2988 }
2989 if wrapper_ctx.under_layer {
2990 acc.selectors_with_value_refs_under_layer_names
2991 .extend(resolved.iter().cloned());
2992 }
2993 }
2994 if ref_facts.has_local_value_refs {
2995 acc.selectors_with_local_value_refs_names
2996 .extend(resolved.iter().cloned());
2997 if wrapper_ctx.under_media {
2998 acc.selectors_with_local_value_refs_under_media_names
2999 .extend(resolved.iter().cloned());
3000 }
3001 if wrapper_ctx.under_supports {
3002 acc.selectors_with_local_value_refs_under_supports_names
3003 .extend(resolved.iter().cloned());
3004 }
3005 if wrapper_ctx.under_layer {
3006 acc.selectors_with_local_value_refs_under_layer_names
3007 .extend(resolved.iter().cloned());
3008 }
3009 }
3010 if ref_facts.has_imported_value_refs {
3011 acc.selectors_with_imported_value_refs_names
3012 .extend(resolved.iter().cloned());
3013 if wrapper_ctx.under_media {
3014 acc.selectors_with_imported_value_refs_under_media_names
3015 .extend(resolved.iter().cloned());
3016 }
3017 if wrapper_ctx.under_supports {
3018 acc.selectors_with_imported_value_refs_under_supports_names
3019 .extend(resolved.iter().cloned());
3020 }
3021 if wrapper_ctx.under_layer {
3022 acc.selectors_with_imported_value_refs_under_layer_names
3023 .extend(resolved.iter().cloned());
3024 }
3025 }
3026 if ref_facts.has_animation_refs {
3027 acc.selectors_with_animation_ref_names
3028 .extend(resolved.iter().cloned());
3029 if wrapper_ctx.under_media {
3030 acc.selectors_with_animation_refs_under_media_names
3031 .extend(resolved.iter().cloned());
3032 }
3033 if wrapper_ctx.under_supports {
3034 acc.selectors_with_animation_refs_under_supports_names
3035 .extend(resolved.iter().cloned());
3036 }
3037 if wrapper_ctx.under_layer {
3038 acc.selectors_with_animation_refs_under_layer_names
3039 .extend(resolved.iter().cloned());
3040 }
3041 }
3042 if ref_facts.has_animation_name_refs {
3043 acc.selectors_with_animation_name_ref_names
3044 .extend(resolved.iter().cloned());
3045 if wrapper_ctx.under_media {
3046 acc.selectors_with_animation_name_refs_under_media_names
3047 .extend(resolved.iter().cloned());
3048 }
3049 if wrapper_ctx.under_supports {
3050 acc.selectors_with_animation_name_refs_under_supports_names
3051 .extend(resolved.iter().cloned());
3052 }
3053 if wrapper_ctx.under_layer {
3054 acc.selectors_with_animation_name_refs_under_layer_names
3055 .extend(resolved.iter().cloned());
3056 }
3057 }
3058 if ref_facts.has_custom_property_refs {
3059 let selector_contexts = selector_contexts_for_rule(rule, &resolved_branches);
3060 let source_order_offset = acc.custom_property_ref_facts.len();
3061 acc.selectors_with_custom_property_refs_names
3062 .extend(resolved.iter().cloned());
3063 acc.custom_property_ref_facts.extend(
3064 ref_facts
3065 .custom_property_ref_names
3066 .iter()
3067 .cloned()
3068 .enumerate()
3069 .map(|(index, name)| ParserIndexCustomPropertyRefFactV0 {
3070 name,
3071 source_order: source_order_offset + index,
3072 selector_contexts: selector_contexts.clone(),
3073 under_media: wrapper_ctx.under_media,
3074 under_supports: wrapper_ctx.under_supports,
3075 under_layer: wrapper_ctx.under_layer,
3076 }),
3077 );
3078 if wrapper_ctx.under_media {
3079 acc.selectors_with_custom_property_refs_under_media_names
3080 .extend(resolved.iter().cloned());
3081 }
3082 if wrapper_ctx.under_supports {
3083 acc.selectors_with_custom_property_refs_under_supports_names
3084 .extend(resolved.iter().cloned());
3085 }
3086 if wrapper_ctx.under_layer {
3087 acc.selectors_with_custom_property_refs_under_layer_names
3088 .extend(resolved.iter().cloned());
3089 }
3090 }
3091 if ref_facts.has_sass_variable_refs {
3092 acc.sass_selectors_with_variable_refs_names
3093 .extend(resolved.iter().cloned());
3094 }
3095 if ref_facts.has_resolved_sass_variable_refs {
3096 acc.sass_selectors_with_resolved_variable_refs_names
3097 .extend(resolved.iter().cloned());
3098 }
3099 if ref_facts.has_unresolved_sass_variable_refs {
3100 acc.sass_selectors_with_unresolved_variable_refs_names
3101 .extend(resolved.iter().cloned());
3102 }
3103 if ref_facts.has_sass_mixin_includes {
3104 acc.sass_selectors_with_mixin_includes_names
3105 .extend(resolved.iter().cloned());
3106 }
3107 if ref_facts.has_resolved_sass_mixin_includes {
3108 acc.sass_selectors_with_resolved_mixin_includes_names
3109 .extend(resolved.iter().cloned());
3110 }
3111 if ref_facts.has_unresolved_sass_mixin_includes {
3112 acc.sass_selectors_with_unresolved_mixin_includes_names
3113 .extend(resolved.iter().cloned());
3114 }
3115 if ref_facts.has_sass_function_calls {
3116 acc.sass_selectors_with_function_calls_names
3117 .extend(resolved.iter().cloned());
3118 }
3119 for selector_name in &resolved {
3120 acc.sass_selector_symbol_facts
3121 .extend(ref_facts.sass_symbol_facts.iter().map(|fact| {
3122 ParserIndexSassSelectorSymbolFactV0 {
3123 selector_name: selector_name.clone(),
3124 symbol_kind: fact.symbol_kind,
3125 name: fact.name.clone(),
3126 namespace: fact.namespace.clone(),
3127 role: fact.role,
3128 resolution: fact.resolution,
3129 byte_span: fact.byte_span,
3130 range: source_range_for_byte_span(ctx.source, fact.byte_span),
3131 }
3132 }));
3133 }
3134 let composes_facts = collect_rule_composes_facts(&node.children);
3135 if composes_facts.local_class_name_count > 0
3136 || composes_facts.imported_class_name_count > 0
3137 || composes_facts.global_class_name_count > 0
3138 {
3139 if wrapper_ctx.under_media {
3140 acc.selectors_with_composes_under_media_names
3141 .extend(resolved.iter().cloned());
3142 }
3143 if wrapper_ctx.under_supports {
3144 acc.selectors_with_composes_under_supports_names
3145 .extend(resolved.iter().cloned());
3146 }
3147 if wrapper_ctx.under_layer {
3148 acc.selectors_with_composes_under_layer_names
3149 .extend(resolved.iter().cloned());
3150 }
3151 }
3152 if composes_facts.local_class_name_count > 0 {
3153 if wrapper_ctx.under_media {
3154 acc.local_composes_selector_names_under_media
3155 .extend(resolved.iter().cloned());
3156 }
3157 if wrapper_ctx.under_supports {
3158 acc.local_composes_selector_names_under_supports
3159 .extend(resolved.iter().cloned());
3160 }
3161 if wrapper_ctx.under_layer {
3162 acc.local_composes_selector_names_under_layer
3163 .extend(resolved.iter().cloned());
3164 }
3165 }
3166 if composes_facts.imported_class_name_count > 0 {
3167 if wrapper_ctx.under_media {
3168 acc.imported_composes_selector_names_under_media
3169 .extend(resolved.iter().cloned());
3170 acc.composes_import_sources_under_media.extend(
3171 composes_facts.imported_sources.iter().flat_map(|source| {
3172 std::iter::repeat_n(source.clone(), resolved.len())
3173 }),
3174 );
3175 }
3176 if wrapper_ctx.under_supports {
3177 acc.imported_composes_selector_names_under_supports
3178 .extend(resolved.iter().cloned());
3179 acc.composes_import_sources_under_supports.extend(
3180 composes_facts.imported_sources.iter().flat_map(|source| {
3181 std::iter::repeat_n(source.clone(), resolved.len())
3182 }),
3183 );
3184 }
3185 if wrapper_ctx.under_layer {
3186 acc.imported_composes_selector_names_under_layer
3187 .extend(resolved.iter().cloned());
3188 acc.composes_import_sources_under_layer.extend(
3189 composes_facts.imported_sources.iter().flat_map(|source| {
3190 std::iter::repeat_n(source.clone(), resolved.len())
3191 }),
3192 );
3193 }
3194 }
3195 if composes_facts.global_class_name_count > 0 {
3196 if wrapper_ctx.under_media {
3197 acc.global_composes_selector_names_under_media
3198 .extend(resolved.iter().cloned());
3199 }
3200 if wrapper_ctx.under_supports {
3201 acc.global_composes_selector_names_under_supports
3202 .extend(resolved.iter().cloned());
3203 }
3204 if wrapper_ctx.under_layer {
3205 acc.global_composes_selector_names_under_layer
3206 .extend(resolved.iter().cloned());
3207 }
3208 }
3209 if wrapper_ctx.under_media {
3210 acc.selectors_under_media_names
3211 .extend(resolved.iter().cloned());
3212 }
3213 if wrapper_ctx.under_supports {
3214 acc.selectors_under_supports_names
3215 .extend(resolved.iter().cloned());
3216 }
3217 if wrapper_ctx.under_layer {
3218 acc.selectors_under_layer_names
3219 .extend(resolved.iter().cloned());
3220 }
3221 next_parent_branches = resolved_branches;
3222 split_child_branches = true;
3223 }
3224 } else if let Some(SyntaxNodePayload::AtRule(at_rule)) = &node.payload {
3225 if at_rule.kind == AtRuleKind::Keyframes && !at_rule.params.is_empty() {
3226 if wrapper_ctx.under_media {
3227 acc.keyframes_names_under_media.push(at_rule.params.clone());
3228 }
3229 if wrapper_ctx.under_supports {
3230 acc.keyframes_names_under_supports
3231 .push(at_rule.params.clone());
3232 }
3233 if wrapper_ctx.under_layer {
3234 acc.keyframes_names_under_layer.push(at_rule.params.clone());
3235 }
3236 }
3237 match at_rule.kind {
3238 AtRuleKind::Media => child_wrapper_ctx.under_media = true,
3239 AtRuleKind::Supports => child_wrapper_ctx.under_supports = true,
3240 AtRuleKind::Layer => child_wrapper_ctx.under_layer = true,
3241 _ => {}
3242 }
3243 }
3244
3245 if split_child_branches {
3246 for parent_branch in &next_parent_branches {
3247 collect_index_selector_attachment_facts_with_context(
3248 &node.children,
3249 ctx,
3250 acc,
3251 std::slice::from_ref(parent_branch),
3252 child_wrapper_ctx,
3253 );
3254 }
3255 } else {
3256 collect_index_selector_attachment_facts_with_context(
3257 &node.children,
3258 ctx,
3259 acc,
3260 &next_parent_branches,
3261 child_wrapper_ctx,
3262 );
3263 }
3264 }
3265}
3266
3267fn parse_local_value_decl_parts(params: &str) -> Option<(&str, &str)> {
3268 if params.contains(" from ") {
3269 return None;
3270 }
3271 let (name, value) = params.split_once(':')?;
3272 let trimmed_name = name.trim();
3273 let trimmed_value = value.trim();
3274 if trimmed_name.is_empty() || trimmed_value.is_empty() {
3275 return None;
3276 }
3277 Some((trimmed_name, trimmed_value))
3278}
3279
3280struct ValueImportSpec {
3281 imported_name: String,
3282 local_name: String,
3283 from_source: Option<String>,
3284}
3285
3286fn parse_value_import_specs(params: &str) -> Option<Vec<ValueImportSpec>> {
3287 let (raw_specs, raw_source) = params.split_once(" from ")?;
3288 let from_source = parse_quoted_import_source(raw_source);
3289 let mut specs = Vec::new();
3290 for raw_spec in raw_specs.split(',') {
3291 let trimmed = raw_spec.trim();
3292 if trimmed.is_empty() {
3293 continue;
3294 }
3295 let imported_name = trimmed
3296 .split_once(" as ")
3297 .map(|(imported, _)| imported.trim())
3298 .unwrap_or(trimmed);
3299 let local_name = trimmed
3300 .split_once(" as ")
3301 .map(|(_, local)| local.trim())
3302 .unwrap_or(trimmed);
3303 if !imported_name.is_empty() && !local_name.is_empty() {
3304 specs.push(ValueImportSpec {
3305 imported_name: imported_name.to_string(),
3306 local_name: local_name.to_string(),
3307 from_source: from_source.clone(),
3308 });
3309 }
3310 }
3311 (!specs.is_empty()).then_some(specs)
3312}
3313
3314fn parse_composes_spec(value: &str) -> Option<ComposesSpec> {
3315 let head = value
3316 .split_once(" from ")
3317 .map(|(left, _)| left)
3318 .unwrap_or(value);
3319 let class_names: Vec<String> = head
3320 .split_whitespace()
3321 .filter(|name| !name.is_empty())
3322 .map(ToString::to_string)
3323 .collect();
3324 if class_names.is_empty() {
3325 return None;
3326 }
3327 let from_source = value
3328 .split_once(" from ")
3329 .and_then(|(_, source)| parse_quoted_import_source(source));
3330 let kind = match value.split_once(" from ").map(|(_, source)| source.trim()) {
3331 Some("global") => ComposesKind::Global,
3332 Some(_) => ComposesKind::Imported,
3333 None => ComposesKind::Local,
3334 };
3335 Some(ComposesSpec {
3336 class_names,
3337 kind,
3338 from_source,
3339 })
3340}
3341
3342fn parse_quoted_import_source(raw: &str) -> Option<String> {
3343 let trimmed = raw.trim();
3344 if trimmed.len() < 2 {
3345 return None;
3346 }
3347 let quote = trimmed.chars().next()?;
3348 if !matches!(quote, '"' | '\'') || !trimmed.ends_with(quote) {
3349 return None;
3350 }
3351 Some(trimmed[1..trimmed.len() - 1].to_string())
3352}
3353
3354fn parse_sass_variable_decl_name(property: &str) -> Option<String> {
3355 let name = property.trim().strip_prefix('$')?;
3356 (!name.is_empty() && name.chars().all(is_sass_ident_continue)).then(|| name.to_string())
3357}
3358
3359fn parse_sass_callable_name_with_span(params: &str, base_start: usize) -> Option<SassNameSpan> {
3360 let trimmed_start = params.find(|ch: char| !ch.is_whitespace())?;
3361 let trimmed = ¶ms[trimmed_start..];
3362 let end = trimmed
3363 .find(|ch: char| ch.is_whitespace() || ch == '(')
3364 .unwrap_or(trimmed.len());
3365 let raw_name = &trimmed[..end];
3366 let (namespace, name, name_offset) = if let Some((namespace, name)) = raw_name.split_once('.') {
3367 if !is_valid_sass_namespace(namespace) {
3368 return None;
3369 }
3370 (Some(namespace.to_string()), name, namespace.len() + 1)
3371 } else {
3372 (None, raw_name, 0)
3373 };
3374 (!name.is_empty() && name.chars().all(is_sass_ident_continue)).then(|| SassNameSpan {
3375 name: name.to_string(),
3376 namespace,
3377 byte_span: ParserByteSpanV0 {
3378 start: base_start + trimmed_start + name_offset,
3379 end: base_start + trimmed_start + end,
3380 },
3381 })
3382}
3383
3384fn parse_sass_parameter_names(params: &str) -> Vec<String> {
3385 let Some(open_index) = params.find('(') else {
3386 return Vec::new();
3387 };
3388 let close_index = params[open_index + 1..]
3389 .find(')')
3390 .map(|index| open_index + 1 + index)
3391 .unwrap_or(params.len());
3392 find_sass_variable_refs(¶ms[open_index + 1..close_index])
3393}
3394
3395fn parse_sass_module_sources(params: &str) -> Vec<String> {
3396 let mut sources = Vec::new();
3397 let chars: Vec<char> = params.chars().collect();
3398 let mut index = 0usize;
3399
3400 while index < chars.len() {
3401 let quote = chars[index];
3402 if !matches!(quote, '"' | '\'') {
3403 index += 1;
3404 continue;
3405 }
3406 index += 1;
3407 let start = index;
3408 while index < chars.len() {
3409 if chars[index] == '\\' {
3410 index = (index + 2).min(chars.len());
3411 continue;
3412 }
3413 if chars[index] == quote {
3414 break;
3415 }
3416 index += 1;
3417 }
3418 if start < index {
3419 sources.push(chars[start..index].iter().collect());
3420 }
3421 index += usize::from(index < chars.len());
3422 }
3423
3424 sources
3425}
3426
3427fn parse_sass_module_use_edges(params: &str) -> Vec<ParserIndexSassModuleUseFactV0> {
3428 let alias = parse_sass_use_alias(params);
3429 parse_sass_module_sources(params)
3430 .into_iter()
3431 .map(|source| match alias.as_deref() {
3432 Some("*") => ParserIndexSassModuleUseFactV0 {
3433 source,
3434 namespace_kind: "wildcard",
3435 namespace: None,
3436 },
3437 Some(namespace) if is_valid_sass_namespace(namespace) => {
3438 ParserIndexSassModuleUseFactV0 {
3439 source,
3440 namespace_kind: "alias",
3441 namespace: Some(namespace.to_string()),
3442 }
3443 }
3444 _ => ParserIndexSassModuleUseFactV0 {
3445 namespace: default_sass_namespace_for_source(&source),
3446 source,
3447 namespace_kind: "default",
3448 },
3449 })
3450 .collect()
3451}
3452
3453fn parse_sass_module_import_use_edges(params: &str) -> Vec<ParserIndexSassModuleUseFactV0> {
3454 parse_sass_module_sources(params)
3455 .into_iter()
3456 .map(|source| ParserIndexSassModuleUseFactV0 {
3457 source,
3458 namespace_kind: "wildcard",
3459 namespace: None,
3460 })
3461 .collect()
3462}
3463
3464fn parse_sass_use_alias(params: &str) -> Option<String> {
3465 let chars: Vec<char> = params.chars().collect();
3466 let mut tokens = Vec::new();
3467 let mut current = String::new();
3468 let mut index = 0usize;
3469 let mut quote: Option<char> = None;
3470
3471 while index < chars.len() {
3472 let ch = chars[index];
3473 if let Some(active_quote) = quote {
3474 if ch == '\\' && index + 1 < chars.len() {
3475 index += 2;
3476 continue;
3477 }
3478 if ch == active_quote {
3479 quote = None;
3480 }
3481 index += 1;
3482 continue;
3483 }
3484
3485 if ch == '"' || ch == '\'' {
3486 if !current.is_empty() {
3487 tokens.push(std::mem::take(&mut current));
3488 }
3489 quote = Some(ch);
3490 index += 1;
3491 continue;
3492 }
3493
3494 if ch.is_whitespace() || ch == ';' || ch == ',' {
3495 if !current.is_empty() {
3496 tokens.push(std::mem::take(&mut current));
3497 }
3498 index += 1;
3499 continue;
3500 }
3501
3502 current.push(ch);
3503 index += 1;
3504 }
3505
3506 if !current.is_empty() {
3507 tokens.push(current);
3508 }
3509
3510 tokens
3511 .windows(2)
3512 .find_map(|window| (window[0].eq_ignore_ascii_case("as")).then(|| window[1].clone()))
3513}
3514
3515fn default_sass_namespace_for_source(source: &str) -> Option<String> {
3516 let clean = source
3517 .split(['?', '#'])
3518 .next()
3519 .unwrap_or(source)
3520 .trim_end_matches('/');
3521 let segment = clean.rsplit('/').next().unwrap_or(clean);
3522 let package_segment = segment.rsplit(':').next().unwrap_or(segment);
3523 let stem = package_segment
3524 .rsplit_once('.')
3525 .map(|(stem, _)| stem)
3526 .unwrap_or(package_segment);
3527 let stem = stem.strip_prefix('_').unwrap_or(stem);
3528
3529 is_valid_sass_namespace(stem).then(|| stem.to_string())
3530}
3531
3532fn is_valid_sass_namespace(value: &str) -> bool {
3533 let mut chars = value.chars();
3534 let Some(first) = chars.next() else {
3535 return false;
3536 };
3537 is_sass_ident_start(first) && chars.all(is_sass_ident_continue)
3538}
3539
3540fn find_sass_variable_refs(raw: &str) -> Vec<String> {
3541 find_sass_variable_ref_spans(raw, 0)
3542 .into_iter()
3543 .map(|span| span.name)
3544 .collect()
3545}
3546
3547fn find_sass_variable_ref_spans(raw: &str, base_start: usize) -> Vec<SassNameSpan> {
3548 let mut refs = Vec::new();
3549 let mut chars = raw.char_indices().peekable();
3550 let mut quote: Option<char> = None;
3551
3552 while let Some((byte_index, ch)) = chars.next() {
3553 if let Some(active_quote) = quote {
3554 if ch == '\\' {
3555 chars.next();
3556 continue;
3557 }
3558 if ch == active_quote {
3559 quote = None;
3560 }
3561 continue;
3562 }
3563
3564 if ch == '"' || ch == '\'' {
3565 quote = Some(ch);
3566 continue;
3567 }
3568
3569 if ch == '$' {
3570 let namespace = sass_module_qualified_namespace_before(raw, byte_index);
3571 let name_start = byte_index + ch.len_utf8();
3572 let mut name_end = name_start;
3573 let mut name = String::new();
3574 while let Some(&(next_index, next_ch)) = chars.peek() {
3575 if !is_sass_ident_continue(next_ch) {
3576 break;
3577 }
3578 name.push(next_ch);
3579 name_end = next_index + next_ch.len_utf8();
3580 chars.next();
3581 }
3582 if !name.is_empty() {
3583 refs.push(SassNameSpan {
3584 name,
3585 namespace,
3586 byte_span: ParserByteSpanV0 {
3587 start: base_start + byte_index,
3588 end: base_start + name_end,
3589 },
3590 });
3591 }
3592 }
3593 }
3594
3595 refs
3596}
3597
3598fn find_sass_function_calls(raw: &str, known_function_names: &BTreeSet<String>) -> Vec<String> {
3599 find_sass_function_call_spans(raw, 0, known_function_names)
3600 .into_iter()
3601 .filter(|span| span.namespace.is_none())
3602 .map(|span| span.name)
3603 .collect()
3604}
3605
3606fn find_sass_function_call_spans(
3607 raw: &str,
3608 base_start: usize,
3609 known_function_names: &BTreeSet<String>,
3610) -> Vec<SassNameSpan> {
3611 let mut calls = Vec::new();
3612 let mut chars = raw.char_indices().peekable();
3613 let mut quote: Option<char> = None;
3614
3615 while let Some((byte_index, ch)) = chars.next() {
3616 if let Some(active_quote) = quote {
3617 if ch == '\\' {
3618 chars.next();
3619 continue;
3620 }
3621 if ch == active_quote {
3622 quote = None;
3623 }
3624 continue;
3625 }
3626
3627 if ch == '"' || ch == '\'' {
3628 quote = Some(ch);
3629 continue;
3630 }
3631
3632 if is_sass_ident_start(ch) {
3633 let mut name = String::from(ch);
3634 let name_start = byte_index;
3635 let mut name_end = byte_index + ch.len_utf8();
3636 while let Some(&(next_index, next_ch)) = chars.peek() {
3637 if !is_sass_ident_continue(next_ch) {
3638 break;
3639 }
3640 name.push(next_ch);
3641 name_end = next_index + next_ch.len_utf8();
3642 chars.next();
3643 }
3644 let mut namespace = None;
3645 if matches!(chars.peek(), Some(&(_, '.'))) {
3646 chars.next();
3647 let Some(&(member_start, member_first)) = chars.peek() else {
3648 continue;
3649 };
3650 if !is_sass_ident_start(member_first) {
3651 continue;
3652 }
3653 namespace = Some(name);
3654 name = String::new();
3655 name_end = member_start;
3656 while let Some(&(next_index, next_ch)) = chars.peek() {
3657 if !is_sass_ident_continue(next_ch) {
3658 break;
3659 }
3660 name.push(next_ch);
3661 name_end = next_index + next_ch.len_utf8();
3662 chars.next();
3663 }
3664 }
3665 while let Some(&(_, next_ch)) = chars.peek() {
3666 if !next_ch.is_whitespace() {
3667 break;
3668 }
3669 chars.next();
3670 }
3671 if matches!(chars.peek(), Some(&(_, '(')))
3672 && (namespace.is_some() || known_function_names.contains(&name))
3673 {
3674 let span_start = namespace
3675 .as_ref()
3676 .map(|namespace| name_start + namespace.len() + 1)
3677 .unwrap_or(name_start);
3678 calls.push(SassNameSpan {
3679 name,
3680 namespace,
3681 byte_span: ParserByteSpanV0 {
3682 start: base_start + span_start,
3683 end: base_start + name_end,
3684 },
3685 });
3686 }
3687 }
3688 }
3689
3690 calls
3691}
3692
3693fn sass_module_qualified_namespace_before(raw: &str, start: usize) -> Option<String> {
3694 if start <= 1 || !raw[..start].ends_with('.') {
3695 return None;
3696 }
3697 let namespace = raw[..start - 1]
3698 .rsplit(|ch: char| !is_sass_ident_continue(ch))
3699 .next()
3700 .unwrap_or("");
3701 is_valid_sass_namespace(namespace).then(|| namespace.to_string())
3702}
3703
3704fn find_identifier_matches(raw: &str, known_names: &BTreeSet<String>) -> Vec<String> {
3705 let mut matches = Vec::new();
3706 let chars: Vec<char> = raw.chars().collect();
3707 let mut index = 0usize;
3708 let mut quote: Option<char> = None;
3709 let mut identifier_start: Option<usize> = None;
3710
3711 let flush_identifier =
3712 |end: usize, identifier_start: &mut Option<usize>, matches: &mut Vec<String>| {
3713 if let Some(start) = *identifier_start {
3714 let candidate: String = chars[start..end].iter().collect();
3715 if known_names.contains(&candidate) {
3716 matches.push(candidate);
3717 }
3718 *identifier_start = None;
3719 }
3720 };
3721
3722 while index < chars.len() {
3723 let ch = chars[index];
3724 if let Some(active_quote) = quote {
3725 if ch == '\\' && index + 1 < chars.len() {
3726 index += 2;
3727 continue;
3728 }
3729 if ch == active_quote {
3730 quote = None;
3731 }
3732 index += 1;
3733 continue;
3734 }
3735
3736 if ch == '"' || ch == '\'' {
3737 flush_identifier(index, &mut identifier_start, &mut matches);
3738 quote = Some(ch);
3739 index += 1;
3740 continue;
3741 }
3742
3743 if is_value_ident_continue(ch) {
3744 if identifier_start.is_none() {
3745 identifier_start = Some(index);
3746 }
3747 index += 1;
3748 continue;
3749 }
3750
3751 flush_identifier(index, &mut identifier_start, &mut matches);
3752 index += 1;
3753 }
3754
3755 flush_identifier(chars.len(), &mut identifier_start, &mut matches);
3756 matches
3757}
3758
3759fn is_value_ident_continue(ch: char) -> bool {
3760 ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-' | '$')
3761}
3762
3763fn find_css_var_ref_names(raw: &str) -> Vec<String> {
3764 let mut refs = Vec::new();
3765 let mut search_start = 0usize;
3766
3767 while let Some(relative_start) = raw[search_start..].find("var(") {
3768 let function_start = search_start + relative_start;
3769 if !is_css_function_boundary(raw, function_start) {
3770 search_start = function_start + "var(".len();
3771 continue;
3772 }
3773
3774 let mut cursor = function_start + "var(".len();
3775 while cursor < raw.len() {
3776 let Some(ch) = raw[cursor..].chars().next() else {
3777 break;
3778 };
3779 if !ch.is_whitespace() {
3780 break;
3781 }
3782 cursor += ch.len_utf8();
3783 }
3784
3785 if !raw[cursor..].starts_with("--") {
3786 search_start = cursor;
3787 continue;
3788 }
3789
3790 let name_start = cursor;
3791 let mut name_end = cursor;
3792 for (relative_index, ch) in raw[name_start..].char_indices() {
3793 let absolute_index = name_start + relative_index;
3794 if relative_index == 0 {
3795 name_end = absolute_index + ch.len_utf8();
3796 continue;
3797 }
3798 if !is_css_custom_property_ident_continue(ch) {
3799 break;
3800 }
3801 name_end = absolute_index + ch.len_utf8();
3802 }
3803
3804 let candidate = &raw[name_start..name_end];
3805 if is_css_custom_property_name(candidate) {
3806 refs.push(candidate.to_string());
3807 }
3808 search_start = name_end.max(function_start + "var(".len());
3809 }
3810
3811 refs
3812}
3813
3814fn is_css_function_boundary(raw: &str, function_start: usize) -> bool {
3815 raw[..function_start]
3816 .chars()
3817 .next_back()
3818 .is_none_or(|ch| !ch.is_ascii_alphanumeric() && ch != '_')
3819}
3820
3821fn is_css_custom_property_name(name: &str) -> bool {
3822 let Some(rest) = name.strip_prefix("--") else {
3823 return false;
3824 };
3825 let mut chars = rest.chars();
3826 let Some(first) = chars.next() else {
3827 return false;
3828 };
3829 is_css_custom_property_ident_start(first) && chars.all(is_css_custom_property_ident_continue)
3830}
3831
3832fn is_css_custom_property_ident_start(ch: char) -> bool {
3833 ch == '_' || ch == '-' || ch.is_ascii_alphabetic() || !ch.is_ascii()
3834}
3835
3836fn is_css_custom_property_ident_continue(ch: char) -> bool {
3837 is_css_custom_property_ident_start(ch) || ch.is_ascii_digit()
3838}
3839
3840fn is_sass_ident_start(ch: char) -> bool {
3841 ch.is_ascii_alphabetic() || ch == '_' || ch == '-' || !ch.is_ascii()
3842}
3843
3844fn is_sass_ident_continue(ch: char) -> bool {
3845 is_sass_ident_start(ch) || ch.is_ascii_digit()
3846}
3847
3848fn extract_simple_selector_name(prelude: &str) -> Option<String> {
3849 let trimmed = prelude.trim();
3850 let rest = trimmed.strip_prefix('.')?;
3851 if rest.is_empty() {
3852 return None;
3853 }
3854 if rest.contains([' ', ',', ':', '&', '#', '[', '>', '+', '~']) {
3855 return None;
3856 }
3857 Some(rest.to_string())
3858}
3859
3860fn resolve_rule_selector_branches(
3861 rule: &RulePayload,
3862 parent_branches: &[ResolvedSelectorBranch],
3863) -> Vec<ResolvedSelectorBranch> {
3864 let mut branches = Vec::new();
3865
3866 for group in &rule.selector_groups {
3867 let resolved = extract_group_selector_branches(group, parent_branches);
3868 if !resolved.is_empty() {
3869 branches.extend(resolved);
3870 } else if let Some(local_names) = extract_local_function_selector_names(&group.raw) {
3871 branches.extend(repeat_names_for_parent_branches(
3872 local_names,
3873 parent_branches,
3874 false,
3875 ));
3876 } else if let Some(name) = extract_simple_selector_name(&group.raw) {
3877 branches.extend(repeat_names_for_parent_branches(
3878 vec![name],
3879 parent_branches,
3880 true,
3881 ));
3882 }
3883 }
3884
3885 branches
3886}
3887
3888fn repeat_names_for_parent_branches(
3889 names: Vec<String>,
3890 parent_branches: &[ResolvedSelectorBranch],
3891 bare_when_top_level_single: bool,
3892) -> Vec<ResolvedSelectorBranch> {
3893 if names.is_empty() {
3894 return Vec::new();
3895 }
3896 if parent_branches.is_empty() {
3897 let bare_suffix_base = bare_when_top_level_single && names.len() == 1;
3898 return names
3899 .into_iter()
3900 .map(|name| ResolvedSelectorBranch {
3901 name,
3902 bare_suffix_base,
3903 })
3904 .collect();
3905 }
3906 if parent_branches.len() == 1 {
3907 return names
3908 .into_iter()
3909 .map(|name| ResolvedSelectorBranch {
3910 name,
3911 bare_suffix_base: false,
3912 })
3913 .collect();
3914 }
3915 let mut repeated = Vec::with_capacity(names.len() * parent_branches.len());
3916 for _ in parent_branches {
3917 repeated.extend(names.iter().cloned().map(|name| ResolvedSelectorBranch {
3918 name,
3919 bare_suffix_base: false,
3920 }));
3921 }
3922 repeated
3923}
3924
3925fn extract_group_selector_branches(
3926 group: &SelectorGroup,
3927 parent_branches: &[ResolvedSelectorBranch],
3928) -> Vec<ResolvedSelectorBranch> {
3929 match group.segments.as_slice() {
3930 [SelectorSegment::ClassName(name)] => {
3931 repeat_names_for_parent_branches(vec![name.clone()], parent_branches, true)
3932 }
3933 [
3934 SelectorSegment::Ampersand,
3935 SelectorSegment::BemSuffix(suffix),
3936 ] => parent_branches
3937 .iter()
3938 .filter(|parent| parent.bare_suffix_base)
3939 .map(|parent| ResolvedSelectorBranch {
3940 name: format!("{}{}", parent.name, suffix),
3941 bare_suffix_base: true,
3942 })
3943 .collect(),
3944 [
3945 SelectorSegment::Ampersand,
3946 SelectorSegment::AmpersandSuffix(suffix),
3947 ] => parent_branches
3948 .iter()
3949 .filter(|parent| parent.bare_suffix_base)
3950 .map(|parent| ResolvedSelectorBranch {
3951 name: format!("{}{}", parent.name, suffix),
3952 bare_suffix_base: true,
3953 })
3954 .collect(),
3955 [SelectorSegment::Ampersand, SelectorSegment::ClassName(name)] => {
3956 repeat_names_for_parent_branches(vec![name.clone()], parent_branches, false)
3957 }
3958 segments => {
3959 let last_combinator = segments
3960 .iter()
3961 .enumerate()
3962 .rev()
3963 .find_map(|(index, segment)| {
3964 matches!(segment, SelectorSegment::Combinator(_)).then_some(index)
3965 });
3966 let tail = last_combinator
3967 .map(|index| &segments[index + 1..])
3968 .unwrap_or(segments);
3969 if let [
3970 SelectorSegment::Ampersand,
3971 SelectorSegment::BemSuffix(suffix) | SelectorSegment::AmpersandSuffix(suffix),
3972 ] = tail
3973 {
3974 return parent_branches
3975 .iter()
3976 .filter(|parent| parent.bare_suffix_base)
3977 .map(|parent| ResolvedSelectorBranch {
3978 name: format!("{}{}", parent.name, suffix),
3979 bare_suffix_base: false,
3980 })
3981 .collect();
3982 }
3983 let names: Vec<String> = tail
3984 .iter()
3985 .filter_map(|segment| match segment {
3986 SelectorSegment::ClassName(name) => Some(name.clone()),
3987 _ => None,
3988 })
3989 .collect();
3990 repeat_names_for_parent_branches(names, parent_branches, false)
3991 }
3992 }
3993}
3994
3995fn extract_local_function_selector_names(raw: &str) -> Option<Vec<String>> {
3996 let trimmed = raw.trim();
3997 let inner = trimmed
3998 .strip_prefix(":local(")
3999 .and_then(|rest| rest.strip_suffix(')'))?;
4000 let mut names = Vec::new();
4001 let chars: Vec<char> = inner.chars().collect();
4002 let mut index = 0usize;
4003
4004 while index < chars.len() {
4005 if chars[index] == '.' {
4006 index += 1;
4007 let start = index;
4008 while index < chars.len() && is_selector_ident_continue(chars[index]) {
4009 index += 1;
4010 }
4011 if start < index {
4012 names.push(chars[start..index].iter().collect());
4013 continue;
4014 }
4015 }
4016 index += 1;
4017 }
4018
4019 (!names.is_empty()).then_some(names)
4020}
4021
4022fn parse_selector_groups(prelude: &str) -> Vec<SelectorGroup> {
4023 let mut groups = Vec::new();
4024 let mut depth_paren = 0usize;
4025 let mut depth_bracket = 0usize;
4026 let mut start = 0usize;
4027
4028 for (index, ch) in prelude.char_indices() {
4029 match ch {
4030 '(' => depth_paren += 1,
4031 ')' => depth_paren = depth_paren.saturating_sub(1),
4032 '[' => depth_bracket += 1,
4033 ']' => depth_bracket = depth_bracket.saturating_sub(1),
4034 ',' if depth_paren == 0 && depth_bracket == 0 => {
4035 let raw = prelude[start..index].trim();
4036 if !raw.is_empty() {
4037 groups.push(SelectorGroup {
4038 raw: raw.to_string(),
4039 segments: parse_selector_segments(raw),
4040 });
4041 }
4042 start = index + ch.len_utf8();
4043 }
4044 _ => {}
4045 }
4046 }
4047
4048 let raw = prelude[start..].trim();
4049 if !raw.is_empty() {
4050 groups.push(SelectorGroup {
4051 raw: raw.to_string(),
4052 segments: parse_selector_segments(raw),
4053 });
4054 }
4055
4056 groups
4057}
4058
4059fn parse_selector_segments(raw: &str) -> Vec<SelectorSegment> {
4060 let chars: Vec<char> = raw.chars().collect();
4061 let mut index = 0usize;
4062 let mut segments = Vec::new();
4063
4064 while index < chars.len() {
4065 match chars[index] {
4066 c if c.is_whitespace() => {
4067 let start = index;
4068 while index < chars.len() && chars[index].is_whitespace() {
4069 index += 1;
4070 }
4071 let next = chars.get(index).copied();
4072 let has_prev = segments.last().is_some();
4073 let prev_is_combinator =
4074 matches!(segments.last(), Some(SelectorSegment::Combinator(_)));
4075 let next_starts_selector = next.is_some_and(|ch| {
4076 matches!(ch, '.' | '&' | ':' | '#' | '*' | '[') || ch.is_ascii_alphabetic()
4077 });
4078 if start > 0 && has_prev && !prev_is_combinator && next_starts_selector {
4079 segments.push(SelectorSegment::Combinator(" ".to_string()));
4080 }
4081 }
4082 '.' => {
4083 index += 1;
4084 let start = index;
4085 while index < chars.len() && is_selector_ident_continue(chars[index]) {
4086 index += 1;
4087 }
4088 if start < index {
4089 segments.push(SelectorSegment::ClassName(
4090 chars[start..index].iter().collect(),
4091 ));
4092 } else {
4093 segments.push(SelectorSegment::Other(".".to_string()));
4094 }
4095 }
4096 '&' => {
4097 segments.push(SelectorSegment::Ampersand);
4098 index += 1;
4099 if index + 1 < chars.len()
4100 && ((chars[index] == '-' && chars[index + 1] == '-')
4101 || (chars[index] == '_' && chars[index + 1] == '_'))
4102 {
4103 let start = index;
4104 index += 2;
4105 while index < chars.len() && is_selector_ident_continue(chars[index]) {
4106 index += 1;
4107 }
4108 segments.push(SelectorSegment::BemSuffix(
4109 chars[start..index].iter().collect(),
4110 ));
4111 } else if index < chars.len()
4112 && !starts_selector_component_after_ampersand(chars[index])
4113 {
4114 let start = index;
4115 while index < chars.len() && is_selector_ident_continue(chars[index]) {
4116 index += 1;
4117 }
4118 if start < index {
4119 segments.push(SelectorSegment::AmpersandSuffix(
4120 chars[start..index].iter().collect(),
4121 ));
4122 }
4123 }
4124 }
4125 ':' => {
4126 index += 1;
4127 let start = index;
4128 while index < chars.len() && is_selector_ident_continue(chars[index]) {
4129 index += 1;
4130 }
4131 segments.push(SelectorSegment::Pseudo(
4132 chars[start..index].iter().collect(),
4133 ));
4134 if index < chars.len() && chars[index] == '(' {
4135 let mut depth = 1usize;
4136 index += 1;
4137 while index < chars.len() && depth > 0 {
4138 match chars[index] {
4139 '(' => depth += 1,
4140 ')' => depth = depth.saturating_sub(1),
4141 '\'' | '"' => {
4142 let quote = chars[index];
4143 index += 1;
4144 while index < chars.len() {
4145 if chars[index] == '\\' {
4146 index += 2;
4147 continue;
4148 }
4149 if chars[index] == quote {
4150 break;
4151 }
4152 index += 1;
4153 }
4154 }
4155 _ => {}
4156 }
4157 index += 1;
4158 }
4159 }
4160 }
4161 '>' | '+' | '~' => {
4162 segments.push(SelectorSegment::Combinator(chars[index].to_string()));
4163 index += 1;
4164 }
4165 _ => {
4166 let start = index;
4167 index += 1;
4168 while index < chars.len()
4169 && !chars[index].is_whitespace()
4170 && !matches!(chars[index], '.' | '&' | ':' | '>' | '+' | '~' | ',')
4171 {
4172 index += 1;
4173 }
4174 segments.push(SelectorSegment::Other(chars[start..index].iter().collect()));
4175 }
4176 }
4177 }
4178
4179 segments
4180}
4181
4182fn is_selector_ident_continue(ch: char) -> bool {
4183 ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-') || !ch.is_ascii()
4184}
4185
4186fn starts_selector_component_after_ampersand(ch: char) -> bool {
4187 matches!(
4188 ch,
4189 ':' | '.' | '[' | '#' | '>' | '+' | '~' | ',' | '*' | ')' | '}' | ']'
4190 ) || ch.is_whitespace()
4191}
4192
4193fn tokenize(language: StyleLanguage, source: &str) -> (Vec<Token>, Vec<ParseDiagnostic>) {
4194 let mut tokens = Vec::new();
4195 let mut diagnostics = Vec::new();
4196 let bytes = source.as_bytes();
4197 let mut i = 0;
4198
4199 while i < bytes.len() {
4200 let start = i;
4201 let byte = bytes[i];
4202
4203 if byte.is_ascii_whitespace() {
4204 i += 1;
4205 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
4206 i += 1;
4207 }
4208 tokens.push(Token {
4209 kind: TokenKind::Whitespace,
4210 span: TextSpan::new(start, i),
4211 });
4212 continue;
4213 }
4214
4215 if language.supports_line_comments() && byte == b'/' && bytes.get(i + 1) == Some(&b'/') {
4216 i += 2;
4217 while i < bytes.len() && bytes[i] != b'\n' {
4218 i += 1;
4219 }
4220 tokens.push(Token {
4221 kind: TokenKind::LineComment,
4222 span: TextSpan::new(start, i),
4223 });
4224 continue;
4225 }
4226
4227 if byte == b'/' && bytes.get(i + 1) == Some(&b'*') {
4228 i += 2;
4229 let mut closed = false;
4230 while i + 1 < bytes.len() {
4231 if bytes[i] == b'*' && bytes[i + 1] == b'/' {
4232 i += 2;
4233 closed = true;
4234 break;
4235 }
4236 i += 1;
4237 }
4238 if !closed {
4239 i = bytes.len();
4240 diagnostics.push(ParseDiagnostic {
4241 message: "unterminated block comment".to_string(),
4242 span: TextSpan::new(start, i),
4243 });
4244 }
4245 tokens.push(Token {
4246 kind: TokenKind::BlockComment,
4247 span: TextSpan::new(start, i),
4248 });
4249 continue;
4250 }
4251
4252 if byte == b'"' || byte == b'\'' {
4253 let quote = byte;
4254 i += 1;
4255 let mut closed = false;
4256 while i < bytes.len() {
4257 if bytes[i] == b'\\' {
4258 i = (i + 2).min(bytes.len());
4259 continue;
4260 }
4261 if bytes[i] == quote {
4262 i += 1;
4263 closed = true;
4264 break;
4265 }
4266 i += 1;
4267 }
4268 if !closed {
4269 diagnostics.push(ParseDiagnostic {
4270 message: "unterminated string literal".to_string(),
4271 span: TextSpan::new(start, i),
4272 });
4273 }
4274 tokens.push(Token {
4275 kind: TokenKind::String,
4276 span: TextSpan::new(start, i),
4277 });
4278 continue;
4279 }
4280
4281 if byte == b'#' && bytes.get(i + 1) == Some(&b'{') {
4282 i += 2;
4283 tokens.push(Token {
4284 kind: TokenKind::InterpolationStart,
4285 span: TextSpan::new(start, i),
4286 });
4287 continue;
4288 }
4289
4290 if is_ident_start(byte) {
4291 i += 1;
4292 while i < bytes.len() && is_ident_continue(bytes[i]) {
4293 i += 1;
4294 }
4295 tokens.push(Token {
4296 kind: TokenKind::Ident,
4297 span: TextSpan::new(start, i),
4298 });
4299 continue;
4300 }
4301
4302 if byte.is_ascii_digit() {
4303 i += 1;
4304 while i < bytes.len() && bytes[i].is_ascii_digit() {
4305 i += 1;
4306 }
4307 tokens.push(Token {
4308 kind: TokenKind::Number,
4309 span: TextSpan::new(start, i),
4310 });
4311 continue;
4312 }
4313
4314 let kind = match byte {
4315 b'.' => TokenKind::Dot,
4316 b'&' => TokenKind::Ampersand,
4317 b'#' => TokenKind::Hash,
4318 b':' => TokenKind::Colon,
4319 b';' => TokenKind::Semicolon,
4320 b',' => TokenKind::Comma,
4321 b'@' => TokenKind::At,
4322 b'{' => TokenKind::OpenBrace,
4323 b'}' => TokenKind::CloseBrace,
4324 b'(' => TokenKind::OpenParen,
4325 b')' => TokenKind::CloseParen,
4326 b'[' => TokenKind::OpenBracket,
4327 b']' => TokenKind::CloseBracket,
4328 _ => TokenKind::Other,
4329 };
4330 i += 1;
4331 tokens.push(Token {
4332 kind,
4333 span: TextSpan::new(start, i),
4334 });
4335 }
4336
4337 (tokens, diagnostics)
4338}
4339
4340fn is_ident_start(byte: u8) -> bool {
4341 byte.is_ascii_alphabetic() || matches!(byte, b'_' | b'-') || byte >= 0x80
4342}
4343
4344fn is_ident_continue(byte: u8) -> bool {
4345 is_ident_start(byte) || byte.is_ascii_digit()
4346}
4347
4348struct Parser<'a> {
4349 source: &'a str,
4350 tokens: &'a [Token],
4351 diagnostics: &'a mut Vec<ParseDiagnostic>,
4352 cursor: usize,
4353}
4354
4355impl<'a> Parser<'a> {
4356 fn new(
4357 source: &'a str,
4358 tokens: &'a [Token],
4359 diagnostics: &'a mut Vec<ParseDiagnostic>,
4360 ) -> Self {
4361 Self {
4362 source,
4363 tokens,
4364 diagnostics,
4365 cursor: 0,
4366 }
4367 }
4368
4369 fn parse_root(&mut self) -> Vec<SyntaxNode> {
4370 self.parse_block(false)
4371 }
4372
4373 fn parse_block(&mut self, stop_at_close_brace: bool) -> Vec<SyntaxNode> {
4374 let mut nodes = Vec::new();
4375
4376 while self.cursor < self.tokens.len() {
4377 let token = &self.tokens[self.cursor];
4378
4379 match token.kind {
4380 TokenKind::Whitespace => {
4381 self.cursor += 1;
4382 }
4383 TokenKind::LineComment | TokenKind::BlockComment => {
4384 nodes.push(SyntaxNode {
4385 kind: SyntaxNodeKind::Comment,
4386 span: token.span,
4387 header_span: None,
4388 payload: Some(SyntaxNodePayload::Comment(CommentPayload {
4389 text: self.slice(token.span).to_string(),
4390 })),
4391 children: Vec::new(),
4392 });
4393 self.cursor += 1;
4394 }
4395 TokenKind::CloseBrace if stop_at_close_brace => {
4396 self.cursor += 1;
4397 return nodes;
4398 }
4399 TokenKind::CloseBrace => {
4400 self.diagnostics.push(ParseDiagnostic {
4401 message: "unexpected closing brace".to_string(),
4402 span: token.span,
4403 });
4404 self.cursor += 1;
4405 }
4406 _ => nodes.push(self.parse_statement()),
4407 }
4408 }
4409
4410 if stop_at_close_brace {
4411 let end = self.tokens.last().map_or(0, |token| token.span.end);
4412 self.diagnostics.push(ParseDiagnostic {
4413 message: "unterminated block".to_string(),
4414 span: TextSpan::new(end, end),
4415 });
4416 }
4417
4418 nodes
4419 }
4420
4421 fn parse_statement(&mut self) -> SyntaxNode {
4422 let start_index = self.cursor;
4423 let mut index = self.cursor;
4424 let mut saw_at = self.tokens[index].kind == TokenKind::At;
4425 let mut saw_colon = false;
4426 let mut first_colon_index = None;
4427 let mut paren_depth = 0usize;
4428 let mut bracket_depth = 0usize;
4429
4430 while index < self.tokens.len() {
4431 let token = &self.tokens[index];
4432 match token.kind {
4433 TokenKind::OpenParen => paren_depth += 1,
4434 TokenKind::CloseParen => paren_depth = paren_depth.saturating_sub(1),
4435 TokenKind::OpenBracket => bracket_depth += 1,
4436 TokenKind::CloseBracket => bracket_depth = bracket_depth.saturating_sub(1),
4437 TokenKind::Colon if paren_depth == 0 && bracket_depth == 0 => {
4438 saw_colon = true;
4439 if first_colon_index.is_none() {
4440 first_colon_index = Some(index);
4441 }
4442 }
4443 TokenKind::At if index == start_index => saw_at = true,
4444 TokenKind::Semicolon if paren_depth == 0 && bracket_depth == 0 => {
4445 let span = TextSpan::new(
4446 self.tokens[start_index].span.start,
4447 self.tokens[index].span.end,
4448 );
4449 self.cursor = index + 1;
4450 return SyntaxNode {
4451 kind: classify_statement_kind(saw_at, saw_colon),
4452 span,
4453 header_span: Some(TextSpan::new(
4454 self.tokens[start_index].span.start,
4455 self.tokens[index].span.start,
4456 )),
4457 payload: self.build_inline_payload(
4458 start_index,
4459 index,
4460 saw_at,
4461 first_colon_index,
4462 ),
4463 children: Vec::new(),
4464 };
4465 }
4466 TokenKind::OpenBrace if paren_depth == 0 && bracket_depth == 0 => {
4467 let header_span = TextSpan::new(
4468 self.tokens[start_index].span.start,
4469 self.tokens[index].span.start,
4470 );
4471 self.cursor = index + 1;
4472 let children = self.parse_block(true);
4473 let end = self
4474 .tokens
4475 .get(self.cursor.saturating_sub(1))
4476 .map_or(self.tokens[index].span.end, |token| token.span.end);
4477 return SyntaxNode {
4478 kind: if saw_at {
4479 SyntaxNodeKind::AtRule
4480 } else {
4481 SyntaxNodeKind::Rule
4482 },
4483 span: TextSpan::new(self.tokens[start_index].span.start, end),
4484 header_span: Some(header_span),
4485 payload: Some(if saw_at {
4486 SyntaxNodePayload::AtRule(
4487 self.build_at_rule_payload(start_index, index),
4488 )
4489 } else {
4490 let prelude = self.slice_trimmed(header_span).to_string();
4491 SyntaxNodePayload::Rule(RulePayload {
4492 selector_groups: parse_selector_groups(&prelude),
4493 prelude,
4494 })
4495 }),
4496 children,
4497 };
4498 }
4499 TokenKind::CloseBrace => break,
4500 _ => {}
4501 }
4502 index += 1;
4503 }
4504
4505 let end = self
4506 .tokens
4507 .get(index.saturating_sub(1))
4508 .map_or(self.tokens[start_index].span.end, |token| token.span.end);
4509 self.cursor = index.max(start_index + 1);
4510 let span = TextSpan::new(self.tokens[start_index].span.start, end);
4511 SyntaxNode {
4512 kind: classify_statement_kind(saw_at, saw_colon),
4513 span,
4514 header_span: Some(span),
4515 payload: self.build_inline_payload(start_index, index, saw_at, first_colon_index),
4516 children: Vec::new(),
4517 }
4518 }
4519
4520 fn build_inline_payload(
4521 &self,
4522 start_index: usize,
4523 end_index: usize,
4524 saw_at: bool,
4525 first_colon_index: Option<usize>,
4526 ) -> Option<SyntaxNodePayload> {
4527 if saw_at {
4528 return Some(SyntaxNodePayload::AtRule(
4529 self.build_at_rule_payload(start_index, end_index),
4530 ));
4531 }
4532
4533 let colon_index = first_colon_index?;
4534 let property_span = TextSpan::new(
4535 self.tokens[start_index].span.start,
4536 self.tokens[colon_index].span.start,
4537 );
4538 let value_start = self.tokens[colon_index].span.end;
4539 let value_end = self
4540 .tokens
4541 .get(end_index.saturating_sub(1))
4542 .map_or(value_start, |token| token.span.end);
4543 Some(SyntaxNodePayload::Declaration(DeclarationPayload {
4544 property: self.slice_trimmed(property_span).to_string(),
4545 value: self
4546 .slice_trimmed(TextSpan::new(value_start, value_end))
4547 .to_string(),
4548 }))
4549 }
4550
4551 fn build_at_rule_payload(&self, start_index: usize, end_index: usize) -> AtRulePayload {
4552 let name = self
4553 .tokens
4554 .get(start_index + 1)
4555 .map(|token| self.slice(token.span))
4556 .unwrap_or_default()
4557 .trim()
4558 .to_string();
4559 let params_start = self
4560 .tokens
4561 .get(start_index + 2)
4562 .map_or(self.tokens[start_index].span.end, |token| token.span.start);
4563 let params_end = self
4564 .tokens
4565 .get(end_index.saturating_sub(1))
4566 .map_or(params_start, |token| token.span.end);
4567 AtRulePayload {
4568 kind: classify_at_rule_kind(&name),
4569 name,
4570 params: self
4571 .slice_trimmed(TextSpan::new(params_start, params_end))
4572 .to_string(),
4573 }
4574 }
4575
4576 fn slice(&self, span: TextSpan) -> &'a str {
4577 &self.source[span.start..span.end]
4578 }
4579
4580 fn slice_trimmed(&self, span: TextSpan) -> &'a str {
4581 self.slice(span).trim()
4582 }
4583}
4584
4585fn classify_statement_kind(saw_at: bool, saw_colon: bool) -> SyntaxNodeKind {
4586 if saw_at {
4587 SyntaxNodeKind::AtRule
4588 } else if saw_colon {
4589 SyntaxNodeKind::Declaration
4590 } else {
4591 SyntaxNodeKind::Unknown
4592 }
4593}
4594
4595fn classify_at_rule_kind(name: &str) -> AtRuleKind {
4596 match name {
4597 "media" => AtRuleKind::Media,
4598 "supports" => AtRuleKind::Supports,
4599 "layer" => AtRuleKind::Layer,
4600 "keyframes" | "-webkit-keyframes" => AtRuleKind::Keyframes,
4601 "value" => AtRuleKind::Value,
4602 "at-root" => AtRuleKind::AtRoot,
4603 "mixin" => AtRuleKind::Mixin,
4604 "include" => AtRuleKind::Include,
4605 "function" => AtRuleKind::Function,
4606 "use" => AtRuleKind::Use,
4607 "forward" => AtRuleKind::Forward,
4608 "import" => AtRuleKind::Import,
4609 _ => AtRuleKind::Generic,
4610 }
4611}
4612
4613#[cfg(test)]
4614mod tests {
4615 use super::{
4616 AtRuleKind, AtRulePayload, DeclarationPayload, ParserByteSpanV0,
4617 ParserIndexSassModuleUseFactV0, ParserIndexSassSelectorSymbolFactV0, ParserPositionV0,
4618 ParserRangeV0, RulePayload, SelectorGroup, SelectorSegment, StyleLanguage, SyntaxNodeKind,
4619 SyntaxNodePayload, TextSpan, TokenKind, parse_stylesheet,
4620 };
4621
4622 fn token_texts<'a>(source: &'a str, sheet: &super::Stylesheet) -> Vec<(TokenKind, &'a str)> {
4623 sheet
4624 .tokens
4625 .iter()
4626 .map(|token| (token.kind, &source[token.span.start..token.span.end]))
4627 .collect()
4628 }
4629
4630 fn span_after(source: &str, anchor: &str, needle: &str) -> Result<ParserByteSpanV0, String> {
4631 let anchor_start = source
4632 .find(anchor)
4633 .ok_or_else(|| format!("missing anchor {anchor:?}"))?;
4634 let relative_start = source[anchor_start..]
4635 .find(needle)
4636 .ok_or_else(|| format!("missing needle {needle:?} after {anchor:?}"))?;
4637 let start = anchor_start + relative_start;
4638 Ok(ParserByteSpanV0 {
4639 start,
4640 end: start + needle.len(),
4641 })
4642 }
4643
4644 fn range_after(source: &str, anchor: &str, needle: &str) -> Result<ParserRangeV0, String> {
4645 span_after(source, anchor, needle)
4646 .map(|span| super::source_range_for_byte_span(source, span))
4647 }
4648
4649 #[test]
4650 fn detects_style_language_from_module_path() {
4651 assert_eq!(
4652 StyleLanguage::from_module_path("/x/Button.module.css"),
4653 Some(StyleLanguage::Css)
4654 );
4655 assert_eq!(
4656 StyleLanguage::from_module_path("/x/Button.module.scss"),
4657 Some(StyleLanguage::Scss)
4658 );
4659 assert_eq!(
4660 StyleLanguage::from_module_path("/x/Button.module.less"),
4661 Some(StyleLanguage::Less)
4662 );
4663 assert_eq!(
4664 StyleLanguage::from_module_path("/x/Button.css"),
4665 Some(StyleLanguage::Css)
4666 );
4667 assert_eq!(
4668 StyleLanguage::from_module_path("/x/_tokens.scss"),
4669 Some(StyleLanguage::Scss)
4670 );
4671 assert_eq!(
4672 StyleLanguage::from_module_path("/x/theme.less"),
4673 Some(StyleLanguage::Less)
4674 );
4675 }
4676
4677 #[test]
4678 fn tokenizes_basic_css_rule() {
4679 let source = ".button { color: red; }";
4680 let sheet = parse_stylesheet(StyleLanguage::Css, source);
4681 let tokens = token_texts(source, &sheet);
4682 assert!(tokens.contains(&(TokenKind::Dot, ".")));
4683 assert!(tokens.contains(&(TokenKind::Ident, "button")));
4684 assert!(tokens.contains(&(TokenKind::OpenBrace, "{")));
4685 assert!(tokens.contains(&(TokenKind::Semicolon, ";")));
4686 assert!(sheet.diagnostics.is_empty());
4687 }
4688
4689 #[test]
4690 fn keeps_css_double_slash_as_regular_tokens() {
4691 let source = ".button { // not-a-comment\n color: red; }";
4692 let sheet = parse_stylesheet(StyleLanguage::Css, source);
4693 assert!(
4694 !sheet
4695 .tokens
4696 .iter()
4697 .any(|token| matches!(token.kind, TokenKind::LineComment))
4698 );
4699 }
4700
4701 #[test]
4702 fn parses_scss_nested_rules_and_comments() {
4703 let source = ".button {\n // note\n &--primary { color: red; }\n}\n";
4704 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4705 assert_eq!(sheet.nodes.len(), 1);
4706 let root_rule = &sheet.nodes[0];
4707 assert_eq!(root_rule.kind, SyntaxNodeKind::Rule);
4708 assert_eq!(
4709 root_rule.payload,
4710 Some(SyntaxNodePayload::Rule(RulePayload {
4711 prelude: ".button".to_string(),
4712 selector_groups: vec![SelectorGroup {
4713 raw: ".button".to_string(),
4714 segments: vec![SelectorSegment::ClassName("button".to_string())],
4715 }],
4716 }))
4717 );
4718 assert_eq!(root_rule.children.len(), 2);
4719 assert_eq!(root_rule.children[0].kind, SyntaxNodeKind::Comment);
4720 assert_eq!(
4721 root_rule.children[0].payload,
4722 Some(SyntaxNodePayload::Comment(super::CommentPayload {
4723 text: "// note".to_string(),
4724 }))
4725 );
4726 assert_eq!(root_rule.children[1].kind, SyntaxNodeKind::Rule);
4727 assert_eq!(
4728 root_rule.children[1].payload,
4729 Some(SyntaxNodePayload::Rule(RulePayload {
4730 prelude: "&--primary".to_string(),
4731 selector_groups: vec![SelectorGroup {
4732 raw: "&--primary".to_string(),
4733 segments: vec![
4734 SelectorSegment::Ampersand,
4735 SelectorSegment::BemSuffix("--primary".to_string()),
4736 ],
4737 }],
4738 }))
4739 );
4740 assert_eq!(
4741 root_rule.children[1].children[0].payload,
4742 Some(SyntaxNodePayload::Declaration(DeclarationPayload {
4743 property: "color".to_string(),
4744 value: "red".to_string(),
4745 }))
4746 );
4747 assert!(sheet.diagnostics.is_empty());
4748 }
4749
4750 #[test]
4751 fn parses_less_at_rule_like_variable_assignment() {
4752 let source = "@color: red;\n.button { color: @color; }";
4753 let sheet = parse_stylesheet(StyleLanguage::Less, source);
4754 assert_eq!(sheet.nodes[0].kind, SyntaxNodeKind::AtRule);
4755 assert_eq!(
4756 sheet.nodes[0].payload,
4757 Some(SyntaxNodePayload::AtRule(AtRulePayload {
4758 kind: AtRuleKind::Generic,
4759 name: "color".to_string(),
4760 params: ": red".to_string(),
4761 }))
4762 );
4763 assert_eq!(sheet.nodes[1].kind, SyntaxNodeKind::Rule);
4764 }
4765
4766 #[test]
4767 fn parses_at_rule_header_and_params() {
4768 let source = "@media screen and (min-width: 10px) { .button { color: red; } }";
4769 let sheet = parse_stylesheet(StyleLanguage::Css, source);
4770 assert_eq!(
4771 sheet.nodes[0].payload,
4772 Some(SyntaxNodePayload::AtRule(AtRulePayload {
4773 kind: AtRuleKind::Media,
4774 name: "media".to_string(),
4775 params: "screen and (min-width: 10px)".to_string(),
4776 }))
4777 );
4778 }
4779
4780 #[test]
4781 fn classifies_keyframes_and_value_at_rules() {
4782 let source = "@value brand: red;\n@keyframes fade { from { opacity: 0; } }\n";
4783 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4784 assert_eq!(
4785 sheet.nodes[0].payload,
4786 Some(SyntaxNodePayload::AtRule(AtRulePayload {
4787 kind: AtRuleKind::Value,
4788 name: "value".to_string(),
4789 params: "brand: red".to_string(),
4790 }))
4791 );
4792 assert_eq!(
4793 sheet.nodes[1].payload,
4794 Some(SyntaxNodePayload::AtRule(AtRulePayload {
4795 kind: AtRuleKind::Keyframes,
4796 name: "keyframes".to_string(),
4797 params: "fade".to_string(),
4798 }))
4799 );
4800 }
4801
4802 #[test]
4803 fn classifies_sass_symbol_at_rules() {
4804 let source = r#"@use "./tokens" as tokens;
4805@forward "./theme";
4806@import "./legacy";
4807@mixin raised($depth) { color: red; }
4808@include raised($gap);
4809@function tone($value) { @return $value; }
4810"#;
4811 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4812 let kinds: Vec<AtRuleKind> = sheet
4813 .nodes
4814 .iter()
4815 .filter_map(|node| match &node.payload {
4816 Some(SyntaxNodePayload::AtRule(at_rule)) => Some(at_rule.kind),
4817 _ => None,
4818 })
4819 .collect();
4820 assert_eq!(
4821 kinds,
4822 vec![
4823 AtRuleKind::Use,
4824 AtRuleKind::Forward,
4825 AtRuleKind::Import,
4826 AtRuleKind::Mixin,
4827 AtRuleKind::Include,
4828 AtRuleKind::Function,
4829 ]
4830 );
4831 }
4832
4833 #[test]
4834 fn records_unterminated_block_comment_diagnostic() {
4835 let source = "/* open";
4836 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4837 assert_eq!(sheet.diagnostics.len(), 1);
4838 assert_eq!(sheet.diagnostics[0].message, "unterminated block comment");
4839 assert_eq!(sheet.diagnostics[0].span, TextSpan::new(0, source.len()));
4840 }
4841
4842 #[test]
4843 fn records_unterminated_block_diagnostic() {
4844 let source = ".button { color: red;";
4845 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4846 assert_eq!(sheet.nodes.len(), 1);
4847 assert_eq!(sheet.nodes[0].kind, SyntaxNodeKind::Rule);
4848 assert!(
4849 sheet
4850 .diagnostics
4851 .iter()
4852 .any(|diagnostic| diagnostic.message == "unterminated block")
4853 );
4854 }
4855
4856 #[test]
4857 fn splits_grouped_selectors_into_groups_and_segments() {
4858 let source = ".a, .b { &--c { color: red; } }";
4859 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4860 assert_eq!(
4861 sheet.nodes[0].payload,
4862 Some(SyntaxNodePayload::Rule(RulePayload {
4863 prelude: ".a, .b".to_string(),
4864 selector_groups: vec![
4865 SelectorGroup {
4866 raw: ".a".to_string(),
4867 segments: vec![SelectorSegment::ClassName("a".to_string())],
4868 },
4869 SelectorGroup {
4870 raw: ".b".to_string(),
4871 segments: vec![SelectorSegment::ClassName("b".to_string())],
4872 },
4873 ],
4874 }))
4875 );
4876 }
4877
4878 #[test]
4879 fn parity_summary_reconstructs_bem_suffix_names() {
4880 let source = ".card { &__icon { &--small { color: red; } } }";
4881 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4882 let summary = super::summarize_parity_lite(&sheet);
4883 assert_eq!(
4884 summary.selector_names,
4885 vec!["card", "card__icon", "card__icon--small"]
4886 );
4887 }
4888
4889 #[test]
4890 fn parity_summary_expands_grouped_parent_bem_suffixes() {
4891 let source = ".a, .b { &--c { color: red; } }";
4892 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4893 let summary = super::summarize_parity_lite(&sheet);
4894 assert_eq!(summary.selector_names, vec!["a", "a--c", "b", "b--c"]);
4895 }
4896
4897 #[test]
4898 fn parity_summary_expands_grouped_parent_nested_bem_suffixes() {
4899 let source = ".a, .b { &__icon { &--small { color: red; } } }";
4900 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4901 let summary = super::summarize_parity_lite(&sheet);
4902 assert_eq!(
4903 summary.selector_names,
4904 vec![
4905 "a",
4906 "a__icon",
4907 "a__icon--small",
4908 "b",
4909 "b__icon",
4910 "b__icon--small"
4911 ]
4912 );
4913 }
4914
4915 #[test]
4916 fn parity_summary_keeps_ampersand_class_as_standalone_class() {
4917 let source = ".a, .b { &.active { color: red; } }";
4918 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4919 let summary = super::summarize_parity_lite(&sheet);
4920 assert_eq!(summary.selector_names, vec!["a", "active", "active", "b"]);
4921 }
4922
4923 #[test]
4924 fn index_summary_matches_nested_bem_safety_matrix() {
4925 let source = ".btn { &--primary {} &__icon {} &.active {} &-legacy {} &_legacy {} & + &--paired {} }";
4926 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4927 let summary = super::summarize_css_modules_intermediate(&sheet);
4928 assert_eq!(
4929 summary.selectors.names,
4930 vec![
4931 "active",
4932 "btn",
4933 "btn--paired",
4934 "btn--primary",
4935 "btn-legacy",
4936 "btn__icon",
4937 "btn_legacy"
4938 ]
4939 );
4940 assert_eq!(
4941 summary.selectors.bem_suffix_safe_names,
4942 vec!["btn--primary", "btn__icon"]
4943 );
4944 assert_eq!(
4945 summary.selectors.nested_unsafe_names,
4946 vec!["active", "btn--paired", "btn-legacy", "btn_legacy"]
4947 );
4948 }
4949
4950 #[test]
4951 fn index_summary_does_not_bem_expand_non_bare_parent_suffixes() {
4952 let source = ".card:hover { &--primary {} }";
4953 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4954 let summary = super::summarize_css_modules_intermediate(&sheet);
4955 assert_eq!(summary.selectors.names, vec!["card"]);
4956 assert_eq!(summary.selectors.bem_suffix_count, 0);
4957 }
4958
4959 #[test]
4960 fn index_summary_marks_plain_nested_selectors_unsafe() {
4961 let source = ".wrapper { .inner { &--mod {} } }";
4962 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4963 let summary = super::summarize_css_modules_intermediate(&sheet);
4964 assert_eq!(summary.selectors.names, vec!["inner", "wrapper"]);
4965 assert_eq!(summary.selectors.nested_unsafe_names, vec!["inner"]);
4966 assert_eq!(summary.selectors.nested_safety_counts.flat, 1);
4967 assert_eq!(summary.selectors.nested_safety_counts.nested_unsafe, 1);
4968 }
4969
4970 #[test]
4971 fn index_summary_allows_unsafe_suffix_to_become_deep_bem_base() {
4972 let source = ".btn { &-legacy { &--x {} } }";
4973 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4974 let summary = super::summarize_css_modules_intermediate(&sheet);
4975 assert_eq!(
4976 summary.selectors.names,
4977 vec!["btn", "btn-legacy", "btn-legacy--x"]
4978 );
4979 assert_eq!(
4980 summary.selectors.bem_suffix_safe_names,
4981 vec!["btn-legacy--x"]
4982 );
4983 assert_eq!(summary.selectors.nested_unsafe_names, vec!["btn-legacy"]);
4984 }
4985
4986 #[test]
4987 fn parity_summary_keeps_class_from_pseudo_selector() {
4988 let source = ".btn:hover { color: red; }";
4989 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4990 let summary = super::summarize_parity_lite(&sheet);
4991 assert_eq!(summary.selector_names, vec!["btn"]);
4992 }
4993
4994 #[test]
4995 fn parity_summary_keeps_multiple_classes_from_compound_selector() {
4996 let source = ".btn.active { color: red; }";
4997 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
4998 let summary = super::summarize_parity_lite(&sheet);
4999 assert_eq!(summary.selector_names, vec!["active", "btn"]);
5000 }
5001
5002 #[test]
5003 fn parity_summary_prefers_rightmost_class_after_combinator() {
5004 let source = ".a > .b { color: red; }";
5005 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5006 let summary = super::summarize_parity_lite(&sheet);
5007 assert_eq!(summary.selector_names, vec!["b"]);
5008 }
5009
5010 #[test]
5011 fn parity_summary_collects_nested_layer_rule_selectors() {
5012 let source = "@layer ui { .btn:hover { color: red; } }";
5013 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5014 let summary = super::summarize_parity_lite(&sheet);
5015 assert_eq!(summary.selector_names, vec!["btn"]);
5016 }
5017
5018 #[test]
5019 fn parity_summary_prefers_rightmost_class_after_descendant_combinator() {
5020 let source = ".a .b { color: red; }";
5021 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5022 let summary = super::summarize_parity_lite(&sheet);
5023 assert_eq!(summary.selector_names, vec!["b"]);
5024 }
5025
5026 #[test]
5027 fn index_summary_exposes_selector_definition_facts_for_lsp_consumers() -> Result<(), String> {
5028 let source = ".item { &.compact .body { color: red; } &--primary { color: blue; } }";
5029 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5030 let summary = super::summarize_css_modules_intermediate(&sheet);
5031 let definition_names: Vec<&str> = summary
5032 .selectors
5033 .definition_facts
5034 .iter()
5035 .map(|fact| fact.name.as_str())
5036 .collect();
5037 assert_eq!(
5038 definition_names,
5039 vec!["body", "compact", "item", "item--primary"]
5040 );
5041 assert_eq!(
5042 summary
5043 .selectors
5044 .definition_facts
5045 .iter()
5046 .find(|fact| fact.name == "compact")
5047 .map(|fact| fact.range),
5048 Some(range_after(source, "&.compact", "compact")?)
5049 );
5050 assert_eq!(
5051 summary
5052 .selectors
5053 .definition_facts
5054 .iter()
5055 .find(|fact| fact.name == "item--primary")
5056 .map(|fact| (fact.range, fact.bem_suffix_parent_name.as_deref())),
5057 Some((
5058 range_after(source, "&--primary", "--primary")?,
5059 Some("item")
5060 ))
5061 );
5062 Ok(())
5063 }
5064
5065 #[test]
5066 fn parity_summary_ignores_classes_inside_pseudo_functions() {
5067 let source = ".btn:is(.active, .primary) { color: red; }";
5068 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5069 let summary = super::summarize_parity_lite(&sheet);
5070 assert_eq!(summary.selector_names, vec!["btn"]);
5071 }
5072
5073 #[test]
5074 fn parity_summary_ignores_global_function_classes() {
5075 let source = ":global(.foo) { color: red; }";
5076 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5077 let summary = super::summarize_parity_lite(&sheet);
5078 assert!(summary.selector_names.is_empty());
5079 }
5080
5081 #[test]
5082 fn parity_summary_ignores_not_function_classes() {
5083 let source = ".btn:not(.disabled) { color: red; }";
5084 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5085 let summary = super::summarize_parity_lite(&sheet);
5086 assert_eq!(summary.selector_names, vec!["btn"]);
5087 }
5088
5089 #[test]
5090 fn parity_summary_keeps_local_function_class() {
5091 let source = ":local(.foo) { color: red; }";
5092 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5093 let summary = super::summarize_parity_lite(&sheet);
5094 assert_eq!(summary.selector_names, vec!["foo"]);
5095 }
5096
5097 #[test]
5098 fn index_summary_collects_sass_symbol_seed_facts() -> Result<(), String> {
5099 let source = r#"@use "./plain";
5100@use "./reset" as *;
5101@use "./tokens" as tokens;
5102@use "sass:color";
5103@forward "./theme";
5104@import "./legacy";
5105$gap: 1rem;
5106@mixin raised($depth) { box-shadow: 0 0 $depth black; }
5107@function tone($value) { @return $value; }
5108.btn { color: $gap; @include raised($gap); border-color: tone($gap); }
5109.ghost { color: $missing; @include absent($gap); }
5110"#;
5111 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5112 let summary = super::summarize_css_modules_intermediate(&sheet);
5113
5114 assert_eq!(summary.sass.variable_decl_names, vec!["gap"]);
5115 assert_eq!(
5116 summary.sass.variable_parameter_names,
5117 vec!["depth", "value"]
5118 );
5119 assert_eq!(
5120 summary.sass.variable_ref_names,
5121 vec!["depth", "gap", "missing", "value"]
5122 );
5123 assert_eq!(
5124 summary.sass.selectors_with_variable_refs_names,
5125 vec!["btn", "ghost"]
5126 );
5127 assert_eq!(
5128 summary.sass.selectors_with_resolved_variable_refs_names,
5129 vec!["btn", "ghost"]
5130 );
5131 assert_eq!(
5132 summary.sass.selectors_with_unresolved_variable_refs_names,
5133 vec!["ghost"]
5134 );
5135 assert_eq!(summary.sass.mixin_decl_names, vec!["raised"]);
5136 assert_eq!(summary.sass.mixin_include_names, vec!["absent", "raised"]);
5137 assert_eq!(
5138 summary.sass.selectors_with_mixin_includes_names,
5139 vec!["btn", "ghost"]
5140 );
5141 assert_eq!(
5142 summary.sass.selectors_with_resolved_mixin_includes_names,
5143 vec!["btn"]
5144 );
5145 assert_eq!(
5146 summary.sass.selectors_with_unresolved_mixin_includes_names,
5147 vec!["ghost"]
5148 );
5149 assert_eq!(summary.sass.function_decl_names, vec!["tone"]);
5150 assert_eq!(summary.sass.function_call_names, vec!["tone"]);
5151 assert_eq!(
5152 summary.sass.selectors_with_function_calls_names,
5153 vec!["btn"]
5154 );
5155 assert_eq!(
5156 summary.sass.selector_symbol_facts,
5157 vec![
5158 ParserIndexSassSelectorSymbolFactV0 {
5159 selector_name: "btn".to_string(),
5160 symbol_kind: "function",
5161 name: "tone".to_string(),
5162 namespace: None,
5163 role: "call",
5164 resolution: "resolved",
5165 byte_span: span_after(source, "border-color:", "tone")?,
5166 range: range_after(source, "border-color:", "tone")?,
5167 },
5168 ParserIndexSassSelectorSymbolFactV0 {
5169 selector_name: "btn".to_string(),
5170 symbol_kind: "mixin",
5171 name: "raised".to_string(),
5172 namespace: None,
5173 role: "include",
5174 resolution: "resolved",
5175 byte_span: span_after(source, "@include", "raised")?,
5176 range: range_after(source, "@include", "raised")?,
5177 },
5178 ParserIndexSassSelectorSymbolFactV0 {
5179 selector_name: "btn".to_string(),
5180 symbol_kind: "variable",
5181 name: "gap".to_string(),
5182 namespace: None,
5183 role: "reference",
5184 resolution: "resolved",
5185 byte_span: span_after(source, ".btn { color", "$gap")?,
5186 range: range_after(source, ".btn { color", "$gap")?,
5187 },
5188 ParserIndexSassSelectorSymbolFactV0 {
5189 selector_name: "btn".to_string(),
5190 symbol_kind: "variable",
5191 name: "gap".to_string(),
5192 namespace: None,
5193 role: "reference",
5194 resolution: "resolved",
5195 byte_span: span_after(source, "@include raised(", "$gap")?,
5196 range: range_after(source, "@include raised(", "$gap")?,
5197 },
5198 ParserIndexSassSelectorSymbolFactV0 {
5199 selector_name: "btn".to_string(),
5200 symbol_kind: "variable",
5201 name: "gap".to_string(),
5202 namespace: None,
5203 role: "reference",
5204 resolution: "resolved",
5205 byte_span: span_after(source, "border-color: tone(", "$gap")?,
5206 range: range_after(source, "border-color: tone(", "$gap")?,
5207 },
5208 ParserIndexSassSelectorSymbolFactV0 {
5209 selector_name: "ghost".to_string(),
5210 symbol_kind: "mixin",
5211 name: "absent".to_string(),
5212 namespace: None,
5213 role: "include",
5214 resolution: "unresolved",
5215 byte_span: span_after(source, ".ghost", "absent")?,
5216 range: range_after(source, ".ghost", "absent")?,
5217 },
5218 ParserIndexSassSelectorSymbolFactV0 {
5219 selector_name: "ghost".to_string(),
5220 symbol_kind: "variable",
5221 name: "gap".to_string(),
5222 namespace: None,
5223 role: "reference",
5224 resolution: "resolved",
5225 byte_span: span_after(source, "absent(", "$gap")?,
5226 range: range_after(source, "absent(", "$gap")?,
5227 },
5228 ParserIndexSassSelectorSymbolFactV0 {
5229 selector_name: "ghost".to_string(),
5230 symbol_kind: "variable",
5231 name: "missing".to_string(),
5232 namespace: None,
5233 role: "reference",
5234 resolution: "unresolved",
5235 byte_span: span_after(source, ".ghost { color", "$missing")?,
5236 range: range_after(source, ".ghost { color", "$missing")?,
5237 },
5238 ]
5239 );
5240 assert_eq!(
5241 summary.sass.module_use_sources,
5242 vec!["./legacy", "./plain", "./reset", "./tokens", "sass:color"]
5243 );
5244 assert_eq!(
5245 summary.sass.module_use_edges,
5246 vec![
5247 ParserIndexSassModuleUseFactV0 {
5248 source: "./legacy".to_string(),
5249 namespace_kind: "wildcard",
5250 namespace: None,
5251 },
5252 ParserIndexSassModuleUseFactV0 {
5253 source: "./plain".to_string(),
5254 namespace_kind: "default",
5255 namespace: Some("plain".to_string()),
5256 },
5257 ParserIndexSassModuleUseFactV0 {
5258 source: "./reset".to_string(),
5259 namespace_kind: "wildcard",
5260 namespace: None,
5261 },
5262 ParserIndexSassModuleUseFactV0 {
5263 source: "./tokens".to_string(),
5264 namespace_kind: "alias",
5265 namespace: Some("tokens".to_string()),
5266 },
5267 ParserIndexSassModuleUseFactV0 {
5268 source: "sass:color".to_string(),
5269 namespace_kind: "default",
5270 namespace: Some("color".to_string()),
5271 },
5272 ]
5273 );
5274 assert_eq!(summary.sass.module_forward_sources, vec!["./theme"]);
5275 assert_eq!(summary.sass.module_import_sources, vec!["./legacy"]);
5276 assert_eq!(
5277 summary
5278 .sass
5279 .same_file_resolution
5280 .resolved_variable_ref_names,
5281 vec!["depth", "gap", "value"]
5282 );
5283 assert_eq!(
5284 summary
5285 .sass
5286 .same_file_resolution
5287 .unresolved_variable_ref_names,
5288 vec!["missing"]
5289 );
5290 assert_eq!(
5291 summary
5292 .sass
5293 .same_file_resolution
5294 .resolved_mixin_include_names,
5295 vec!["raised"]
5296 );
5297 assert_eq!(
5298 summary
5299 .sass
5300 .same_file_resolution
5301 .unresolved_mixin_include_names,
5302 vec!["absent"]
5303 );
5304 assert_eq!(
5305 summary
5306 .sass
5307 .same_file_resolution
5308 .resolved_function_call_names,
5309 vec!["tone"]
5310 );
5311 Ok(())
5312 }
5313
5314 #[test]
5315 fn index_summary_collects_css_custom_property_seed_facts() {
5316 let source = r#":root { --color-gray-700: #767678; }
5317@media (min-width: 1px) { :root { --brand: white; } .btn { color: var(--color-gray-700); } }
5318@supports (display: grid) { @layer ui { [data-theme="dark"] { --surface: black; } .card { color: var(--missing); } } }
5319"#;
5320 let sheet = parse_stylesheet(StyleLanguage::Css, source);
5321 let summary = super::summarize_css_modules_intermediate(&sheet);
5322 let semantic_boundary = super::summarize_semantic_boundary(&sheet);
5323
5324 assert_eq!(
5325 summary.custom_properties.decl_names,
5326 vec!["--brand", "--color-gray-700", "--surface"]
5327 );
5328 assert_eq!(
5329 summary
5330 .custom_properties
5331 .decl_facts
5332 .iter()
5333 .map(|fact| (
5334 fact.name.as_str(),
5335 fact.source_order,
5336 fact.selector_contexts.as_slice(),
5337 fact.under_media,
5338 fact.under_supports,
5339 fact.under_layer,
5340 ))
5341 .collect::<Vec<_>>(),
5342 vec![
5343 (
5344 "--brand",
5345 1,
5346 vec![":root".to_string()].as_slice(),
5347 true,
5348 false,
5349 false,
5350 ),
5351 (
5352 "--color-gray-700",
5353 0,
5354 vec![":root".to_string()].as_slice(),
5355 false,
5356 false,
5357 false,
5358 ),
5359 (
5360 "--surface",
5361 2,
5362 vec!["[data-theme=\"dark\"]".to_string()].as_slice(),
5363 false,
5364 true,
5365 true,
5366 ),
5367 ]
5368 );
5369 assert_eq!(
5370 summary.custom_properties.decl_context_selectors,
5371 vec![":root", "[data-theme=\"dark\"]"]
5372 );
5373 assert_eq!(
5374 summary.custom_properties.decl_names_under_media,
5375 vec!["--brand"]
5376 );
5377 assert_eq!(
5378 summary.custom_properties.decl_names_under_supports,
5379 vec!["--surface"]
5380 );
5381 assert_eq!(
5382 summary.custom_properties.decl_names_under_layer,
5383 vec!["--surface"]
5384 );
5385 assert_eq!(
5386 summary.custom_properties.ref_names,
5387 vec!["--color-gray-700", "--missing"]
5388 );
5389 assert_eq!(
5390 summary
5391 .custom_properties
5392 .ref_facts
5393 .iter()
5394 .map(|fact| (
5395 fact.name.as_str(),
5396 fact.source_order,
5397 fact.selector_contexts.as_slice(),
5398 fact.under_media,
5399 fact.under_supports,
5400 fact.under_layer,
5401 ))
5402 .collect::<Vec<_>>(),
5403 vec![
5404 (
5405 "--color-gray-700",
5406 0,
5407 vec![".btn".to_string()].as_slice(),
5408 true,
5409 false,
5410 false,
5411 ),
5412 (
5413 "--missing",
5414 1,
5415 vec![".card".to_string()].as_slice(),
5416 false,
5417 true,
5418 true,
5419 ),
5420 ]
5421 );
5422 assert_eq!(
5423 summary.custom_properties.selectors_with_refs_names,
5424 vec!["btn", "card"]
5425 );
5426 assert_eq!(
5427 summary
5428 .custom_properties
5429 .selectors_with_refs_under_media_names,
5430 vec!["btn"]
5431 );
5432 assert_eq!(
5433 summary
5434 .custom_properties
5435 .selectors_with_refs_under_supports_names,
5436 vec!["card"]
5437 );
5438 assert_eq!(
5439 summary
5440 .custom_properties
5441 .selectors_with_refs_under_layer_names,
5442 vec!["card"]
5443 );
5444 assert_eq!(
5445 semantic_boundary
5446 .semantic_facts
5447 .custom_properties
5448 .resolved_ref_names,
5449 vec!["--color-gray-700"]
5450 );
5451 assert_eq!(
5452 semantic_boundary
5453 .semantic_facts
5454 .custom_properties
5455 .unresolved_ref_names,
5456 vec!["--missing"]
5457 );
5458 }
5459
5460 #[test]
5461 fn semantic_boundary_respects_custom_property_selector_context() {
5462 let source = r#".theme { --brand: #222; }
5463.button { color: var(--brand); }
5464.theme .button { border-color: var(--brand); }
5465:root { --surface: white; }
5466.card { background: var(--surface); }
5467"#;
5468 let sheet = parse_stylesheet(StyleLanguage::Css, source);
5469 let semantic_boundary = super::summarize_semantic_boundary(&sheet);
5470
5471 assert_eq!(
5472 semantic_boundary
5473 .semantic_facts
5474 .custom_properties
5475 .resolved_ref_names,
5476 vec!["--brand", "--surface"]
5477 );
5478 assert_eq!(
5479 semantic_boundary
5480 .semantic_facts
5481 .custom_properties
5482 .unresolved_ref_names,
5483 vec!["--brand"]
5484 );
5485 }
5486
5487 #[test]
5488 fn semantic_boundary_separates_parser_syntax_from_semantic_resolution() -> Result<(), String> {
5489 let source = r#"@use "./tokens" as tokens;
5490$gap: 1rem;
5491@mixin raised($depth) { box-shadow: 0 0 $depth black; }
5492.btn { color: $gap; @include raised($gap); }
5493.ghost { color: $missing; }
5494"#;
5495 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5496 let summary = super::summarize_semantic_boundary(&sheet);
5497
5498 assert_eq!(
5499 summary.parser_facts.sass.module_use_edges,
5500 vec![ParserIndexSassModuleUseFactV0 {
5501 source: "./tokens".to_string(),
5502 namespace_kind: "alias",
5503 namespace: Some("tokens".to_string()),
5504 }]
5505 );
5506 assert_eq!(
5507 summary.parser_facts.sass.variable_ref_names,
5508 vec!["depth", "gap", "missing"]
5509 );
5510 assert_eq!(
5511 summary
5512 .semantic_facts
5513 .sass
5514 .same_file_resolution
5515 .resolved_variable_ref_names,
5516 vec!["depth", "gap"]
5517 );
5518 assert_eq!(
5519 summary
5520 .semantic_facts
5521 .sass
5522 .same_file_resolution
5523 .unresolved_variable_ref_names,
5524 vec!["missing"]
5525 );
5526 assert_eq!(
5527 summary.semantic_facts.selector_identity.canonical_names,
5528 vec!["btn", "ghost"]
5529 );
5530 assert_eq!(
5531 summary.semantic_facts.sass.selector_symbol_facts,
5532 vec![
5533 ParserIndexSassSelectorSymbolFactV0 {
5534 selector_name: "btn".to_string(),
5535 symbol_kind: "mixin",
5536 name: "raised".to_string(),
5537 namespace: None,
5538 role: "include",
5539 resolution: "resolved",
5540 byte_span: span_after(source, "@include", "raised")?,
5541 range: range_after(source, "@include", "raised")?,
5542 },
5543 ParserIndexSassSelectorSymbolFactV0 {
5544 selector_name: "btn".to_string(),
5545 symbol_kind: "variable",
5546 name: "gap".to_string(),
5547 namespace: None,
5548 role: "reference",
5549 resolution: "resolved",
5550 byte_span: span_after(source, ".btn { color", "$gap")?,
5551 range: range_after(source, ".btn { color", "$gap")?,
5552 },
5553 ParserIndexSassSelectorSymbolFactV0 {
5554 selector_name: "btn".to_string(),
5555 symbol_kind: "variable",
5556 name: "gap".to_string(),
5557 namespace: None,
5558 role: "reference",
5559 resolution: "resolved",
5560 byte_span: span_after(source, "@include raised(", "$gap")?,
5561 range: range_after(source, "@include raised(", "$gap")?,
5562 },
5563 ParserIndexSassSelectorSymbolFactV0 {
5564 selector_name: "ghost".to_string(),
5565 symbol_kind: "variable",
5566 name: "missing".to_string(),
5567 namespace: None,
5568 role: "reference",
5569 resolution: "unresolved",
5570 byte_span: span_after(source, ".ghost { color", "$missing")?,
5571 range: range_after(source, ".ghost { color", "$missing")?,
5572 },
5573 ]
5574 );
5575 Ok(())
5576 }
5577
5578 #[test]
5579 fn semantic_boundary_reports_lossless_cst_span_contract() {
5580 let source = "// heading\n.card { &--primary { color: red; } }\n";
5581 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5582 let summary = super::summarize_semantic_boundary(&sheet);
5583
5584 assert_eq!(
5585 summary.parser_facts.lossless_cst.source_byte_len,
5586 source.len()
5587 );
5588 assert!(summary.parser_facts.lossless_cst.token_count > 0);
5589 assert_eq!(summary.parser_facts.lossless_cst.diagnostic_count, 0);
5590 assert!(
5591 summary
5592 .parser_facts
5593 .lossless_cst
5594 .all_token_spans_within_source
5595 );
5596 assert!(
5597 summary
5598 .parser_facts
5599 .lossless_cst
5600 .all_node_spans_within_source
5601 );
5602 }
5603
5604 #[test]
5605 fn index_summary_does_not_resolve_sass_variables_from_another_local_scope() -> Result<(), String>
5606 {
5607 let source = ".one { $gap: 1rem; }\n.two { color: $gap; }\n";
5608 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5609 let summary = super::summarize_css_modules_intermediate(&sheet);
5610
5611 assert_eq!(
5612 summary.sass.selectors_with_resolved_variable_refs_names,
5613 Vec::<String>::new()
5614 );
5615 assert_eq!(
5616 summary.sass.selectors_with_unresolved_variable_refs_names,
5617 vec!["two"]
5618 );
5619 assert_eq!(
5620 summary
5621 .sass
5622 .same_file_resolution
5623 .resolved_variable_ref_names,
5624 Vec::<String>::new()
5625 );
5626 assert_eq!(
5627 summary
5628 .sass
5629 .same_file_resolution
5630 .unresolved_variable_ref_names,
5631 vec!["gap"]
5632 );
5633 assert_eq!(
5634 summary.sass.selector_symbol_facts,
5635 vec![ParserIndexSassSelectorSymbolFactV0 {
5636 selector_name: "two".to_string(),
5637 symbol_kind: "variable",
5638 name: "gap".to_string(),
5639 namespace: None,
5640 role: "reference",
5641 resolution: "unresolved",
5642 byte_span: span_after(source, ".two", "$gap")?,
5643 range: range_after(source, ".two", "$gap")?,
5644 }]
5645 );
5646 Ok(())
5647 }
5648
5649 #[test]
5650 fn index_summary_tracks_module_qualified_sass_refs_outside_same_file_resolution()
5651 -> Result<(), String> {
5652 let source = r#"@use "./tokens" as tokens;
5653@mixin raised { box-shadow: none; }
5654@function tone($value) { @return $value; }
5655.button {
5656 color: tokens.$gap;
5657 @include tokens.raised;
5658 border-color: tokens.tone(tokens.$gap);
5659}
5660"#;
5661 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5662 let summary = super::summarize_css_modules_intermediate(&sheet);
5663
5664 assert_eq!(summary.sass.variable_ref_names, vec!["gap", "value"]);
5665 assert_eq!(
5666 summary
5667 .sass
5668 .same_file_resolution
5669 .resolved_variable_ref_names,
5670 vec!["value"]
5671 );
5672 assert_eq!(
5673 summary
5674 .sass
5675 .same_file_resolution
5676 .unresolved_variable_ref_names,
5677 Vec::<String>::new()
5678 );
5679 assert_eq!(summary.sass.mixin_include_names, Vec::<String>::new());
5680 assert_eq!(summary.sass.function_call_names, Vec::<String>::new());
5681 assert_eq!(
5682 summary.sass.selectors_with_variable_refs_names,
5683 vec!["button"]
5684 );
5685 assert_eq!(
5686 summary.sass.selectors_with_mixin_includes_names,
5687 vec!["button"]
5688 );
5689 assert_eq!(
5690 summary.sass.selectors_with_function_calls_names,
5691 vec!["button"]
5692 );
5693 assert_eq!(
5694 summary.sass.selector_symbol_facts,
5695 vec![
5696 ParserIndexSassSelectorSymbolFactV0 {
5697 selector_name: "button".to_string(),
5698 symbol_kind: "function",
5699 name: "tone".to_string(),
5700 namespace: Some("tokens".to_string()),
5701 role: "call",
5702 resolution: "external",
5703 byte_span: span_after(source, "border-color:", "tone")?,
5704 range: range_after(source, "border-color:", "tone")?,
5705 },
5706 ParserIndexSassSelectorSymbolFactV0 {
5707 selector_name: "button".to_string(),
5708 symbol_kind: "mixin",
5709 name: "raised".to_string(),
5710 namespace: Some("tokens".to_string()),
5711 role: "include",
5712 resolution: "external",
5713 byte_span: span_after(source, "@include tokens.", "raised")?,
5714 range: range_after(source, "@include tokens.", "raised")?,
5715 },
5716 ParserIndexSassSelectorSymbolFactV0 {
5717 selector_name: "button".to_string(),
5718 symbol_kind: "variable",
5719 name: "gap".to_string(),
5720 namespace: Some("tokens".to_string()),
5721 role: "reference",
5722 resolution: "external",
5723 byte_span: span_after(source, "color: tokens.", "$gap")?,
5724 range: range_after(source, "color: tokens.", "$gap")?,
5725 },
5726 ParserIndexSassSelectorSymbolFactV0 {
5727 selector_name: "button".to_string(),
5728 symbol_kind: "variable",
5729 name: "gap".to_string(),
5730 namespace: Some("tokens".to_string()),
5731 role: "reference",
5732 resolution: "external",
5733 byte_span: span_after(source, "tokens.tone(", "$gap")?,
5734 range: range_after(source, "tokens.tone(", "$gap")?,
5735 },
5736 ]
5737 );
5738 assert_eq!(
5739 summary.sass.module_use_edges,
5740 vec![ParserIndexSassModuleUseFactV0 {
5741 source: "./tokens".to_string(),
5742 namespace_kind: "alias",
5743 namespace: Some("tokens".to_string()),
5744 }]
5745 );
5746 Ok(())
5747 }
5748
5749 #[test]
5750 fn index_summary_reports_sass_symbol_ranges_after_non_ascii_text() -> Result<(), String> {
5751 let source = "$gap: 1rem;\n.btn { content: \"한🙂\"; color: $gap; }\n";
5752 let sheet = parse_stylesheet(StyleLanguage::Scss, source);
5753 let summary = super::summarize_css_modules_intermediate(&sheet);
5754
5755 assert_eq!(
5756 summary.sass.selector_symbol_facts,
5757 vec![ParserIndexSassSelectorSymbolFactV0 {
5758 selector_name: "btn".to_string(),
5759 symbol_kind: "variable",
5760 name: "gap".to_string(),
5761 namespace: None,
5762 role: "reference",
5763 resolution: "resolved",
5764 byte_span: ParserByteSpanV0 { start: 46, end: 50 },
5765 range: ParserRangeV0 {
5766 start: ParserPositionV0 {
5767 line: 1,
5768 character: 30,
5769 },
5770 end: ParserPositionV0 {
5771 line: 1,
5772 character: 34,
5773 },
5774 },
5775 }]
5776 );
5777
5778 Ok(())
5779 }
5780}