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