1use cstree::{
7 Syntax,
8 build::GreenNodeBuilder,
9 green::GreenNode,
10 interning::TokenInterner,
11 syntax::SyntaxNode,
12 text::{TextRange, TextSize},
13};
14use omena_interner::{
15 NameKind, intern_class_name, intern_css_ident, intern_custom_property_name, intern_file_path,
16 intern_keyframes_name, intern_mixin_name, intern_property_name, intern_selector_key,
17};
18pub use omena_syntax::StyleDialect;
19use omena_syntax::SyntaxKind;
20use serde::{Deserialize, Serialize};
21use std::{
22 collections::{BTreeMap, BTreeSet},
23 sync::Arc,
24};
25
26mod public_product;
31pub use public_product::{
32 ParserCanonicalCandidateBundleV0, ParserCanonicalProducerSignalV0, ParserEvaluatorCandidatesV0,
33 ParserIndexSummaryV0, dialect_for_path, summarize_css_modules_intermediate,
34 summarize_parser_canonical_candidate, summarize_parser_canonical_producer_signal,
35 summarize_parser_evaluator_candidates,
36};
37
38const VALUES_L4_MATH_FUNCTION_NAMES: &[&str] = &[
39 "min", "max", "clamp", "round", "mod", "rem", "sin", "cos", "tan", "asin", "acos", "atan",
40 "atan2", "pow", "sqrt", "hypot", "log", "exp", "abs", "sign",
41];
42
43const CSS_COLOR_FUNCTION_NAMES: &[&str] = &[
44 "rgb",
45 "rgba",
46 "hsl",
47 "hsla",
48 "hwb",
49 "lab",
50 "lch",
51 "oklab",
52 "oklch",
53 "color",
54 "color-mix",
55 "device-cmyk",
56 "light-dark",
57 "contrast-color",
58];
59
60const CSS_GRADIENT_FUNCTION_NAMES: &[&str] = &[
61 "linear-gradient",
62 "radial-gradient",
63 "conic-gradient",
64 "repeating-linear-gradient",
65 "repeating-radial-gradient",
66 "repeating-conic-gradient",
67];
68
69const CSS_TRANSFORM_FUNCTION_NAMES: &[&str] = &[
70 "matrix",
71 "matrix3d",
72 "translate",
73 "translate3d",
74 "translateX",
75 "translateY",
76 "translateZ",
77 "scale",
78 "scale3d",
79 "scaleX",
80 "scaleY",
81 "scaleZ",
82 "rotate",
83 "rotate3d",
84 "rotateX",
85 "rotateY",
86 "rotateZ",
87 "skew",
88 "skewX",
89 "skewY",
90 "perspective",
91];
92
93const CSS_FILTER_FUNCTION_NAMES: &[&str] = &[
94 "blur",
95 "brightness",
96 "contrast",
97 "drop-shadow",
98 "grayscale",
99 "hue-rotate",
100 "invert",
101 "opacity",
102 "saturate",
103 "sepia",
104];
105
106const CSS_IMAGE_FUNCTION_NAMES: &[&str] = &["image", "image-set", "cross-fade", "element", "paint"];
107
108const CSS_SHAPE_FUNCTION_NAMES: &[&str] = &[
109 "path", "shape", "ray", "inset", "circle", "ellipse", "polygon",
110];
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
113#[serde(rename_all = "camelCase")]
114pub struct ParserByteSpanV0 {
115 pub start: usize,
116 pub end: usize,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Default)]
120#[serde(rename_all = "camelCase")]
121pub struct ParserPositionV0 {
122 pub line: usize,
123 pub character: usize,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Default)]
127#[serde(rename_all = "camelCase")]
128pub struct ParserRangeV0 {
129 pub start: ParserPositionV0,
130 pub end: ParserPositionV0,
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum StyleLanguage {
135 Css,
136 Scss,
137 Less,
138}
139
140impl StyleLanguage {
141 pub fn from_module_path(path: &str) -> Option<Self> {
142 if path.ends_with(".module.css") || path.ends_with(".css") {
143 Some(Self::Css)
144 } else if path.ends_with(".module.scss") || path.ends_with(".scss") {
145 Some(Self::Scss)
146 } else if path.ends_with(".module.less") || path.ends_with(".less") {
147 Some(Self::Less)
148 } else {
149 None
150 }
151 }
152}
153
154#[derive(Debug, Clone)]
155pub struct ParseResult {
156 green: GreenNode,
157 interner: Option<Arc<TokenInterner>>,
158 errors: Vec<ParseError>,
159 token_count: usize,
160 dialect: StyleDialect,
161}
162
163impl PartialEq for ParseResult {
164 fn eq(&self, other: &Self) -> bool {
165 self.green == other.green
166 && self.errors == other.errors
167 && self.token_count == other.token_count
168 && self.dialect == other.dialect
169 }
170}
171
172impl Eq for ParseResult {}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct LexResult {
176 tokens: Vec<LexedToken>,
177 errors: Vec<ParseError>,
178 dialect: StyleDialect,
179}
180
181impl LexResult {
182 pub fn tokens(&self) -> &[LexedToken] {
183 &self.tokens
184 }
185
186 pub fn errors(&self) -> &[ParseError] {
187 &self.errors
188 }
189
190 pub fn dialect(&self) -> StyleDialect {
191 self.dialect
192 }
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub struct LexedToken {
197 pub kind: SyntaxKind,
198 pub range: TextRange,
199 pub text: String,
200}
201
202impl ParseResult {
203 pub fn green(&self) -> &GreenNode {
204 &self.green
205 }
206
207 pub fn syntax(&self) -> SyntaxNode<SyntaxKind> {
208 if let Some(interner) = &self.interner {
209 return SyntaxNode::new_root_with_resolver(self.green.clone(), Arc::clone(interner))
210 .syntax()
211 .clone();
212 }
213 SyntaxNode::new_root(self.green.clone())
214 }
215
216 pub fn source_text(&self) -> Option<String> {
217 let syntax = self.syntax();
218 syntax
219 .try_resolved()
220 .map(|resolved| resolved.text().to_string())
221 }
222
223 pub fn errors(&self) -> &[ParseError] {
224 &self.errors
225 }
226
227 pub fn token_count(&self) -> usize {
228 self.token_count
229 }
230
231 pub fn dialect(&self) -> StyleDialect {
232 self.dialect
233 }
234
235 pub fn cst(&self) -> ParsedCst {
236 ParsedCst::new(self.syntax())
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct ParseError {
242 pub code: ParseErrorCode,
243 pub range: TextRange,
244 pub message: &'static str,
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum ParseErrorCode {
249 UnterminatedBlockComment,
250 UnterminatedString,
251 UnexpectedCharacter,
252 ExpectedSelectorName,
253 UnterminatedAttributeSelector,
254 ExpectedValue,
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub enum ParseEntryPoint {
259 Stylesheet,
260 RuleList,
261 Rule,
262 DeclarationList,
263 Declaration,
264 Value,
265 ComponentValue,
266 ComponentValueList,
267 CommaSeparatedComponentValueList,
268 SimpleBlock,
269}
270
271#[derive(Debug, Clone, PartialEq, Eq)]
272pub struct ParserBoundarySummary {
273 pub product: &'static str,
274 pub tree_model: &'static str,
275 pub parser_track: &'static str,
276 pub dialect_count: usize,
277 pub shared_name_kind_count: usize,
278 pub ready_surfaces: Vec<&'static str>,
279 pub not_ready_surfaces: Vec<&'static str>,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
283pub struct ParserSemanticNameConsumptionSummaryV0 {
284 pub product: &'static str,
285 pub dialect: StyleDialect,
286 pub semantic_name_count: usize,
287 pub interned_name_count: usize,
288 pub invalid_name_count: usize,
289 pub class_name_count: usize,
290 pub css_ident_count: usize,
291 pub property_name_count: usize,
292 pub selector_key_count: usize,
293 pub custom_property_name_count: usize,
294 pub keyframes_name_count: usize,
295 pub mixin_name_count: usize,
296 pub file_path_count: usize,
297 pub ready_surfaces: Vec<&'static str>,
298}
299
300#[derive(Debug, Clone, PartialEq, Eq)]
301pub struct ParserCstEquivalenceSummaryV0 {
302 pub product: &'static str,
303 pub dialect: StyleDialect,
304 pub root_kind: SyntaxKind,
305 pub parser_node_count: usize,
306 pub parser_token_count: usize,
307 pub typed_wrapper_count: usize,
308 pub source_text_round_trip_ready: bool,
309 pub syntax_kind_round_trip_ready: bool,
310 pub zero_unknown_kind_ready: bool,
311 pub typed_cst_wrapper_ready: bool,
312 pub ready_surfaces: Vec<&'static str>,
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316pub struct ParserPrattValueCoverageSummaryV0 {
317 pub product: &'static str,
318 pub infix_operator_kinds: Vec<SyntaxKind>,
319 pub prefix_operator_kinds: Vec<SyntaxKind>,
320 pub value_expression_node_kinds: Vec<SyntaxKind>,
321 pub specialized_function_family_count: usize,
322 pub css_values_l4_math_function_count: usize,
323 pub css_color_function_count: usize,
324 pub ready_surfaces: Vec<&'static str>,
325 pub next_surfaces: Vec<&'static str>,
326}
327
328#[derive(Debug, Clone, PartialEq, Eq)]
329pub struct ParserRecursiveDescentCoverageSummaryV0 {
330 pub product: &'static str,
331 pub dialect_count: usize,
332 pub entry_point_count: usize,
333 pub selector_surface_count: usize,
334 pub at_rule_surface_count: usize,
335 pub dialect_extension_surface_count: usize,
336 pub recovery_surface_count: usize,
337 pub ready_surfaces: Vec<&'static str>,
338 pub next_surfaces: Vec<&'static str>,
339}
340
341#[derive(Debug, Clone, PartialEq, Eq)]
342struct ParserSemanticNameCandidateV0 {
343 kind: NameKind,
344 text: String,
345}
346
347#[derive(Debug, Clone, PartialEq, Eq)]
348pub struct ParsedStyleFacts {
349 pub product: &'static str,
350 pub dialect: StyleDialect,
351 pub selector_count: usize,
352 pub selectors: Vec<ParsedSelectorFact>,
353 pub variable_count: usize,
354 pub variables: Vec<ParsedVariableFact>,
355 pub sass_symbol_count: usize,
356 pub sass_symbols: Vec<ParsedSassSymbolFact>,
357 pub sass_include_count: usize,
358 pub sass_includes: Vec<ParsedSassIncludeFact>,
359 pub sass_module_edge_count: usize,
360 pub sass_module_edges: Vec<ParsedSassModuleEdgeFact>,
361 pub extend_target_count: usize,
362 pub extend_targets: Vec<ParsedExtendTargetFact>,
363 pub animation_count: usize,
364 pub animations: Vec<ParsedAnimationFact>,
365 pub css_module_value_count: usize,
366 pub css_module_values: Vec<ParsedCssModuleValueFact>,
367 pub css_module_value_import_edge_count: usize,
368 pub css_module_value_import_edges: Vec<ParsedCssModuleValueImportEdgeFact>,
369 pub css_module_value_definition_edge_count: usize,
370 pub css_module_value_definition_edges: Vec<ParsedCssModuleValueDefinitionEdgeFact>,
371 pub css_module_composes_count: usize,
372 pub css_module_composes: Vec<ParsedCssModuleComposesFact>,
373 pub css_module_composes_edge_count: usize,
374 pub css_module_composes_edges: Vec<ParsedCssModuleComposesEdgeFact>,
375 pub icss_count: usize,
376 pub icss: Vec<ParsedIcssFact>,
377 pub icss_import_edge_count: usize,
378 pub icss_import_edges: Vec<ParsedIcssImportEdgeFact>,
379 pub icss_export_edge_count: usize,
380 pub icss_export_edges: Vec<ParsedIcssExportEdgeFact>,
381 pub at_rule_count: usize,
382 pub at_rules: Vec<ParsedAtRuleFact>,
383 pub error_count: usize,
384}
385
386#[derive(Debug, Clone, PartialEq, Eq)]
387pub struct ParsedSelectorFact {
388 pub kind: ParsedSelectorFactKind,
389 pub name: String,
390 pub range: TextRange,
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
394pub enum ParsedSelectorFactKind {
395 Class,
396 Id,
397 Placeholder,
398}
399
400#[derive(Debug, Clone, PartialEq, Eq)]
401pub struct ParsedVariableFact {
402 pub kind: ParsedVariableFactKind,
403 pub name: String,
404 pub range: TextRange,
405 pub has_fallback: bool,
410}
411
412#[derive(Debug, Clone, Copy, PartialEq, Eq)]
413pub enum ParsedVariableFactKind {
414 ScssDeclaration,
415 ScssReference,
416 LessDeclaration,
417 LessReference,
418 CustomPropertyDeclaration,
419 CustomPropertyReference,
420}
421
422#[derive(Debug, Clone, PartialEq, Eq)]
423pub struct ParsedSassSymbolFact {
424 pub kind: ParsedSassSymbolFactKind,
425 pub symbol_kind: &'static str,
426 pub name: String,
427 pub role: &'static str,
428 pub namespace: Option<String>,
429 pub range: TextRange,
430}
431
432#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
433pub enum ParsedSassSymbolFactKind {
434 VariableDeclaration,
435 VariableReference,
436 MixinDeclaration,
437 MixinInclude,
438 FunctionDeclaration,
439 FunctionCall,
440}
441
442#[derive(Debug, Clone, PartialEq, Eq)]
443pub struct ParsedSassIncludeFact {
444 pub name: String,
445 pub namespace: Option<String>,
446 pub params: String,
447 pub range: TextRange,
448}
449
450#[derive(Debug, Clone, PartialEq, Eq)]
451pub struct ParsedSassModuleEdgeFact {
452 pub kind: ParsedSassModuleEdgeFactKind,
453 pub source: String,
454 pub namespace_kind: Option<&'static str>,
455 pub namespace: Option<String>,
456 pub forward_prefix: Option<String>,
457 pub visibility_filter_kind: Option<&'static str>,
458 pub visibility_filter_names: Vec<String>,
459 pub media_qualified: bool,
466 pub range: TextRange,
467}
468
469#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
470pub enum ParsedSassModuleEdgeFactKind {
471 Use,
472 Forward,
473 Import,
474}
475
476#[derive(Debug, Clone, PartialEq, Eq)]
482pub struct ParsedExtendTargetFact {
483 pub kind: ParsedExtendTargetFactKind,
484 pub name: String,
485 pub optional: bool,
486 pub range: TextRange,
487}
488
489#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
490pub enum ParsedExtendTargetFactKind {
491 Class,
492 Placeholder,
493}
494
495#[derive(Debug, Clone, PartialEq, Eq)]
496pub struct ParsedAnimationFact {
497 pub kind: ParsedAnimationFactKind,
498 pub name: String,
499 pub range: TextRange,
500}
501
502#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
503pub enum ParsedAnimationFactKind {
504 KeyframesDeclaration,
505 AnimationNameReference,
506}
507
508#[derive(Debug, Clone, PartialEq, Eq)]
509pub struct ParsedCssModuleValueFact {
510 pub kind: ParsedCssModuleValueFactKind,
511 pub name: String,
512 pub range: TextRange,
513}
514
515#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
516pub enum ParsedCssModuleValueFactKind {
517 Definition,
518 Reference,
519 ImportSource,
520}
521
522#[derive(Debug, Clone, PartialEq, Eq)]
523pub struct ParsedCssModuleValueImportEdgeFact {
524 pub remote_name: String,
525 pub local_name: String,
526 pub import_source: String,
527 pub local_range: TextRange,
528 pub remote_range: TextRange,
529 pub range: TextRange,
530}
531
532#[derive(Debug, Clone, PartialEq, Eq)]
533pub struct ParsedCssModuleValueDefinitionEdgeFact {
534 pub definition_name: String,
535 pub reference_names: Vec<String>,
536 pub range: TextRange,
537}
538
539#[derive(Debug, Clone, PartialEq, Eq)]
540pub struct ParsedCssModuleComposesFact {
541 pub kind: ParsedCssModuleComposesFactKind,
542 pub name: String,
543 pub range: TextRange,
544}
545
546#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
547pub enum ParsedCssModuleComposesFactKind {
548 Target,
549 ImportSource,
550}
551
552#[derive(Debug, Clone, PartialEq, Eq)]
553pub struct ParsedCssModuleComposesEdgeFact {
554 pub kind: ParsedCssModuleComposesEdgeKind,
555 pub owner_selector_names: Vec<String>,
556 pub target_names: Vec<String>,
557 pub import_source: Option<String>,
558 pub range: TextRange,
559}
560
561#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
562pub enum ParsedCssModuleComposesEdgeKind {
563 Local,
564 Global,
565 External,
566}
567
568#[derive(Debug, Clone, PartialEq, Eq)]
569pub struct ParsedIcssFact {
570 pub kind: ParsedIcssFactKind,
571 pub name: String,
572 pub range: TextRange,
573}
574
575#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
576pub enum ParsedIcssFactKind {
577 ExportName,
578 ImportLocalName,
579 ImportRemoteName,
580 ImportSource,
581}
582
583#[derive(Debug, Clone, PartialEq, Eq)]
584pub struct ParsedIcssImportEdgeFact {
585 pub local_name: String,
586 pub remote_name: String,
587 pub import_source: String,
588 pub range: TextRange,
589}
590
591#[derive(Debug, Clone, PartialEq, Eq)]
592pub struct ParsedIcssExportEdgeFact {
593 pub export_name: String,
594 pub reference_names: Vec<String>,
595 pub range: TextRange,
596}
597
598#[derive(Debug, Clone, PartialEq, Eq)]
599pub struct ParsedAtRuleFact {
600 pub name: String,
601 pub node_kind: Option<SyntaxKind>,
602 pub range: TextRange,
603}
604
605#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
606#[serde(rename_all = "camelCase")]
607pub struct OmenaParserStyleFactsSummaryV0 {
608 pub schema_version: &'static str,
609 pub product: &'static str,
610 pub dialect: &'static str,
611 pub class_selector_names: Vec<String>,
612 pub id_selector_names: Vec<String>,
613 pub placeholder_selector_names: Vec<String>,
614 pub keyframe_names: Vec<String>,
615 pub animation_reference_names: Vec<String>,
616 pub css_module_value_definition_names: Vec<String>,
617 pub css_module_value_reference_names: Vec<String>,
618 pub css_module_value_import_sources: Vec<String>,
619 pub css_module_value_import_edges: Vec<OmenaParserCssModuleValueImportEdgeFactV0>,
620 pub css_module_value_definition_edges: Vec<OmenaParserCssModuleValueDefinitionEdgeFactV0>,
621 pub css_module_composes_target_names: Vec<String>,
622 pub css_module_composes_import_sources: Vec<String>,
623 pub css_module_composes_edges: Vec<OmenaParserCssModuleComposesEdgeFactV0>,
624 pub icss_export_names: Vec<String>,
625 pub icss_import_local_names: Vec<String>,
626 pub icss_import_remote_names: Vec<String>,
627 pub icss_import_sources: Vec<String>,
628 pub icss_import_edges: Vec<OmenaParserIcssImportEdgeFactV0>,
629 pub icss_export_edges: Vec<OmenaParserIcssExportEdgeFactV0>,
630 pub variable_names: Vec<String>,
631 pub sass_symbol_declaration_names: Vec<String>,
632 pub sass_symbol_reference_names: Vec<String>,
633 pub sass_symbol_facts: Vec<OmenaParserSassSymbolFactV0>,
634 pub sass_symbol_resolution: OmenaParserSassSymbolResolutionV0,
635 pub sass_module_use_sources: Vec<String>,
636 pub sass_module_forward_sources: Vec<String>,
637 pub sass_module_import_sources: Vec<String>,
638 pub sass_module_edges: Vec<OmenaParserSassModuleEdgeFactV0>,
639 pub custom_property_names: Vec<String>,
640 pub custom_property_decl_names: Vec<String>,
641 pub custom_property_ref_names: Vec<String>,
642 pub at_rule_names: Vec<String>,
643 pub parser_error_count: usize,
644}
645
646#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
647#[serde(rename_all = "camelCase")]
648pub struct OmenaParserCssModuleValueImportEdgeFactV0 {
649 pub remote_name: String,
650 pub local_name: String,
651 pub import_source: String,
652}
653
654#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
655#[serde(rename_all = "camelCase")]
656pub struct OmenaParserCssModuleValueDefinitionEdgeFactV0 {
657 pub definition_name: String,
658 pub reference_names: Vec<String>,
659}
660
661#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
662#[serde(rename_all = "camelCase")]
663pub struct OmenaParserCssModuleComposesEdgeFactV0 {
664 pub kind: &'static str,
665 pub owner_selector_names: Vec<String>,
666 pub target_names: Vec<String>,
667 pub import_source: Option<String>,
668}
669
670#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
671#[serde(rename_all = "camelCase")]
672pub struct OmenaParserIcssImportEdgeFactV0 {
673 pub local_name: String,
674 pub remote_name: String,
675 pub import_source: String,
676}
677
678#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
679#[serde(rename_all = "camelCase")]
680pub struct OmenaParserIcssExportEdgeFactV0 {
681 pub export_name: String,
682 pub reference_names: Vec<String>,
683}
684
685#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
686#[serde(rename_all = "camelCase")]
687pub struct OmenaParserSassSymbolFactV0 {
688 pub kind: &'static str,
689 pub symbol_kind: &'static str,
690 pub name: String,
691 pub role: &'static str,
692 pub namespace: Option<String>,
693}
694
695#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
696#[serde(rename_all = "camelCase")]
697pub struct OmenaParserSassModuleEdgeFactV0 {
698 pub kind: &'static str,
699 pub source: String,
700 pub namespace_kind: Option<&'static str>,
701 pub namespace: Option<String>,
702 pub visibility_filter_kind: Option<&'static str>,
703 pub visibility_filter_names: Vec<String>,
704}
705
706#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
707#[serde(rename_all = "camelCase")]
708pub struct OmenaParserSassSymbolResolutionV0 {
709 pub schema_version: &'static str,
710 pub product: &'static str,
711 pub resolution_scope: &'static str,
712 pub declaration_count: usize,
713 pub reference_count: usize,
714 pub resolved_reference_count: usize,
715 pub unresolved_reference_count: usize,
716 pub edges: Vec<OmenaParserSassSymbolResolutionEdgeV0>,
717 pub capabilities: OmenaParserSassSymbolResolutionCapabilitiesV0,
718}
719
720#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
721#[serde(rename_all = "camelCase")]
722pub struct OmenaParserSassSymbolResolutionEdgeV0 {
723 pub symbol_kind: &'static str,
724 pub name: String,
725 pub namespace: Option<String>,
726 pub reference_kind: &'static str,
727 pub reference_role: &'static str,
728 pub reference_source_order: usize,
729 pub declaration_kind: Option<&'static str>,
730 pub declaration_source_order: Option<usize>,
731 pub status: &'static str,
732}
733
734#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
735#[serde(rename_all = "camelCase")]
736pub struct OmenaParserSassSymbolResolutionCapabilitiesV0 {
737 pub same_file_lexical_resolution_ready: bool,
738 pub declaration_before_reference_ready: bool,
739 pub unresolved_reference_reporting_ready: bool,
740 pub cross_file_module_resolution_ready: bool,
741}
742
743#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
744#[serde(rename_all = "camelCase")]
745pub struct OmenaParserLexSummaryV0 {
746 pub schema_version: &'static str,
747 pub product: &'static str,
748 pub dialect: &'static str,
749 pub tokens: Vec<OmenaParserLexTokenV0>,
750 pub parser_error_count: usize,
751}
752
753#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
754#[serde(rename_all = "camelCase")]
755pub struct OmenaParserLexTokenV0 {
756 pub kind: String,
757 pub text: String,
758 pub start: usize,
759 pub end: usize,
760}
761
762#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
763#[serde(rename_all = "camelCase")]
764pub struct OmenaParserParityLiteSummaryV0 {
765 pub schema_version: &'static str,
766 pub language: &'static str,
767 pub selector_names: Vec<String>,
768 pub keyframes_names: Vec<String>,
769 pub value_decl_names: Vec<String>,
770 pub diagnostic_count: usize,
771 pub rule_count: usize,
772 pub declaration_count: usize,
773 pub grouped_selector_count: usize,
774 pub max_nesting_depth: usize,
775 pub at_rule_kind_counts: OmenaParserAtRuleKindCountsV0,
776 pub declaration_kind_counts: OmenaParserDeclarationKindCountsV0,
777}
778
779#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
780#[serde(rename_all = "camelCase")]
781pub struct OmenaParserAtRuleKindCountsV0 {
782 pub media: usize,
783 pub supports: usize,
784 pub layer: usize,
785 pub keyframes: usize,
786 pub value: usize,
787 pub at_root: usize,
788 pub generic: usize,
789}
790
791#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
792#[serde(rename_all = "camelCase")]
793pub struct OmenaParserDeclarationKindCountsV0 {
794 pub composes: usize,
795 pub animation: usize,
796 pub animation_name: usize,
797 pub generic: usize,
798}
799
800#[derive(Debug, Clone, PartialEq, Eq)]
801pub struct ParsedCst {
802 root: SyntaxNode<SyntaxKind>,
803}
804
805impl ParsedCst {
806 pub fn new(root: SyntaxNode<SyntaxKind>) -> Self {
807 Self { root }
808 }
809
810 pub fn root(&self) -> &SyntaxNode<SyntaxKind> {
811 &self.root
812 }
813
814 pub fn stylesheet(&self) -> Option<StylesheetCstNode> {
815 self.first_node(StylesheetCstNode::cast)
816 }
817
818 pub fn rules(&self) -> Vec<RuleCstNode> {
819 self.nodes(RuleCstNode::cast)
820 }
821
822 pub fn selectors(&self) -> Vec<SelectorCstNode> {
823 self.nodes(SelectorCstNode::cast)
824 }
825
826 pub fn declarations(&self) -> Vec<DeclarationCstNode> {
827 self.nodes(DeclarationCstNode::cast)
828 }
829
830 pub fn declaration_lists(&self) -> Vec<DeclarationListCstNode> {
831 self.nodes(DeclarationListCstNode::cast)
832 }
833
834 pub fn values(&self) -> Vec<ValueCstNode> {
835 self.nodes(ValueCstNode::cast)
836 }
837
838 pub fn component_values(&self) -> Vec<ComponentValueCstNode> {
839 self.nodes(ComponentValueCstNode::cast)
840 }
841
842 pub fn simple_blocks(&self) -> Vec<SimpleBlockCstNode> {
843 self.nodes(SimpleBlockCstNode::cast)
844 }
845
846 pub fn component_value_lists(&self) -> Vec<ComponentValueListCstNode> {
847 self.nodes(ComponentValueListCstNode::cast)
848 }
849
850 pub fn comma_separated_component_value_lists(
851 &self,
852 ) -> Vec<CommaSeparatedComponentValueListCstNode> {
853 self.nodes(CommaSeparatedComponentValueListCstNode::cast)
854 }
855
856 pub fn custom_property_values(&self) -> Vec<CustomPropertyValueCstNode> {
857 self.nodes(CustomPropertyValueCstNode::cast)
858 }
859
860 pub fn at_rules(&self) -> Vec<AtRuleCstNode> {
861 self.nodes(AtRuleCstNode::cast)
862 }
863
864 pub fn bogus_nodes(&self) -> Vec<BogusCstNode> {
865 self.nodes(BogusCstNode::cast)
866 }
867
868 pub fn has_bogus_nodes(&self) -> bool {
869 self.first_node(BogusCstNode::cast).is_some()
870 }
871
872 fn first_node<T>(&self, cast: impl Fn(SyntaxNode<SyntaxKind>) -> Option<T>) -> Option<T> {
873 let mut nodes = Vec::new();
874 collect_typed_nodes(&self.root, &cast, &mut nodes);
875 nodes.into_iter().next()
876 }
877
878 fn nodes<T>(&self, cast: impl Fn(SyntaxNode<SyntaxKind>) -> Option<T>) -> Vec<T> {
879 let mut nodes = Vec::new();
880 collect_typed_nodes(&self.root, &cast, &mut nodes);
881 nodes
882 }
883}
884
885pub trait TypedCstNode: Sized {
886 fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self>;
887 fn syntax(&self) -> &SyntaxNode<SyntaxKind>;
888
889 fn kind(&self) -> SyntaxKind {
890 self.syntax().kind()
891 }
892
893 fn text_range(&self) -> TextRange {
894 self.syntax().text_range()
895 }
896
897 fn into_syntax(self) -> SyntaxNode<SyntaxKind>;
898}
899
900macro_rules! typed_cst_node {
901 ($name:ident, $kind:expr) => {
902 #[derive(Debug, Clone, PartialEq, Eq)]
903 pub struct $name {
904 syntax: SyntaxNode<SyntaxKind>,
905 }
906
907 impl $name {
908 pub const KIND: SyntaxKind = $kind;
909 }
910
911 impl TypedCstNode for $name {
912 fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self> {
913 (syntax.kind() == Self::KIND).then_some(Self { syntax })
914 }
915
916 fn syntax(&self) -> &SyntaxNode<SyntaxKind> {
917 &self.syntax
918 }
919
920 fn into_syntax(self) -> SyntaxNode<SyntaxKind> {
921 self.syntax
922 }
923 }
924 };
925}
926
927typed_cst_node!(StylesheetCstNode, SyntaxKind::Stylesheet);
928typed_cst_node!(RuleCstNode, SyntaxKind::Rule);
929typed_cst_node!(SelectorCstNode, SyntaxKind::Selector);
930typed_cst_node!(DeclarationCstNode, SyntaxKind::Declaration);
931typed_cst_node!(DeclarationListCstNode, SyntaxKind::DeclarationList);
932typed_cst_node!(ValueCstNode, SyntaxKind::Value);
933typed_cst_node!(ComponentValueCstNode, SyntaxKind::ComponentValue);
934typed_cst_node!(SimpleBlockCstNode, SyntaxKind::SimpleBlock);
935typed_cst_node!(ComponentValueListCstNode, SyntaxKind::ComponentValueList);
936typed_cst_node!(
937 CommaSeparatedComponentValueListCstNode,
938 SyntaxKind::CommaSeparatedComponentValueList
939);
940typed_cst_node!(CustomPropertyValueCstNode, SyntaxKind::CustomPropertyValue);
941
942#[derive(Debug, Clone, PartialEq, Eq)]
943pub struct AtRuleCstNode {
944 syntax: SyntaxNode<SyntaxKind>,
945}
946
947#[derive(Debug, Clone, PartialEq, Eq)]
948pub struct BogusCstNode {
949 syntax: SyntaxNode<SyntaxKind>,
950}
951
952impl TypedCstNode for AtRuleCstNode {
953 fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self> {
954 is_at_rule_node_kind(syntax.kind()).then_some(Self { syntax })
955 }
956
957 fn syntax(&self) -> &SyntaxNode<SyntaxKind> {
958 &self.syntax
959 }
960
961 fn into_syntax(self) -> SyntaxNode<SyntaxKind> {
962 self.syntax
963 }
964}
965
966impl TypedCstNode for BogusCstNode {
967 fn cast(syntax: SyntaxNode<SyntaxKind>) -> Option<Self> {
968 syntax.kind().is_bogus().then_some(Self { syntax })
969 }
970
971 fn syntax(&self) -> &SyntaxNode<SyntaxKind> {
972 &self.syntax
973 }
974
975 fn into_syntax(self) -> SyntaxNode<SyntaxKind> {
976 self.syntax
977 }
978}
979
980pub fn is_at_rule_node_kind(kind: SyntaxKind) -> bool {
981 matches!(
982 kind,
983 SyntaxKind::AtRule
984 | SyntaxKind::MediaRule
985 | SyntaxKind::SupportsRule
986 | SyntaxKind::ContainerRule
987 | SyntaxKind::LayerRule
988 | SyntaxKind::ScopeRule
989 | SyntaxKind::KeyframesRule
990 | SyntaxKind::FontFaceRule
991 | SyntaxKind::PageRule
992 | SyntaxKind::NamespaceRule
993 | SyntaxKind::ImportRule
994 | SyntaxKind::CharsetRule
995 | SyntaxKind::PropertyRule
996 | SyntaxKind::StartingStyleRule
997 | SyntaxKind::PageMarginRule
998 | SyntaxKind::WhenRule
999 | SyntaxKind::ElseRule
1000 | SyntaxKind::CounterStyleRule
1001 | SyntaxKind::FontPaletteValuesRule
1002 | SyntaxKind::ColorProfileRule
1003 | SyntaxKind::PositionTryRule
1004 | SyntaxKind::FontFeatureValuesRule
1005 | SyntaxKind::FontFeatureValuesStylisticRule
1006 | SyntaxKind::FontFeatureValuesStylesetRule
1007 | SyntaxKind::FontFeatureValuesCharacterVariantRule
1008 | SyntaxKind::FontFeatureValuesSwashRule
1009 | SyntaxKind::FontFeatureValuesOrnamentsRule
1010 | SyntaxKind::FontFeatureValuesAnnotationRule
1011 | SyntaxKind::FontFeatureValuesHistoricalFormsRule
1012 | SyntaxKind::ViewTransitionRule
1013 | SyntaxKind::NestRule
1014 | SyntaxKind::CustomMediaRule
1015 | SyntaxKind::ScssUseRule
1016 | SyntaxKind::ScssForwardRule
1017 | SyntaxKind::ScssMixinDeclaration
1018 | SyntaxKind::ScssIncludeRule
1019 | SyntaxKind::ScssFunctionDeclaration
1020 | SyntaxKind::ScssReturnRule
1021 | SyntaxKind::ScssAtRootRule
1022 | SyntaxKind::ScssErrorRule
1023 | SyntaxKind::ScssWarnRule
1024 | SyntaxKind::ScssDebugRule
1025 | SyntaxKind::ScssContentRule
1026 )
1027}
1028
1029fn collect_typed_nodes<T>(
1030 node: &SyntaxNode<SyntaxKind>,
1031 cast: &impl Fn(SyntaxNode<SyntaxKind>) -> Option<T>,
1032 nodes: &mut Vec<T>,
1033) {
1034 if let Some(typed) = cast(node.clone()) {
1035 nodes.push(typed);
1036 }
1037 for child in node.children() {
1038 collect_typed_nodes(child, cast, nodes);
1039 }
1040}
1041
1042#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1043pub struct TokenSet {
1044 kinds: &'static [SyntaxKind],
1045}
1046
1047impl TokenSet {
1048 pub const fn new(kinds: &'static [SyntaxKind]) -> Self {
1049 Self { kinds }
1050 }
1051
1052 pub fn contains(self, kind: SyntaxKind) -> bool {
1053 self.kinds.contains(&kind)
1054 }
1055
1056 pub fn len(self) -> usize {
1057 self.kinds.len()
1058 }
1059
1060 pub fn is_empty(self) -> bool {
1061 self.kinds.is_empty()
1062 }
1063}
1064
1065pub const RECOVERY_TOP: TokenSet = TokenSet::new(&[
1066 SyntaxKind::AtKeyword,
1067 SyntaxKind::Dot,
1068 SyntaxKind::Hash,
1069 SyntaxKind::RightBrace,
1070 SyntaxKind::Semicolon,
1071]);
1072
1073pub const RECOVERY_DECLARATION: TokenSet =
1074 TokenSet::new(&[SyntaxKind::Semicolon, SyntaxKind::RightBrace]);
1075
1076pub const RECOVERY_SELECTOR: TokenSet = TokenSet::new(&[
1077 SyntaxKind::Comma,
1078 SyntaxKind::LeftBrace,
1079 SyntaxKind::RightBrace,
1080]);
1081
1082pub trait DialectExtension {
1083 fn dialect(&self) -> StyleDialect;
1084
1085 fn classify_variable_token(&self, text: &str) -> Option<SyntaxKind> {
1086 match self.dialect() {
1087 StyleDialect::Css => None,
1088 StyleDialect::Scss | StyleDialect::Sass if text.starts_with('$') => {
1089 Some(SyntaxKind::ScssVariable)
1090 }
1091 StyleDialect::Less if text.starts_with('@') => Some(SyntaxKind::LessVariable),
1092 StyleDialect::Scss | StyleDialect::Sass | StyleDialect::Less => None,
1093 }
1094 }
1095}
1096
1097#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1098pub struct BuiltinDialectExtension {
1099 dialect: StyleDialect,
1100}
1101
1102impl BuiltinDialectExtension {
1103 pub const fn new(dialect: StyleDialect) -> Self {
1104 Self { dialect }
1105 }
1106}
1107
1108impl DialectExtension for BuiltinDialectExtension {
1109 fn dialect(&self) -> StyleDialect {
1110 self.dialect
1111 }
1112}
1113
1114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1115struct Token<'text> {
1116 kind: SyntaxKind,
1117 text: &'text str,
1118 range: TextRange,
1119}
1120
1121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1122struct AtRuleSpec {
1123 node_kind: SyntaxKind,
1124 block_kind: AtRuleBlockKind,
1125}
1126
1127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1128enum AtRuleBlockKind {
1129 GroupRuleList,
1130 DeclarationList,
1131 Keyframes,
1132 Raw,
1133}
1134
1135pub fn parse(text: &str, dialect: StyleDialect) -> ParseResult {
1136 parse_entry_point(text, dialect, ParseEntryPoint::Stylesheet)
1137}
1138
1139pub fn parse_entry_point(
1140 text: &str,
1141 dialect: StyleDialect,
1142 entry_point: ParseEntryPoint,
1143) -> ParseResult {
1144 let extension = BuiltinDialectExtension::new(dialect);
1145 parse_entry_point_with_extension(text, &extension, entry_point)
1146}
1147
1148pub fn lex(text: &str, dialect: StyleDialect) -> LexResult {
1149 let extension = BuiltinDialectExtension::new(dialect);
1150 lex_with_extension(text, &extension)
1151}
1152
1153pub fn lex_with_extension(text: &str, extension: &impl DialectExtension) -> LexResult {
1154 let (tokens, errors) = tokenize(text, extension);
1155 LexResult {
1156 tokens: tokens
1157 .into_iter()
1158 .map(|token| LexedToken {
1159 kind: token.kind,
1160 range: token.range,
1161 text: public_token_text(token.text),
1162 })
1163 .collect(),
1164 errors,
1165 dialect: extension.dialect(),
1166 }
1167}
1168
1169pub fn parse_with_extension(text: &str, extension: &impl DialectExtension) -> ParseResult {
1170 parse_entry_point_with_extension(text, extension, ParseEntryPoint::Stylesheet)
1171}
1172
1173pub fn parse_entry_point_with_extension(
1174 text: &str,
1175 extension: &impl DialectExtension,
1176 entry_point: ParseEntryPoint,
1177) -> ParseResult {
1178 let (tokens, errors) = tokenize(text, extension);
1179 let token_count = tokens.len();
1180 let mut parser = Parser::new(tokens, errors, extension.dialect());
1181 let (green, interner) = parser.parse_entry_point(entry_point);
1182
1183 ParseResult {
1184 green,
1185 interner,
1186 errors: parser.into_errors(),
1187 token_count,
1188 dialect: extension.dialect(),
1189 }
1190}
1191
1192pub fn collect_style_facts(text: &str, dialect: StyleDialect) -> ParsedStyleFacts {
1193 let extension = BuiltinDialectExtension::new(dialect);
1194 collect_style_facts_with_extension(text, &extension)
1195}
1196
1197pub fn summarize_omena_parser_style_facts(
1198 style_source: &str,
1199 dialect: StyleDialect,
1200) -> OmenaParserStyleFactsSummaryV0 {
1201 let facts = collect_style_facts(style_source, dialect);
1202 let sass_symbol_resolution = summarize_omena_parser_sass_symbol_resolution(&facts.sass_symbols);
1203 let mut class_selector_names = Vec::new();
1204 let mut id_selector_names = Vec::new();
1205 let mut placeholder_selector_names = Vec::new();
1206 let mut keyframe_names = Vec::new();
1207 let mut animation_reference_names = Vec::new();
1208 let mut css_module_value_definition_names = BTreeSet::new();
1209 let mut css_module_value_reference_names = BTreeSet::new();
1210 let mut css_module_value_import_sources = BTreeSet::new();
1211 let mut css_module_composes_target_names = BTreeSet::new();
1212 let mut css_module_composes_import_sources = BTreeSet::new();
1213 let mut icss_export_names = BTreeSet::new();
1214 let mut icss_import_local_names = BTreeSet::new();
1215 let mut icss_import_remote_names = BTreeSet::new();
1216 let mut icss_import_sources = BTreeSet::new();
1217 let mut variable_names = BTreeSet::new();
1218 let mut sass_symbol_declaration_names = BTreeSet::new();
1219 let mut sass_symbol_reference_names = BTreeSet::new();
1220 let mut sass_module_use_sources = BTreeSet::new();
1221 let mut sass_module_forward_sources = BTreeSet::new();
1222 let mut sass_module_import_sources = BTreeSet::new();
1223 let mut custom_property_names = BTreeSet::new();
1224 let mut custom_property_decl_names = BTreeSet::new();
1225 let mut custom_property_ref_names = BTreeSet::new();
1226
1227 for selector in facts.selectors {
1228 match selector.kind {
1229 ParsedSelectorFactKind::Class => class_selector_names.push(selector.name),
1230 ParsedSelectorFactKind::Id => id_selector_names.push(selector.name),
1231 ParsedSelectorFactKind::Placeholder => placeholder_selector_names.push(selector.name),
1232 }
1233 }
1234
1235 for variable in facts.variables {
1236 match variable.kind {
1237 ParsedVariableFactKind::ScssDeclaration
1238 | ParsedVariableFactKind::ScssReference
1239 | ParsedVariableFactKind::LessDeclaration
1240 | ParsedVariableFactKind::LessReference => {
1241 variable_names.insert(variable.name);
1242 }
1243 ParsedVariableFactKind::CustomPropertyDeclaration
1244 | ParsedVariableFactKind::CustomPropertyReference => {
1245 custom_property_names.insert(variable.name.clone());
1246 match variable.kind {
1247 ParsedVariableFactKind::CustomPropertyDeclaration => {
1248 custom_property_decl_names.insert(variable.name);
1249 }
1250 ParsedVariableFactKind::CustomPropertyReference => {
1251 custom_property_ref_names.insert(variable.name);
1252 }
1253 _ => {}
1254 }
1255 }
1256 }
1257 }
1258
1259 for symbol in &facts.sass_symbols {
1260 match symbol.role {
1261 "declaration" => {
1262 sass_symbol_declaration_names.insert(symbol.name.clone());
1263 }
1264 _ => {
1265 sass_symbol_reference_names.insert(symbol.name.clone());
1266 }
1267 }
1268 }
1269
1270 for edge in &facts.sass_module_edges {
1271 match edge.kind {
1272 ParsedSassModuleEdgeFactKind::Use => {
1273 sass_module_use_sources.insert(edge.source.clone());
1274 }
1275 ParsedSassModuleEdgeFactKind::Forward => {
1276 sass_module_forward_sources.insert(edge.source.clone());
1277 }
1278 ParsedSassModuleEdgeFactKind::Import => {
1279 sass_module_import_sources.insert(edge.source.clone());
1280 }
1281 }
1282 }
1283
1284 for animation in facts.animations {
1285 match animation.kind {
1286 ParsedAnimationFactKind::KeyframesDeclaration => keyframe_names.push(animation.name),
1287 ParsedAnimationFactKind::AnimationNameReference => {
1288 animation_reference_names.push(animation.name);
1289 }
1290 }
1291 }
1292
1293 for value in facts.css_module_values {
1294 match value.kind {
1295 ParsedCssModuleValueFactKind::Definition => {
1296 css_module_value_definition_names.insert(value.name);
1297 }
1298 ParsedCssModuleValueFactKind::Reference => {
1299 css_module_value_reference_names.insert(value.name);
1300 }
1301 ParsedCssModuleValueFactKind::ImportSource => {
1302 css_module_value_import_sources.insert(value.name);
1303 }
1304 }
1305 }
1306
1307 for composes in facts.css_module_composes {
1308 match composes.kind {
1309 ParsedCssModuleComposesFactKind::Target => {
1310 css_module_composes_target_names.insert(composes.name);
1311 }
1312 ParsedCssModuleComposesFactKind::ImportSource => {
1313 css_module_composes_import_sources.insert(composes.name);
1314 }
1315 }
1316 }
1317
1318 for icss in facts.icss {
1319 match icss.kind {
1320 ParsedIcssFactKind::ExportName => {
1321 icss_export_names.insert(icss.name);
1322 }
1323 ParsedIcssFactKind::ImportLocalName => {
1324 icss_import_local_names.insert(icss.name);
1325 }
1326 ParsedIcssFactKind::ImportRemoteName => {
1327 icss_import_remote_names.insert(icss.name);
1328 }
1329 ParsedIcssFactKind::ImportSource => {
1330 icss_import_sources.insert(icss.name);
1331 }
1332 }
1333 }
1334
1335 OmenaParserStyleFactsSummaryV0 {
1336 schema_version: "0",
1337 product: "omena-parser.style-facts",
1338 dialect: style_dialect_label(dialect),
1339 class_selector_names,
1340 id_selector_names,
1341 placeholder_selector_names,
1342 keyframe_names,
1343 animation_reference_names,
1344 css_module_value_definition_names: css_module_value_definition_names.into_iter().collect(),
1345 css_module_value_reference_names: css_module_value_reference_names.into_iter().collect(),
1346 css_module_value_import_sources: css_module_value_import_sources.into_iter().collect(),
1347 css_module_value_import_edges: facts
1348 .css_module_value_import_edges
1349 .into_iter()
1350 .map(|edge| OmenaParserCssModuleValueImportEdgeFactV0 {
1351 remote_name: edge.remote_name,
1352 local_name: edge.local_name,
1353 import_source: edge.import_source,
1354 })
1355 .collect(),
1356 css_module_value_definition_edges: facts
1357 .css_module_value_definition_edges
1358 .into_iter()
1359 .map(|edge| OmenaParserCssModuleValueDefinitionEdgeFactV0 {
1360 definition_name: edge.definition_name,
1361 reference_names: edge.reference_names,
1362 })
1363 .collect(),
1364 css_module_composes_target_names: css_module_composes_target_names.into_iter().collect(),
1365 css_module_composes_import_sources: css_module_composes_import_sources
1366 .into_iter()
1367 .collect(),
1368 css_module_composes_edges: facts
1369 .css_module_composes_edges
1370 .into_iter()
1371 .map(|edge| OmenaParserCssModuleComposesEdgeFactV0 {
1372 kind: css_module_composes_edge_kind_label(edge.kind),
1373 owner_selector_names: edge.owner_selector_names,
1374 target_names: edge.target_names,
1375 import_source: edge.import_source,
1376 })
1377 .collect(),
1378 icss_export_names: icss_export_names.into_iter().collect(),
1379 icss_import_local_names: icss_import_local_names.into_iter().collect(),
1380 icss_import_remote_names: icss_import_remote_names.into_iter().collect(),
1381 icss_import_sources: icss_import_sources.into_iter().collect(),
1382 icss_import_edges: facts
1383 .icss_import_edges
1384 .into_iter()
1385 .map(|edge| OmenaParserIcssImportEdgeFactV0 {
1386 local_name: edge.local_name,
1387 remote_name: edge.remote_name,
1388 import_source: edge.import_source,
1389 })
1390 .collect(),
1391 icss_export_edges: facts
1392 .icss_export_edges
1393 .into_iter()
1394 .map(|edge| OmenaParserIcssExportEdgeFactV0 {
1395 export_name: edge.export_name,
1396 reference_names: edge.reference_names,
1397 })
1398 .collect(),
1399 variable_names: variable_names.into_iter().collect(),
1400 sass_symbol_declaration_names: sass_symbol_declaration_names.into_iter().collect(),
1401 sass_symbol_reference_names: sass_symbol_reference_names.into_iter().collect(),
1402 sass_symbol_facts: facts
1403 .sass_symbols
1404 .into_iter()
1405 .map(|symbol| OmenaParserSassSymbolFactV0 {
1406 kind: sass_symbol_fact_kind_label(symbol.kind),
1407 symbol_kind: symbol.symbol_kind,
1408 name: symbol.name,
1409 role: symbol.role,
1410 namespace: symbol.namespace,
1411 })
1412 .collect(),
1413 sass_symbol_resolution,
1414 sass_module_use_sources: sass_module_use_sources.into_iter().collect(),
1415 sass_module_forward_sources: sass_module_forward_sources.into_iter().collect(),
1416 sass_module_import_sources: sass_module_import_sources.into_iter().collect(),
1417 sass_module_edges: facts
1418 .sass_module_edges
1419 .into_iter()
1420 .map(|edge| OmenaParserSassModuleEdgeFactV0 {
1421 kind: sass_module_edge_fact_kind_label(edge.kind),
1422 source: edge.source,
1423 namespace_kind: edge.namespace_kind,
1424 namespace: edge.namespace,
1425 visibility_filter_kind: edge.visibility_filter_kind,
1426 visibility_filter_names: edge.visibility_filter_names,
1427 })
1428 .collect(),
1429 custom_property_names: custom_property_names.into_iter().collect(),
1430 custom_property_decl_names: custom_property_decl_names.into_iter().collect(),
1431 custom_property_ref_names: custom_property_ref_names.into_iter().collect(),
1432 at_rule_names: facts
1433 .at_rules
1434 .into_iter()
1435 .map(|at_rule| at_rule.name)
1436 .collect(),
1437 parser_error_count: facts.error_count,
1438 }
1439}
1440
1441pub fn summarize_omena_parser_lex(source: &str, dialect: StyleDialect) -> OmenaParserLexSummaryV0 {
1442 let result = lex(source, dialect);
1443 OmenaParserLexSummaryV0 {
1444 schema_version: "0",
1445 product: "omena-parser.lex-result",
1446 dialect: style_dialect_label(result.dialect()),
1447 tokens: result
1448 .tokens()
1449 .iter()
1450 .map(|token| OmenaParserLexTokenV0 {
1451 kind: format!("{:?}", token.kind),
1452 text: token.text.clone(),
1453 start: token.range.start().into(),
1454 end: token.range.end().into(),
1455 })
1456 .collect(),
1457 parser_error_count: result.errors().len(),
1458 }
1459}
1460
1461pub fn summarize_omena_parser_parity_lite(
1462 source: &str,
1463 dialect: StyleDialect,
1464) -> OmenaParserParityLiteSummaryV0 {
1465 let facts = collect_style_facts(source, dialect);
1466 let result = parse(source, dialect);
1467 let (tokens, _) = tokenize(source, &BuiltinDialectExtension::new(dialect));
1468 let mut structural = ParserStructuralSummary::default();
1469 summarize_parser_structural_range(&tokens, 0, tokens.len(), 0, &mut structural);
1470 let mut selector_names = collect_parity_lite_selector_names_from_tokens(&tokens);
1471 selector_names.sort();
1472
1473 OmenaParserParityLiteSummaryV0 {
1474 schema_version: "0",
1475 language: style_dialect_label(dialect),
1476 selector_names,
1477 keyframes_names: sorted_unique(
1478 facts
1479 .animations
1480 .iter()
1481 .filter(|animation| animation.kind == ParsedAnimationFactKind::KeyframesDeclaration)
1482 .map(|animation| animation.name.clone()),
1483 ),
1484 value_decl_names: sorted_unique(
1485 facts
1486 .css_module_values
1487 .iter()
1488 .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
1489 .map(|value| value.name.clone()),
1490 ),
1491 diagnostic_count: result.errors().len(),
1492 rule_count: structural.rule_count,
1493 declaration_count: structural.declaration_count,
1494 grouped_selector_count: structural.grouped_selector_count,
1495 max_nesting_depth: structural.max_nesting_depth,
1496 at_rule_kind_counts: structural.at_rule_kind_counts,
1497 declaration_kind_counts: structural.declaration_kind_counts,
1498 }
1499}
1500
1501fn style_dialect_label(dialect: StyleDialect) -> &'static str {
1502 match dialect {
1503 StyleDialect::Css => "css",
1504 StyleDialect::Scss => "scss",
1505 StyleDialect::Sass => "sass",
1506 StyleDialect::Less => "less",
1507 }
1508}
1509
1510#[derive(Default)]
1511struct ParserStructuralSummary {
1512 rule_count: usize,
1513 declaration_count: usize,
1514 grouped_selector_count: usize,
1515 max_nesting_depth: usize,
1516 at_rule_kind_counts: OmenaParserAtRuleKindCountsV0,
1517 declaration_kind_counts: OmenaParserDeclarationKindCountsV0,
1518}
1519
1520fn summarize_parser_structural_range(
1521 tokens: &[Token<'_>],
1522 start: usize,
1523 end: usize,
1524 depth: usize,
1525 summary: &mut ParserStructuralSummary,
1526) {
1527 let mut index = start;
1528 while index < end {
1529 index = skip_trivia_tokens(tokens, index, end);
1530 if index >= end {
1531 break;
1532 }
1533
1534 if tokens[index].kind == SyntaxKind::AtKeyword {
1535 increment_omena_parser_at_rule_kind_count(
1536 &mut summary.at_rule_kind_counts,
1537 classify_omena_parser_at_rule_kind(tokens[index].text),
1538 );
1539 let next_depth = depth + 1;
1540 summary.max_nesting_depth = summary.max_nesting_depth.max(next_depth);
1541 if let Some((open, close)) = find_block_after_header(tokens, index, end) {
1542 summarize_parser_structural_range(tokens, open + 1, close, next_depth, summary);
1543 index = close + 1;
1544 } else {
1545 index = skip_statement(tokens, index, end);
1546 }
1547 continue;
1548 }
1549
1550 let statement_end = css_module_value_statement_end(tokens, index);
1551 if is_root_less_variable_statement(tokens, index, statement_end.min(end), depth) {
1552 increment_omena_parser_at_rule_kind_count(
1553 &mut summary.at_rule_kind_counts,
1554 keyof_omena_parser_at_rule_kind_counts::Kind::Generic,
1555 );
1556 if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
1557 break;
1558 }
1559 index = statement_end + 1;
1560 continue;
1561 }
1562
1563 if statement_end < end && tokens[statement_end].kind == SyntaxKind::LeftBrace {
1564 summary.rule_count += 1;
1565 let next_depth = depth + 1;
1566 summary.max_nesting_depth = summary.max_nesting_depth.max(next_depth);
1567 let group_count = count_omena_parser_selector_groups(tokens, index, statement_end);
1568 if group_count > 1 {
1569 summary.grouped_selector_count += group_count;
1570 }
1571 if let Some(close) = matching_right_brace(tokens, statement_end, end) {
1572 summarize_parser_structural_range(
1573 tokens,
1574 statement_end + 1,
1575 close,
1576 next_depth,
1577 summary,
1578 );
1579 index = close + 1;
1580 } else {
1581 index = statement_end + 1;
1582 }
1583 continue;
1584 }
1585
1586 if let Some(colon_index) = declaration_colon_index(tokens, index, statement_end.min(end)) {
1587 summary.declaration_count += 1;
1588 let property = previous_non_trivia_token_index(tokens, colon_index, index)
1589 .map(|property| tokens[property].text)
1590 .unwrap_or_default();
1591 increment_omena_parser_declaration_kind_count(
1592 &mut summary.declaration_kind_counts,
1593 classify_omena_parser_declaration_kind(property),
1594 );
1595 }
1596
1597 if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
1598 break;
1599 }
1600 index = statement_end + 1;
1601 }
1602}
1603
1604fn is_root_less_variable_statement(
1605 tokens: &[Token<'_>],
1606 start: usize,
1607 end: usize,
1608 depth: usize,
1609) -> bool {
1610 if depth != 0 {
1611 return false;
1612 }
1613 let Some(first) = next_non_trivia_token_index_until(tokens, start, end) else {
1614 return false;
1615 };
1616 tokens[first].kind == SyntaxKind::LessVariable
1617 && declaration_colon_index(tokens, first, end).is_some()
1618}
1619
1620fn count_omena_parser_selector_groups(tokens: &[Token<'_>], start: usize, end: usize) -> usize {
1621 split_selector_groups(tokens, start, end)
1622 .into_iter()
1623 .filter(|(group_start, group_end)| {
1624 *group_start < *group_end
1625 && next_non_trivia_token_index_until(tokens, *group_start, *group_end).is_some()
1626 })
1627 .count()
1628}
1629
1630fn collect_parity_lite_selector_names_from_tokens(tokens: &[Token<'_>]) -> Vec<String> {
1631 let mut names = Vec::new();
1632 collect_parity_lite_selector_names_in_range(tokens, 0, tokens.len(), &[], None, &mut names);
1633 names
1634}
1635
1636fn collect_parity_lite_selector_names_in_range(
1637 tokens: &[Token<'_>],
1638 start: usize,
1639 end: usize,
1640 parent_branches: &[SelectorBranch],
1641 css_module_scope: Option<&'static str>,
1642 names: &mut Vec<String>,
1643) {
1644 let mut index = start;
1645 while index < end {
1646 index = skip_trivia_tokens(tokens, index, end);
1647 if index >= end {
1648 break;
1649 }
1650
1651 if tokens[index].kind == SyntaxKind::AtKeyword {
1652 let block = find_block_after_header(tokens, index, end);
1653 if let Some((open, close)) = block {
1654 if tokens[index].text == "@nest" {
1655 if css_module_scope == Some("global") {
1656 collect_parity_lite_selector_names_in_range(
1657 tokens,
1658 open + 1,
1659 close,
1660 &[],
1661 css_module_scope,
1662 names,
1663 );
1664 } else {
1665 let branches =
1666 resolve_selector_header(tokens, index + 1, open, parent_branches);
1667 names.extend(branches.iter().map(|branch| branch.name.clone()));
1668 collect_grouped_ampersand_compound_selector_duplicates(
1669 tokens,
1670 index + 1,
1671 open,
1672 parent_branches.len(),
1673 names,
1674 );
1675 collect_parity_lite_selector_names_in_range(
1676 tokens,
1677 open + 1,
1678 close,
1679 &branches,
1680 css_module_scope,
1681 names,
1682 );
1683 }
1684 } else if style_wrapper_at_rule(tokens[index].text) {
1685 collect_parity_lite_selector_names_in_range(
1686 tokens,
1687 open + 1,
1688 close,
1689 parent_branches,
1690 css_module_scope,
1691 names,
1692 );
1693 }
1694 index = close + 1;
1695 } else {
1696 index = skip_statement(tokens, index, end);
1697 }
1698 continue;
1699 }
1700
1701 let Some((open, close)) = find_block_after_header(tokens, index, end) else {
1702 index = skip_statement(tokens, index, end);
1703 continue;
1704 };
1705
1706 let effective_scope = css_module_scope
1707 .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
1708 if effective_scope == Some("global") {
1709 collect_parity_lite_selector_names_in_range(
1710 tokens,
1711 open + 1,
1712 close,
1713 &[],
1714 effective_scope,
1715 names,
1716 );
1717 } else {
1718 let branches = resolve_selector_header(tokens, index, open, parent_branches);
1719 names.extend(branches.iter().map(|branch| branch.name.clone()));
1720 collect_grouped_ampersand_compound_selector_duplicates(
1721 tokens,
1722 index,
1723 open,
1724 parent_branches.len(),
1725 names,
1726 );
1727 collect_parity_lite_selector_names_in_range(
1728 tokens,
1729 open + 1,
1730 close,
1731 &branches,
1732 effective_scope,
1733 names,
1734 );
1735 }
1736 index = close + 1;
1737 }
1738}
1739
1740fn collect_grouped_ampersand_compound_selector_duplicates(
1741 tokens: &[Token<'_>],
1742 start: usize,
1743 end: usize,
1744 parent_branch_count: usize,
1745 names: &mut Vec<String>,
1746) {
1747 if parent_branch_count <= 1 || !header_contains_ampersand(tokens, start, end) {
1748 return;
1749 }
1750 for (name, _) in collect_class_selector_names_from_header(tokens, start, end) {
1751 names.extend(std::iter::repeat_n(name, parent_branch_count - 1));
1752 }
1753}
1754
1755fn header_contains_ampersand(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
1756 tokens[start..end]
1757 .iter()
1758 .any(|token| token.kind == SyntaxKind::Ampersand)
1759}
1760
1761fn classify_omena_parser_at_rule_kind(text: &str) -> keyof_omena_parser_at_rule_kind_counts::Kind {
1762 match text.trim_start_matches('@').to_ascii_lowercase().as_str() {
1763 "media" => keyof_omena_parser_at_rule_kind_counts::Kind::Media,
1764 "supports" => keyof_omena_parser_at_rule_kind_counts::Kind::Supports,
1765 "layer" => keyof_omena_parser_at_rule_kind_counts::Kind::Layer,
1766 "keyframes" | "-webkit-keyframes" => {
1767 keyof_omena_parser_at_rule_kind_counts::Kind::Keyframes
1768 }
1769 "value" => keyof_omena_parser_at_rule_kind_counts::Kind::Value,
1770 "at-root" => keyof_omena_parser_at_rule_kind_counts::Kind::AtRoot,
1771 _ => keyof_omena_parser_at_rule_kind_counts::Kind::Generic,
1772 }
1773}
1774
1775fn increment_omena_parser_at_rule_kind_count(
1776 counts: &mut OmenaParserAtRuleKindCountsV0,
1777 kind: keyof_omena_parser_at_rule_kind_counts::Kind,
1778) {
1779 match kind {
1780 keyof_omena_parser_at_rule_kind_counts::Kind::Media => counts.media += 1,
1781 keyof_omena_parser_at_rule_kind_counts::Kind::Supports => counts.supports += 1,
1782 keyof_omena_parser_at_rule_kind_counts::Kind::Layer => counts.layer += 1,
1783 keyof_omena_parser_at_rule_kind_counts::Kind::Keyframes => counts.keyframes += 1,
1784 keyof_omena_parser_at_rule_kind_counts::Kind::Value => counts.value += 1,
1785 keyof_omena_parser_at_rule_kind_counts::Kind::AtRoot => counts.at_root += 1,
1786 keyof_omena_parser_at_rule_kind_counts::Kind::Generic => counts.generic += 1,
1787 }
1788}
1789
1790fn classify_omena_parser_declaration_kind(
1791 property: &str,
1792) -> keyof_omena_parser_declaration_kind_counts::Kind {
1793 match property.trim().to_ascii_lowercase().as_str() {
1794 "composes" => keyof_omena_parser_declaration_kind_counts::Kind::Composes,
1795 "animation" => keyof_omena_parser_declaration_kind_counts::Kind::Animation,
1796 "animation-name" => keyof_omena_parser_declaration_kind_counts::Kind::AnimationName,
1797 _ => keyof_omena_parser_declaration_kind_counts::Kind::Generic,
1798 }
1799}
1800
1801fn increment_omena_parser_declaration_kind_count(
1802 counts: &mut OmenaParserDeclarationKindCountsV0,
1803 kind: keyof_omena_parser_declaration_kind_counts::Kind,
1804) {
1805 match kind {
1806 keyof_omena_parser_declaration_kind_counts::Kind::Composes => counts.composes += 1,
1807 keyof_omena_parser_declaration_kind_counts::Kind::Animation => counts.animation += 1,
1808 keyof_omena_parser_declaration_kind_counts::Kind::AnimationName => {
1809 counts.animation_name += 1
1810 }
1811 keyof_omena_parser_declaration_kind_counts::Kind::Generic => counts.generic += 1,
1812 }
1813}
1814
1815mod keyof_omena_parser_at_rule_kind_counts {
1816 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1817 pub enum Kind {
1818 Media,
1819 Supports,
1820 Layer,
1821 Keyframes,
1822 Value,
1823 AtRoot,
1824 Generic,
1825 }
1826}
1827
1828mod keyof_omena_parser_declaration_kind_counts {
1829 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1830 pub enum Kind {
1831 Composes,
1832 Animation,
1833 AnimationName,
1834 Generic,
1835 }
1836}
1837
1838fn sorted_unique(values: impl IntoIterator<Item = String>) -> Vec<String> {
1839 values
1840 .into_iter()
1841 .collect::<BTreeSet<_>>()
1842 .into_iter()
1843 .collect()
1844}
1845
1846fn css_module_composes_edge_kind_label(kind: ParsedCssModuleComposesEdgeKind) -> &'static str {
1847 match kind {
1848 ParsedCssModuleComposesEdgeKind::Local => "local",
1849 ParsedCssModuleComposesEdgeKind::Global => "global",
1850 ParsedCssModuleComposesEdgeKind::External => "external",
1851 }
1852}
1853
1854fn sass_symbol_fact_kind_label(kind: ParsedSassSymbolFactKind) -> &'static str {
1855 match kind {
1856 ParsedSassSymbolFactKind::VariableDeclaration => "sassVariableDeclaration",
1857 ParsedSassSymbolFactKind::VariableReference => "sassVariableReference",
1858 ParsedSassSymbolFactKind::MixinDeclaration => "sassMixinDeclaration",
1859 ParsedSassSymbolFactKind::MixinInclude => "sassMixinInclude",
1860 ParsedSassSymbolFactKind::FunctionDeclaration => "sassFunctionDeclaration",
1861 ParsedSassSymbolFactKind::FunctionCall => "sassFunctionCall",
1862 }
1863}
1864
1865fn sass_module_edge_fact_kind_label(kind: ParsedSassModuleEdgeFactKind) -> &'static str {
1866 match kind {
1867 ParsedSassModuleEdgeFactKind::Use => "sassUse",
1868 ParsedSassModuleEdgeFactKind::Forward => "sassForward",
1869 ParsedSassModuleEdgeFactKind::Import => "sassImport",
1870 }
1871}
1872
1873fn summarize_omena_parser_sass_symbol_resolution(
1874 symbols: &[ParsedSassSymbolFact],
1875) -> OmenaParserSassSymbolResolutionV0 {
1876 let mut declaration_by_symbol: BTreeMap<
1877 (&'static str, Option<String>, String),
1878 (usize, &'static str),
1879 > = BTreeMap::new();
1880 let mut declaration_count = 0usize;
1881 let mut reference_count = 0usize;
1882 let mut edges = Vec::new();
1883
1884 for (source_order, symbol) in symbols.iter().enumerate() {
1885 let kind = sass_symbol_fact_kind_label(symbol.kind);
1886 if sass_symbol_fact_kind_is_declaration(symbol.kind) {
1887 declaration_count += 1;
1888 declaration_by_symbol.insert(
1889 (
1890 symbol.symbol_kind,
1891 symbol.namespace.clone(),
1892 symbol.name.clone(),
1893 ),
1894 (source_order, kind),
1895 );
1896 continue;
1897 }
1898 if !sass_symbol_fact_kind_is_reference(symbol.kind) {
1899 continue;
1900 }
1901
1902 reference_count += 1;
1903 let declaration = declaration_by_symbol.get(&(
1904 symbol.symbol_kind,
1905 symbol.namespace.clone(),
1906 symbol.name.clone(),
1907 ));
1908 edges.push(OmenaParserSassSymbolResolutionEdgeV0 {
1909 symbol_kind: symbol.symbol_kind,
1910 name: symbol.name.clone(),
1911 namespace: symbol.namespace.clone(),
1912 reference_kind: kind,
1913 reference_role: symbol.role,
1914 reference_source_order: source_order,
1915 declaration_kind: declaration.map(|(_, declaration_kind)| *declaration_kind),
1916 declaration_source_order: declaration.map(|(declaration_order, _)| *declaration_order),
1917 status: if declaration.is_some() {
1918 "resolved"
1919 } else {
1920 "unresolved"
1921 },
1922 });
1923 }
1924
1925 let resolved_reference_count = edges
1926 .iter()
1927 .filter(|edge| edge.status == "resolved")
1928 .count();
1929
1930 OmenaParserSassSymbolResolutionV0 {
1931 schema_version: "0",
1932 product: "omena-parser.sass-symbol-same-file-resolution",
1933 resolution_scope: "same-file",
1934 declaration_count,
1935 reference_count,
1936 resolved_reference_count,
1937 unresolved_reference_count: reference_count.saturating_sub(resolved_reference_count),
1938 edges,
1939 capabilities: OmenaParserSassSymbolResolutionCapabilitiesV0 {
1940 same_file_lexical_resolution_ready: true,
1941 declaration_before_reference_ready: true,
1942 unresolved_reference_reporting_ready: true,
1943 cross_file_module_resolution_ready: false,
1944 },
1945 }
1946}
1947
1948fn sass_symbol_fact_kind_is_declaration(kind: ParsedSassSymbolFactKind) -> bool {
1949 matches!(
1950 kind,
1951 ParsedSassSymbolFactKind::VariableDeclaration
1952 | ParsedSassSymbolFactKind::MixinDeclaration
1953 | ParsedSassSymbolFactKind::FunctionDeclaration
1954 )
1955}
1956
1957fn sass_symbol_fact_kind_is_reference(kind: ParsedSassSymbolFactKind) -> bool {
1958 matches!(
1959 kind,
1960 ParsedSassSymbolFactKind::VariableReference
1961 | ParsedSassSymbolFactKind::MixinInclude
1962 | ParsedSassSymbolFactKind::FunctionCall
1963 )
1964}
1965
1966pub fn summarize_parser_cst_equivalence(
1967 text: &str,
1968 dialect: StyleDialect,
1969) -> ParserCstEquivalenceSummaryV0 {
1970 let result = parse(text, dialect);
1971 let syntax = result.syntax();
1972 let cst = result.cst();
1973
1974 let mut node_count = 0;
1975 let mut token_count = 0;
1976 let mut syntax_kind_round_trip_ready = true;
1977 let mut zero_unknown_kind_ready = true;
1978
1979 for node in syntax.descendants() {
1980 node_count += 1;
1981 let kind = node.kind();
1982 syntax_kind_round_trip_ready &= SyntaxKind::from_raw(kind.into_raw()) == kind;
1983 zero_unknown_kind_ready &= SyntaxKind::ALL.contains(&kind);
1984 }
1985
1986 for token in syntax
1987 .descendants_with_tokens()
1988 .filter_map(|element| element.into_token())
1989 {
1990 token_count += 1;
1991 let kind = token.kind();
1992 syntax_kind_round_trip_ready &= SyntaxKind::from_raw(kind.into_raw()) == kind;
1993 zero_unknown_kind_ready &= SyntaxKind::ALL.contains(&kind);
1994 }
1995
1996 let typed_wrapper_count = usize::from(cst.stylesheet().is_some())
1997 + cst.rules().len()
1998 + cst.selectors().len()
1999 + cst.declarations().len()
2000 + cst.declaration_lists().len()
2001 + cst.values().len()
2002 + cst.component_values().len()
2003 + cst.simple_blocks().len()
2004 + cst.component_value_lists().len()
2005 + cst.comma_separated_component_value_lists().len()
2006 + cst.custom_property_values().len()
2007 + cst.at_rules().len()
2008 + cst.bogus_nodes().len();
2009
2010 ParserCstEquivalenceSummaryV0 {
2011 product: "omena-parser.cst-equivalence",
2012 dialect,
2013 root_kind: syntax.kind(),
2014 parser_node_count: node_count,
2015 parser_token_count: token_count,
2016 typed_wrapper_count,
2017 source_text_round_trip_ready: result.source_text().as_deref() == Some(text),
2018 syntax_kind_round_trip_ready,
2019 zero_unknown_kind_ready,
2020 typed_cst_wrapper_ready: cst.stylesheet().is_some() && typed_wrapper_count > 1,
2021 ready_surfaces: vec![
2022 "parserCstEquivalence",
2023 "parserUsesOmenaSyntaxKind",
2024 "parserCstSourceTextRoundTrip",
2025 "typedCstWrapperEquivalence",
2026 ],
2027 }
2028}
2029
2030pub fn collect_style_facts_with_extension(
2031 text: &str,
2032 extension: &impl DialectExtension,
2033) -> ParsedStyleFacts {
2034 let (tokens, lex_errors) = tokenize(text, extension);
2035 let mut parser = Parser::new(tokens.clone(), lex_errors, extension.dialect());
2036 let _green = parser.parse();
2037 let errors = parser.into_errors();
2038 let selectors = collect_selector_facts_from_tokens(&tokens);
2039 let variables = collect_variable_facts_from_tokens(&tokens);
2040 let sass_symbols = collect_sass_symbol_facts_from_tokens(&tokens);
2041 let sass_includes = collect_sass_include_facts_from_tokens(text, &tokens);
2042 let sass_module_edges = collect_sass_module_edge_facts_from_tokens(&tokens);
2043 let extend_targets = collect_extend_target_facts_from_tokens(&tokens);
2044 let animations = collect_animation_facts_from_tokens(&tokens);
2045 let css_module_values = collect_css_module_value_facts_from_tokens(&tokens);
2046 let css_module_value_import_edges =
2047 collect_css_module_value_import_edge_facts_from_tokens(&tokens);
2048 let css_module_value_definition_edges =
2049 collect_css_module_value_definition_edge_facts_from_tokens(&tokens);
2050 let css_module_composes = collect_css_module_composes_facts_from_tokens(&tokens);
2051 let css_module_composes_edges = collect_css_module_composes_edge_facts_from_tokens(&tokens);
2052 let icss = collect_icss_facts_from_tokens(&tokens);
2053 let icss_import_edges = collect_icss_import_edge_facts_from_tokens(&tokens);
2054 let icss_export_edges = collect_icss_export_edge_facts_from_tokens(&tokens);
2055 let at_rules = collect_at_rule_facts_from_tokens(&tokens, extension.dialect());
2056
2057 ParsedStyleFacts {
2058 product: "omena-parser.style-facts",
2059 dialect: extension.dialect(),
2060 selector_count: selectors.len(),
2061 selectors,
2062 variable_count: variables.len(),
2063 variables,
2064 sass_symbol_count: sass_symbols.len(),
2065 sass_symbols,
2066 sass_include_count: sass_includes.len(),
2067 sass_includes,
2068 sass_module_edge_count: sass_module_edges.len(),
2069 sass_module_edges,
2070 extend_target_count: extend_targets.len(),
2071 extend_targets,
2072 animation_count: animations.len(),
2073 animations,
2074 css_module_value_count: css_module_values.len(),
2075 css_module_values,
2076 css_module_value_import_edge_count: css_module_value_import_edges.len(),
2077 css_module_value_import_edges,
2078 css_module_value_definition_edge_count: css_module_value_definition_edges.len(),
2079 css_module_value_definition_edges,
2080 css_module_composes_count: css_module_composes.len(),
2081 css_module_composes,
2082 css_module_composes_edge_count: css_module_composes_edges.len(),
2083 css_module_composes_edges,
2084 icss_count: icss.len(),
2085 icss,
2086 icss_import_edge_count: icss_import_edges.len(),
2087 icss_import_edges,
2088 icss_export_edge_count: icss_export_edges.len(),
2089 icss_export_edges,
2090 at_rule_count: at_rules.len(),
2091 at_rules,
2092 error_count: errors.len(),
2093 }
2094}
2095
2096pub fn summarize_parser_semantic_name_consumption(
2097 text: &str,
2098 dialect: StyleDialect,
2099 db: &dyn salsa::Database,
2100) -> ParserSemanticNameConsumptionSummaryV0 {
2101 let facts = collect_style_facts(text, dialect);
2102 let candidates = parser_semantic_name_candidates(&facts);
2103 let interned_name_count = candidates
2104 .iter()
2105 .filter(|candidate| intern_parser_semantic_name(db, candidate.kind, &candidate.text))
2106 .count();
2107 let invalid_name_count = candidates.len().saturating_sub(interned_name_count);
2108
2109 ParserSemanticNameConsumptionSummaryV0 {
2110 product: "omena-parser.semantic-name-consumption",
2111 dialect,
2112 semantic_name_count: candidates.len(),
2113 interned_name_count,
2114 invalid_name_count,
2115 class_name_count: count_parser_semantic_name_kind(&candidates, NameKind::ClassName),
2116 css_ident_count: count_parser_semantic_name_kind(&candidates, NameKind::CssIdent),
2117 property_name_count: count_parser_semantic_name_kind(&candidates, NameKind::PropertyName),
2118 selector_key_count: count_parser_semantic_name_kind(&candidates, NameKind::SelectorKey),
2119 custom_property_name_count: count_parser_semantic_name_kind(
2120 &candidates,
2121 NameKind::CustomPropertyName,
2122 ),
2123 keyframes_name_count: count_parser_semantic_name_kind(&candidates, NameKind::KeyframesName),
2124 mixin_name_count: count_parser_semantic_name_kind(&candidates, NameKind::MixinName),
2125 file_path_count: count_parser_semantic_name_kind(&candidates, NameKind::FilePath),
2126 ready_surfaces: vec![
2127 "parserSemanticNameConsumption",
2128 "typedInternerValidation",
2129 "styleFactNameKindProjection",
2130 ],
2131 }
2132}
2133
2134fn parser_semantic_name_candidates(facts: &ParsedStyleFacts) -> Vec<ParserSemanticNameCandidateV0> {
2135 let mut candidates = Vec::new();
2136
2137 for selector in &facts.selectors {
2138 let kind = match selector.kind {
2139 ParsedSelectorFactKind::Class => NameKind::ClassName,
2140 ParsedSelectorFactKind::Id | ParsedSelectorFactKind::Placeholder => {
2141 NameKind::SelectorKey
2142 }
2143 };
2144 push_parser_semantic_name_candidate(&mut candidates, kind, &selector.name);
2145 }
2146
2147 for variable in &facts.variables {
2148 let kind = match variable.kind {
2149 ParsedVariableFactKind::CustomPropertyDeclaration
2150 | ParsedVariableFactKind::CustomPropertyReference => NameKind::CustomPropertyName,
2151 ParsedVariableFactKind::ScssDeclaration
2152 | ParsedVariableFactKind::ScssReference
2153 | ParsedVariableFactKind::LessDeclaration
2154 | ParsedVariableFactKind::LessReference => NameKind::CssIdent,
2155 };
2156 push_parser_semantic_name_candidate(&mut candidates, kind, &variable.name);
2157 }
2158
2159 for symbol in &facts.sass_symbols {
2160 let kind = match symbol.kind {
2161 ParsedSassSymbolFactKind::MixinDeclaration | ParsedSassSymbolFactKind::MixinInclude => {
2162 NameKind::MixinName
2163 }
2164 ParsedSassSymbolFactKind::VariableDeclaration
2165 | ParsedSassSymbolFactKind::VariableReference
2166 | ParsedSassSymbolFactKind::FunctionDeclaration
2167 | ParsedSassSymbolFactKind::FunctionCall => NameKind::CssIdent,
2168 };
2169 push_parser_semantic_name_candidate(&mut candidates, kind, &symbol.name);
2170 if let Some(namespace) = &symbol.namespace {
2171 push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
2172 }
2173 }
2174
2175 for include in &facts.sass_includes {
2176 push_parser_semantic_name_candidate(&mut candidates, NameKind::MixinName, &include.name);
2177 if let Some(namespace) = &include.namespace {
2178 push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
2179 }
2180 }
2181
2182 for edge in &facts.sass_module_edges {
2183 push_parser_semantic_name_candidate(&mut candidates, NameKind::FilePath, &edge.source);
2184 if let Some(namespace) = &edge.namespace {
2185 push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
2186 }
2187 }
2188
2189 for animation in &facts.animations {
2190 push_parser_semantic_name_candidate(
2191 &mut candidates,
2192 NameKind::KeyframesName,
2193 &animation.name,
2194 );
2195 }
2196
2197 for value in &facts.css_module_values {
2198 let kind = match value.kind {
2199 ParsedCssModuleValueFactKind::Definition | ParsedCssModuleValueFactKind::Reference => {
2200 NameKind::CssIdent
2201 }
2202 ParsedCssModuleValueFactKind::ImportSource => NameKind::FilePath,
2203 };
2204 push_parser_semantic_name_candidate(&mut candidates, kind, &value.name);
2205 }
2206
2207 for edge in &facts.css_module_value_import_edges {
2208 push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.local_name);
2209 push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.remote_name);
2210 push_parser_semantic_name_candidate(
2211 &mut candidates,
2212 NameKind::FilePath,
2213 &edge.import_source,
2214 );
2215 }
2216
2217 for edge in &facts.css_module_value_definition_edges {
2218 push_parser_semantic_name_candidate(
2219 &mut candidates,
2220 NameKind::CssIdent,
2221 &edge.definition_name,
2222 );
2223 for reference_name in &edge.reference_names {
2224 push_parser_semantic_name_candidate(
2225 &mut candidates,
2226 NameKind::CssIdent,
2227 reference_name,
2228 );
2229 }
2230 }
2231
2232 for composes in &facts.css_module_composes {
2233 let kind = match composes.kind {
2234 ParsedCssModuleComposesFactKind::Target => NameKind::ClassName,
2235 ParsedCssModuleComposesFactKind::ImportSource => NameKind::FilePath,
2236 };
2237 push_parser_semantic_name_candidate(&mut candidates, kind, &composes.name);
2238 }
2239
2240 for edge in &facts.css_module_composes_edges {
2241 for owner_selector_name in &edge.owner_selector_names {
2242 push_parser_semantic_name_candidate(
2243 &mut candidates,
2244 NameKind::ClassName,
2245 owner_selector_name,
2246 );
2247 }
2248 for target_name in &edge.target_names {
2249 push_parser_semantic_name_candidate(&mut candidates, NameKind::ClassName, target_name);
2250 }
2251 if let Some(import_source) = &edge.import_source {
2252 push_parser_semantic_name_candidate(&mut candidates, NameKind::FilePath, import_source);
2253 }
2254 }
2255
2256 for icss in &facts.icss {
2257 let kind = match icss.kind {
2258 ParsedIcssFactKind::ImportSource => NameKind::FilePath,
2259 ParsedIcssFactKind::ExportName
2260 | ParsedIcssFactKind::ImportLocalName
2261 | ParsedIcssFactKind::ImportRemoteName => NameKind::CssIdent,
2262 };
2263 push_parser_semantic_name_candidate(&mut candidates, kind, &icss.name);
2264 }
2265
2266 for edge in &facts.icss_import_edges {
2267 push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.local_name);
2268 push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.remote_name);
2269 push_parser_semantic_name_candidate(
2270 &mut candidates,
2271 NameKind::FilePath,
2272 &edge.import_source,
2273 );
2274 }
2275
2276 for edge in &facts.icss_export_edges {
2277 push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.export_name);
2278 for reference_name in &edge.reference_names {
2279 push_parser_semantic_name_candidate(
2280 &mut candidates,
2281 NameKind::CssIdent,
2282 reference_name,
2283 );
2284 }
2285 }
2286
2287 for at_rule in &facts.at_rules {
2288 push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &at_rule.name);
2289 }
2290
2291 candidates
2292}
2293
2294fn push_parser_semantic_name_candidate(
2295 candidates: &mut Vec<ParserSemanticNameCandidateV0>,
2296 kind: NameKind,
2297 text: &str,
2298) {
2299 candidates.push(ParserSemanticNameCandidateV0 {
2300 kind,
2301 text: text.to_string(),
2302 });
2303}
2304
2305fn count_parser_semantic_name_kind(
2306 candidates: &[ParserSemanticNameCandidateV0],
2307 kind: NameKind,
2308) -> usize {
2309 candidates
2310 .iter()
2311 .filter(|candidate| candidate.kind == kind)
2312 .count()
2313}
2314
2315fn intern_parser_semantic_name(db: &dyn salsa::Database, kind: NameKind, text: &str) -> bool {
2316 match kind {
2317 NameKind::ClassName => intern_class_name(db, text).is_ok(),
2318 NameKind::CssIdent => intern_css_ident(db, text).is_ok(),
2319 NameKind::PropertyName => intern_property_name(db, text).is_ok(),
2320 NameKind::SelectorKey => intern_selector_key(db, text).is_ok(),
2321 NameKind::CustomPropertyName => intern_custom_property_name(db, text).is_ok(),
2322 NameKind::KeyframesName => intern_keyframes_name(db, text).is_ok(),
2323 NameKind::MixinName => intern_mixin_name(db, text).is_ok(),
2324 NameKind::FilePath => intern_file_path(db, text).is_ok(),
2325 }
2326}
2327
2328pub fn summarize_pratt_value_parser_coverage() -> ParserPrattValueCoverageSummaryV0 {
2329 ParserPrattValueCoverageSummaryV0 {
2330 product: "omena-parser.pratt-value-coverage",
2331 infix_operator_kinds: vec![
2332 SyntaxKind::Plus,
2333 SyntaxKind::Minus,
2334 SyntaxKind::Star,
2335 SyntaxKind::Slash,
2336 SyntaxKind::Percent,
2337 ],
2338 prefix_operator_kinds: vec![SyntaxKind::Plus, SyntaxKind::Minus],
2339 value_expression_node_kinds: vec![
2340 SyntaxKind::UnaryExpression,
2341 SyntaxKind::BinaryExpression,
2342 SyntaxKind::ParenthesizedExpression,
2343 SyntaxKind::FunctionCall,
2344 SyntaxKind::FunctionArguments,
2345 SyntaxKind::ValueList,
2346 SyntaxKind::ComponentValueList,
2347 SyntaxKind::SimpleBlock,
2348 SyntaxKind::BogusValue,
2349 ],
2350 specialized_function_family_count: 10,
2351 css_values_l4_math_function_count: VALUES_L4_MATH_FUNCTION_NAMES.len(),
2352 css_color_function_count: CSS_COLOR_FUNCTION_NAMES.len(),
2353 ready_surfaces: vec![
2354 "prattValueParserCore",
2355 "prefixUnaryExpressions",
2356 "additiveMultiplicativePrecedence",
2357 "parenthesizedValueExpressions",
2358 "functionArgumentValueLists",
2359 "specializedCssValueFunctionFamilies",
2360 "valuesL4MathFunctionArityChecks",
2361 "varEnvAttrFunctionHeadChecks",
2362 "dynamicInterpolationEscapeHatches",
2363 "valueBogusRecovery",
2364 ],
2365 next_surfaces: vec!["fullPropertyValueGrammarRegistry"],
2366 }
2367}
2368
2369pub fn summarize_recursive_descent_parser_coverage() -> ParserRecursiveDescentCoverageSummaryV0 {
2370 ParserRecursiveDescentCoverageSummaryV0 {
2371 product: "omena-parser.recursive-descent-coverage",
2372 dialect_count: 4,
2373 entry_point_count: 10,
2374 selector_surface_count: 12,
2375 at_rule_surface_count: 19,
2376 dialect_extension_surface_count: 17,
2377 recovery_surface_count: 8,
2378 ready_surfaces: vec![
2379 "recursiveDescentParserCore",
2380 "stylesheetRuleDeclarationEntryPoints",
2381 "selectorsLevelFourCstNodes",
2382 "registeredAtRulePreludeParsers",
2383 "cssNestingRuleItems",
2384 "scssDialectStatements",
2385 "sassIndentedBlocks",
2386 "lessDialectStatements",
2387 "bogusRecoverySkeleton",
2388 "styleFactExtractionSurface",
2389 ],
2390 next_surfaces: vec!["completeExternalSpecMirror"],
2391 }
2392}
2393
2394pub fn summarize_parser_boundary() -> ParserBoundarySummary {
2395 ParserBoundarySummary {
2396 product: "omena-parser.boundary",
2397 tree_model: "cstree-green-root",
2398 parser_track: "greenFieldNextToEngineStyleParser",
2399 dialect_count: 4,
2400 shared_name_kind_count: NameKind::ALL.len(),
2401 ready_surfaces: vec![
2402 "lexResult",
2403 "lexedTokenTextSurface",
2404 "parseResult",
2405 "panicFreeTokenizer",
2406 "cstreeGreenBuilder",
2407 "tokenSetRecoveryScaffold",
2408 "dialectExtensionScaffold",
2409 "recursiveDescentParserCore",
2410 "recursiveDescentCoverageSummary",
2411 "selectorCstSkeleton",
2412 "atRuleRegistrySkeleton",
2413 "prattValueExpressionSkeleton",
2414 "prattValueParserCore",
2415 "prattValueCoverageSummary",
2416 "attributeMatcherTokenization",
2417 "attributeMatcherCstNodes",
2418 "attributeNameValueModifierCstNodes",
2419 "specializedValueFunctionCstNodes",
2420 "caseInsensitiveFunctionRegistry",
2421 "caseInsensitiveAtRuleRegistry",
2422 "valueAtomCstNodes",
2423 "identifierValueCstNodes",
2424 "stringValueCstNodes",
2425 "unicodeRangeValueCstNodes",
2426 "functionArgumentValueLists",
2427 "cssModuleScopeFunctionCstNodes",
2428 "cssModuleGlobalSelectorFactFiltering",
2429 "cssModuleLocalIdSelectorFacts",
2430 "cssModuleValueStyleFacts",
2431 "cssModuleValueDeclarationReferenceFacts",
2432 "cssModuleComposesStyleFacts",
2433 "icssStyleFacts",
2434 "animationNameStyleFacts",
2435 "animationShorthandStyleFacts",
2436 "scssStructuredBlockAtRules",
2437 "scssControlPreludeValidation",
2438 "scssControlStyleFactExtraction",
2439 "scssIncludeContentBlockStyleFacts",
2440 "scssSassModuleEdgeStyleFacts",
2441 "scssSassSymbolStyleFacts",
2442 "scssUtilityAtRules",
2443 "scssVariableFlagCstNodes",
2444 "scssNestedPropertyCstNodes",
2445 "scssModulePreludeSourceValidation",
2446 "scssModulePreludeClauseValidation",
2447 "scssModuleConfigCstNodes",
2448 "scssModuleConfigBogusRecovery",
2449 "scssPlaceholderSelectorCstNodes",
2450 "lessMixinDeclarationCstNodes",
2451 "lessMixinCallCstNodes",
2452 "lessMixinGuardCstNodes",
2453 "lessExtendPseudoCstNodes",
2454 "lessDetachedRulesetCstNodes",
2455 "lessNamespaceAccessCstNodes",
2456 "lessPropertyVariableTokenization",
2457 "lessPropertyVariableCstNodes",
2458 "lessEscapedStringTokenization",
2459 "lessEscapedStringValueCstNodes",
2460 "importantAnnotationTokenization",
2461 "urlTokenization",
2462 "urlValueCstNodes",
2463 "quotedUrlFunctionValueCstNodes",
2464 "conditionalAtRulePreludeCstNodes",
2465 "supportsAtRulePreludeValidation",
2466 "conditionalLevel5AtRuleCstNodes",
2467 "mediaQueryCstNodes",
2468 "mediaQueryListValidation",
2469 "importPreludeCstNodes",
2470 "importSourcePreludeValidation",
2471 "importTailPreludeValidation",
2472 "customMediaPreludeValidation",
2473 "propertyAtRuleNameValidation",
2474 "namedAtRulePreludeValidation",
2475 "containerAtRulePreludeValidation",
2476 "charsetNamespaceAtRulePreludeValidation",
2477 "keyframesAtRuleNameValidation",
2478 "emptyBlockAtRulePreludeValidation",
2479 "layerScopePreludeCstNodes",
2480 "layerAtRulePreludeValidation",
2481 "scopeAtRulePreludeValidation",
2482 "pageAtRulePreludeValidation",
2483 "pageMarginAtRuleCstNodes",
2484 "modernDeclarationAtRuleCstNodes",
2485 "fontFeatureValuesAtRuleCstNodes",
2486 "fontFeatureValuesPreludeValidation",
2487 "keyframeSelectorListValidation",
2488 "viewTransitionAtRuleCstNodes",
2489 "genericAtRulePreludeCstNodes",
2490 "bogusAtRulePreludeCstNodes",
2491 "nestingAtRuleCstNodes",
2492 "customMediaAtRuleCstNodes",
2493 "cssColorFunctionCstNodes",
2494 "colorFunctionArgumentChecks",
2495 "gradientFunctionCstNodes",
2496 "transformFunctionCstNodes",
2497 "filterFunctionCstNodes",
2498 "imageFunctionCstNodes",
2499 "shapeFunctionCstNodes",
2500 "envAttrFunctionCstNodes",
2501 "mathFunctionCstNodes",
2502 "mathFunctionArityChecks",
2503 "mathFunctionEmptyArgumentChecks",
2504 "varEnvAttrFunctionHeadChecks",
2505 "scssInterpolationTokenization",
2506 "scssInterpolationCstNodes",
2507 "lessInterpolationTokenization",
2508 "lessInterpolationCstNodes",
2509 "interpolationBogusRecovery",
2510 "unicodeRangeTokenization",
2511 "badStringTokenRecovery",
2512 "badStringValueBogusNodes",
2513 "emptyDeclarationValueRecovery",
2514 "emptyVariableValueRecovery",
2515 "missingSemicolonDeclarationRecovery",
2516 "coreBogusPopulationSlice",
2517 "dialectBogusPopulationSlice",
2518 "cssModuleValueCstNodes",
2519 "cssModuleComposesCstNodes",
2520 "icssModuleBlockCstNodes",
2521 "icssImportSourceValidation",
2522 "cssModuleFromClauseSourceValidation",
2523 "cssModuleComposesMultipleFromValidation",
2524 "cssModuleGlobalComposesValidation",
2525 "cssModuleBogusRecovery",
2526 "valueListCstNodes",
2527 "valueListBogusRecovery",
2528 "genericRecoveryBogusNodes",
2529 "sassIndentedTokenization",
2530 "sassIndentedBlockCstNodes",
2531 "sassIndentedStyleFacts",
2532 "differentialCorpusSeed",
2533 "differentialCorpus",
2534 "lightningCssDifferentialCorpusSlice",
2535 "lightningCssSelectorIdAndAtRuleDifferentialSlice",
2536 "midTypingNoPanicPropertySlice",
2537 "deterministicPanicFreeCorpus",
2538 "losslessCstTextRoundTripSmoke",
2539 "parseResultSourceTextSurface",
2540 "parseSourceParseRoundTripSmoke",
2541 "typedNumericValueAtomCstNodes",
2542 "bracketedValueCstNodes",
2543 "importantAnnotationCstNodes",
2544 "splitImportantAnnotationCstNodes",
2545 "unexpectedValueTokenBogusNodes",
2546 "cdoCdcTokenization",
2547 "cssIdentifierEscapeTokenization",
2548 "nullAndBomInputPreprocessingSlice",
2549 "hashDelimiterTokenization",
2550 "cssDashIdentTokenization",
2551 "signedNumericTokenization",
2552 "exponentNumericTokenization",
2553 "badUrlWhitespaceRecovery",
2554 "parserEntryPointApiSlice",
2555 "ruleListEntryPointApiSlice",
2556 "componentValueEntryPointApiSlice",
2557 "componentValueListEntryPointApiSlice",
2558 "commaSeparatedComponentValueListEntryPointApiSlice",
2559 "simpleBlockEntryPointApiSlice",
2560 "typedCstWrapperSlice",
2561 "parserCstEquivalence",
2562 "typedBogusCstWrapperSlice",
2563 "componentValueCstNodes",
2564 "simpleBlockCstNodes",
2565 "fullBogusPopulation",
2566 "componentValueListCstNodes",
2567 "commaSeparatedComponentValueListCstNodes",
2568 "customPropertyAnyValueComponentList",
2569 "customPropertyValueCstNodes",
2570 "functionalPseudoSelectorListCstNodes",
2571 "strictNotPseudoSelectorListCstNodes",
2572 "nthSelectorOfSelectorListCstNodes",
2573 "nthSelectorFormulaCstNodes",
2574 "hasRelativeSelectorListCstNodes",
2575 "langDirSelectorArgumentCstNodes",
2576 "namespaceQualifiedSelectorCstNodes",
2577 "selectorFunctionArgumentFactExclusion",
2578 "missingBlockCloseBogusTrivia",
2579 "initialDialectStatementNodes",
2580 "recoveryBogusSkeleton",
2581 "styleFactExtractionSurface",
2582 "parserSemanticNameConsumption",
2583 "productCutoverGate",
2584 ],
2585 not_ready_surfaces: vec![
2586 "completeExternalSpecMirror",
2587 "fullPropertyValueGrammarRegistry",
2588 ],
2589 }
2590}
2591
2592fn tokenize<'text>(
2593 text: &'text str,
2594 extension: &impl DialectExtension,
2595) -> (Vec<Token<'text>>, Vec<ParseError>) {
2596 let mut tokenizer = Tokenizer::new(text, extension);
2597 tokenizer.tokenize();
2598 (tokenizer.tokens, tokenizer.errors)
2599}
2600
2601struct Tokenizer<'text, 'extension, E> {
2602 text: &'text str,
2603 extension: &'extension E,
2604 offset: usize,
2605 scss_interpolation_depth: usize,
2606 less_interpolation_depth: usize,
2607 sass_indent_stack: Vec<usize>,
2608 tokens: Vec<Token<'text>>,
2609 errors: Vec<ParseError>,
2610}
2611
2612struct Parser<'text> {
2613 tokens: Vec<Token<'text>>,
2614 position: usize,
2615 dialect: StyleDialect,
2616 builder: GreenNodeBuilder<'static, 'static, SyntaxKind>,
2617 errors: Vec<ParseError>,
2618}
2619
2620impl<'text> Parser<'text> {
2621 fn new(tokens: Vec<Token<'text>>, errors: Vec<ParseError>, dialect: StyleDialect) -> Self {
2622 Self {
2623 tokens,
2624 position: 0,
2625 dialect,
2626 builder: GreenNodeBuilder::new(),
2627 errors,
2628 }
2629 }
2630
2631 fn parse(&mut self) -> (GreenNode, Option<Arc<TokenInterner>>) {
2632 self.parse_entry_point(ParseEntryPoint::Stylesheet)
2633 }
2634
2635 fn parse_entry_point(
2636 &mut self,
2637 entry_point: ParseEntryPoint,
2638 ) -> (GreenNode, Option<Arc<TokenInterner>>) {
2639 self.builder.start_node(SyntaxKind::Root);
2640 match entry_point {
2641 ParseEntryPoint::Stylesheet => {
2642 self.builder.start_node(SyntaxKind::Stylesheet);
2643 self.parse_stylesheet_items();
2644 self.builder.finish_node();
2645 }
2646 ParseEntryPoint::RuleList => {
2647 self.builder.start_node(SyntaxKind::RuleList);
2648 self.parse_rule_list_items();
2649 self.builder.finish_node();
2650 }
2651 ParseEntryPoint::Rule => self.parse_rule(),
2652 ParseEntryPoint::DeclarationList => {
2653 self.builder.start_node(SyntaxKind::DeclarationList);
2654 self.parse_declaration_list();
2655 self.builder.finish_node();
2656 }
2657 ParseEntryPoint::Declaration => self.parse_declaration(),
2658 ParseEntryPoint::Value => {
2659 self.builder.start_node(SyntaxKind::Value);
2660 self.parse_value_or_value_list_until(&[]);
2661 self.builder.finish_node();
2662 }
2663 ParseEntryPoint::ComponentValue => self.parse_component_value(&[]),
2664 ParseEntryPoint::ComponentValueList => self.parse_component_value_list_until(&[]),
2665 ParseEntryPoint::CommaSeparatedComponentValueList => {
2666 self.parse_comma_separated_component_value_list_until(&[])
2667 }
2668 ParseEntryPoint::SimpleBlock => self.parse_simple_block_entry_point(&[]),
2669 }
2670 self.parse_sass_indentation_bogus();
2671 self.parse_entry_point_trailing_bogus();
2672 self.builder.finish_node();
2673
2674 let builder = std::mem::take(&mut self.builder);
2675 let (green, cache) = builder.finish();
2676 let interner = cache.and_then(|cache| cache.into_interner()).map(Arc::new);
2677 (green, interner)
2678 }
2679
2680 fn parse_sass_indentation_bogus(&mut self) {
2681 if self.dialect != StyleDialect::Sass
2682 || !self
2683 .errors
2684 .iter()
2685 .any(|error| error.message == "inconsistent Sass indentation")
2686 {
2687 return;
2688 }
2689 self.builder.start_node(SyntaxKind::BogusSassIndentation);
2690 self.builder.finish_node();
2691 }
2692
2693 fn parse_entry_point_trailing_bogus(&mut self) {
2694 self.eat_trivia();
2695 if self.at_end() {
2696 return;
2697 }
2698 self.builder.start_node(SyntaxKind::BogusRecovery);
2699 while !self.at_end() {
2700 self.token_current();
2701 }
2702 self.builder.finish_node();
2703 }
2704
2705 fn into_errors(self) -> Vec<ParseError> {
2706 self.errors
2707 }
2708
2709 fn parse_stylesheet_items(&mut self) {
2710 while !self.at_end() {
2711 self.eat_trivia();
2712 if self.at_end() {
2713 break;
2714 }
2715 match self.current_kind() {
2716 Some(SyntaxKind::AtKeyword) if self.current_is_css_module_value_rule() => {
2717 self.parse_css_module_value_rule()
2718 }
2719 Some(SyntaxKind::AtKeyword) if self.current_dialect_at_rule_spec().is_some() => {
2720 self.parse_dialect_at_rule()
2721 }
2722 Some(SyntaxKind::AtKeyword) => self.parse_at_rule(),
2723 Some(SyntaxKind::ScssVariable)
2724 if matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass) =>
2725 {
2726 self.parse_variable_declaration(SyntaxKind::ScssVariableDeclaration)
2727 }
2728 Some(SyntaxKind::LessVariable) if self.dialect == StyleDialect::Less => {
2729 self.parse_variable_declaration(SyntaxKind::LessVariableDeclaration)
2730 }
2731 Some(SyntaxKind::Cdo | SyntaxKind::Cdc) => self.token_current(),
2732 Some(SyntaxKind::RightBrace | SyntaxKind::SassDedent) => self.token_current(),
2733 Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) => {
2734 self.token_current()
2735 }
2736 Some(_) => self.parse_rule(),
2737 None => break,
2738 }
2739 }
2740 }
2741
2742 fn parse_rule(&mut self) {
2743 let starts_less_mixin =
2744 self.dialect == StyleDialect::Less && self.current_starts_less_callable_signature();
2745 let has_rule_block = self.find_rule_block_open_before_recovery(&[
2746 SyntaxKind::Semicolon,
2747 SyntaxKind::SassOptionalSemicolon,
2748 SyntaxKind::RightBrace,
2749 SyntaxKind::SassDedent,
2750 ]);
2751 let kind = if let Some(kind) = self
2752 .current_icss_module_rule_kind()
2753 .filter(|_| has_rule_block)
2754 {
2755 kind
2756 } else if self.current_starts_less_mixin_declaration() {
2757 SyntaxKind::LessMixinDeclaration
2758 } else if starts_less_mixin {
2759 SyntaxKind::BogusLessMixin
2760 } else if has_rule_block {
2761 SyntaxKind::Rule
2762 } else {
2763 SyntaxKind::BogusRule
2764 };
2765
2766 self.builder.start_node(kind);
2767 if kind == SyntaxKind::CssModuleImportBlock && !self.current_icss_import_has_source() {
2768 self.error_at_current(ParseErrorCode::ExpectedValue, "expected ICSS import source");
2769 }
2770 if kind == SyntaxKind::LessMixinDeclaration {
2771 self.parse_less_mixin_header();
2772 } else if kind == SyntaxKind::BogusLessMixin {
2773 self.parse_until_recovery_with_optional_less_guard(&[
2774 SyntaxKind::Semicolon,
2775 SyntaxKind::RightBrace,
2776 SyntaxKind::SassDedent,
2777 ]);
2778 self.error_at_current(
2779 ParseErrorCode::UnexpectedCharacter,
2780 "expected Less mixin block",
2781 );
2782 } else {
2783 self.parse_selector_list();
2784 }
2785 if self.current_kind() == Some(SyntaxKind::LeftBrace) {
2786 self.token_current();
2787 self.builder
2788 .start_node(if self.previous_left_brace_has_match() {
2789 SyntaxKind::DeclarationList
2790 } else {
2791 SyntaxKind::BogusDeclarationList
2792 });
2793 self.parse_declaration_list();
2794 self.builder.finish_node();
2795 if self.current_kind() == Some(SyntaxKind::RightBrace) {
2796 self.token_current();
2797 } else {
2798 self.missing_token_bogus_trivia(
2799 ParseErrorCode::UnexpectedCharacter,
2800 "unterminated declaration block",
2801 );
2802 }
2803 } else if self.current_kind() == Some(SyntaxKind::SassIndent) {
2804 self.builder.start_node(SyntaxKind::SassIndentedBlock);
2805 self.token_current();
2806 self.builder.start_node(SyntaxKind::DeclarationList);
2807 self.parse_declaration_list();
2808 self.builder.finish_node();
2809 if self.current_kind() == Some(SyntaxKind::SassDedent) {
2810 self.token_current();
2811 } else {
2812 self.missing_token_bogus_trivia(
2813 ParseErrorCode::UnexpectedCharacter,
2814 "unterminated Sass indented declaration block",
2815 );
2816 }
2817 self.builder.finish_node();
2818 } else {
2819 self.consume_until_recovery(&[
2820 SyntaxKind::Semicolon,
2821 SyntaxKind::SassOptionalSemicolon,
2822 SyntaxKind::RightBrace,
2823 SyntaxKind::SassDedent,
2824 ]);
2825 if self.current_kind().is_some_and(is_statement_end) {
2826 self.token_current();
2827 }
2828 }
2829 self.builder.finish_node();
2830 }
2831
2832 fn current_icss_module_rule_kind(&self) -> Option<SyntaxKind> {
2833 if self.current_kind() != Some(SyntaxKind::Colon) {
2834 return None;
2835 }
2836 let (name_index, name_kind) = self.non_trivia_token_from(self.position + 1)?;
2837 if name_kind != SyntaxKind::Ident {
2838 return None;
2839 }
2840 match self.tokens.get(name_index)?.text {
2841 "export" => Some(SyntaxKind::CssModuleExportBlock),
2842 "import" => Some(SyntaxKind::CssModuleImportBlock),
2843 _ => None,
2844 }
2845 }
2846
2847 fn current_icss_import_has_source(&self) -> bool {
2848 let Some((name_index, SyntaxKind::Ident)) = self.non_trivia_token_from(self.position + 1)
2849 else {
2850 return false;
2851 };
2852 if self
2853 .tokens
2854 .get(name_index)
2855 .is_none_or(|token| token.text != "import")
2856 {
2857 return false;
2858 }
2859 let Some((open_index, SyntaxKind::LeftParen)) = self.non_trivia_token_from(name_index + 1)
2860 else {
2861 return false;
2862 };
2863 let Some((_, source_kind)) = self.non_trivia_token_from(open_index + 1) else {
2864 return false;
2865 };
2866 matches!(
2867 source_kind,
2868 SyntaxKind::String | SyntaxKind::Url | SyntaxKind::ScssInterpolationStart
2869 )
2870 }
2871
2872 fn parse_selector_list(&mut self) {
2873 self.parse_selector_list_until(&[]);
2874 }
2875
2876 fn parse_selector_list_until(&mut self, recovery: &[SyntaxKind]) {
2877 let kind = if self.current_kind() == Some(SyntaxKind::LeftBrace) {
2878 SyntaxKind::BogusSelectorList
2879 } else {
2880 SyntaxKind::SelectorList
2881 };
2882 self.builder.start_node(kind);
2883 while !self.at_end() {
2884 match self.current_kind() {
2885 Some(SyntaxKind::Comma) => self.token_current(),
2886 Some(kind) if is_selector_boundary_until(kind, recovery) => break,
2887 Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
2888 Some(_)
2889 if recovery.contains(&SyntaxKind::RightParen)
2890 && self.current_selector_item_is_bogus(recovery) =>
2891 {
2892 self.parse_bogus_selector_until(recovery)
2893 }
2894 Some(_) => self.parse_selector_until(recovery),
2895 None => break,
2896 }
2897 }
2898 self.builder.finish_node();
2899 }
2900
2901 fn parse_strict_selector_list_until(&mut self, recovery: &[SyntaxKind]) {
2902 self.builder.start_node(
2903 if self.selector_list_contains_bogus_item_until(recovery)
2904 && self.current_kind() != Some(SyntaxKind::RightParen)
2905 {
2906 SyntaxKind::BogusSelectorList
2907 } else {
2908 SyntaxKind::SelectorList
2909 },
2910 );
2911 while !self.at_end() {
2912 match self.current_kind() {
2913 Some(SyntaxKind::Comma) => self.token_current(),
2914 Some(kind) if is_selector_boundary_until(kind, recovery) => break,
2915 Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
2916 Some(_)
2917 if self.current_selector_item_is_bogus(recovery)
2918 && self.current_kind() != Some(SyntaxKind::RightParen) =>
2919 {
2920 self.parse_bogus_selector_until(recovery)
2921 }
2922 Some(_) => self.parse_selector_until(recovery),
2923 None => break,
2924 }
2925 }
2926 self.builder.finish_node();
2927 }
2928
2929 fn parse_relative_selector_list_until(&mut self, recovery: &[SyntaxKind]) {
2930 self.builder.start_node(
2931 if self.current_selector_item_is_bogus(recovery)
2932 && self.current_kind() != Some(SyntaxKind::RightParen)
2933 {
2934 SyntaxKind::BogusSelectorList
2935 } else {
2936 SyntaxKind::RelativeSelectorList
2937 },
2938 );
2939 while !self.at_end() {
2940 match self.current_kind() {
2941 Some(SyntaxKind::Comma) => self.token_current(),
2942 Some(kind) if is_selector_boundary_until(kind, recovery) => break,
2943 Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
2944 Some(_)
2945 if self.current_selector_item_is_bogus(recovery)
2946 && self.current_kind() != Some(SyntaxKind::RightParen) =>
2947 {
2948 self.parse_bogus_selector_until(recovery)
2949 }
2950 Some(_) => self.parse_relative_selector_until(recovery),
2951 None => break,
2952 }
2953 }
2954 self.builder.finish_node();
2955 }
2956
2957 fn parse_relative_selector_until(&mut self, recovery: &[SyntaxKind]) {
2958 self.builder.start_node(SyntaxKind::RelativeSelector);
2959 self.builder.start_node(SyntaxKind::ComplexSelector);
2960 self.parse_complex_selector_until(recovery);
2961 self.builder.finish_node();
2962 self.builder.finish_node();
2963 }
2964
2965 fn parse_bogus_selector_until(&mut self, recovery: &[SyntaxKind]) {
2966 self.builder.start_node(SyntaxKind::BogusSelector);
2967 self.error_at_current(
2968 ParseErrorCode::UnexpectedCharacter,
2969 "invalid selector in selector list",
2970 );
2971 let mut paren_depth = 0usize;
2972 let mut bracket_depth = 0usize;
2973 while !self.at_end() {
2974 let Some(kind) = self.current_kind() else {
2975 break;
2976 };
2977 if paren_depth == 0
2978 && bracket_depth == 0
2979 && (kind == SyntaxKind::Comma || is_selector_boundary_until(kind, recovery))
2980 {
2981 break;
2982 }
2983 match kind {
2984 SyntaxKind::LeftParen => paren_depth += 1,
2985 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
2986 SyntaxKind::LeftBracket => bracket_depth += 1,
2987 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
2988 _ => {}
2989 }
2990 self.token_current();
2991 }
2992 self.builder.finish_node();
2993 }
2994
2995 fn parse_selector_until(&mut self, recovery: &[SyntaxKind]) {
2996 self.builder.start_node(SyntaxKind::Selector);
2997 self.builder.start_node(SyntaxKind::ComplexSelector);
2998 self.parse_complex_selector_until(recovery);
2999 self.builder.finish_node();
3000 self.builder.finish_node();
3001 }
3002
3003 fn parse_complex_selector_until(&mut self, recovery: &[SyntaxKind]) {
3004 let mut has_component = false;
3005 while !self.at_end() {
3006 match self.current_kind() {
3007 Some(kind) if is_selector_boundary_until(kind, recovery) => break,
3008 Some(SyntaxKind::Whitespace) => {
3009 if has_component
3010 && self.next_non_trivia_kind().is_some_and(|kind| {
3011 !is_selector_boundary_until(kind, recovery) && !is_combinator(kind)
3012 })
3013 {
3014 self.parse_whitespace_combinator();
3015 has_component = false;
3016 } else {
3017 self.token_current();
3018 }
3019 }
3020 Some(SyntaxKind::SassIndentedNewline) => self.token_current(),
3021 Some(kind) if is_combinator(kind) => {
3022 self.parse_combinator();
3023 has_component = false;
3024 }
3025 Some(_) => {
3026 self.parse_compound_selector_until(recovery);
3027 has_component = true;
3028 }
3029 None => break,
3030 }
3031 }
3032 }
3033
3034 fn parse_compound_selector_until(&mut self, recovery: &[SyntaxKind]) {
3035 let starts_valid = self.current_kind().is_some_and(|kind| {
3036 selector_component_can_start(kind)
3037 || self.current_starts_namespace_qualified_selector(kind)
3038 || is_interpolation_start(kind)
3039 });
3040 self.builder.start_node(if starts_valid {
3041 SyntaxKind::CompoundSelector
3042 } else {
3043 SyntaxKind::BogusCompoundSelector
3044 });
3045 let start = self.position;
3046 while !self.at_end() {
3047 match self.current_kind() {
3048 Some(kind)
3049 if is_selector_boundary_until(kind, recovery)
3050 || kind == SyntaxKind::Whitespace
3051 || kind == SyntaxKind::SassIndentedNewline
3052 || is_combinator(kind) =>
3053 {
3054 break;
3055 }
3056 Some(SyntaxKind::Dot) => self.parse_class_selector(),
3057 Some(SyntaxKind::Hash) => self.parse_id_selector(),
3058 Some(kind) if self.current_starts_namespace_qualified_selector(kind) => {
3059 self.parse_namespace_qualified_selector()
3060 }
3061 Some(SyntaxKind::Ident) => self.parse_type_selector(),
3062 Some(SyntaxKind::Star) => self.parse_universal_selector(),
3063 Some(SyntaxKind::Ampersand) => self.parse_nesting_selector(),
3064 Some(SyntaxKind::ScssPlaceholder) => self.parse_scss_placeholder_selector(),
3065 Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3066 kind,
3067 &[
3068 SyntaxKind::Comma,
3069 SyntaxKind::LeftBrace,
3070 SyntaxKind::SassIndent,
3071 SyntaxKind::RightBrace,
3072 SyntaxKind::SassDedent,
3073 SyntaxKind::RightParen,
3074 SyntaxKind::Semicolon,
3075 SyntaxKind::SassOptionalSemicolon,
3076 ],
3077 ),
3078 Some(SyntaxKind::LeftBracket) => self.parse_attribute_selector(),
3079 Some(SyntaxKind::Colon) if self.current_starts_less_extend_rule() => {
3080 self.parse_less_extend_rule()
3081 }
3082 Some(SyntaxKind::Colon) => {
3083 self.parse_pseudo_selector(SyntaxKind::PseudoClassSelector)
3084 }
3085 Some(SyntaxKind::DoubleColon) => {
3086 self.parse_pseudo_selector(SyntaxKind::PseudoElementSelector)
3087 }
3088 Some(_) => self.token_current(),
3089 None => break,
3090 }
3091 }
3092 if self.position == start {
3093 self.token_current();
3094 }
3095 if !starts_valid {
3096 self.error_at_current(
3097 ParseErrorCode::UnexpectedCharacter,
3098 "expected selector component",
3099 );
3100 }
3101 self.builder.finish_node();
3102 }
3103
3104 fn parse_class_selector(&mut self) {
3105 self.builder.start_node(SyntaxKind::ClassSelector);
3106 self.token_current();
3107 if matches!(
3108 self.current_kind(),
3109 Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
3110 ) {
3111 self.token_current();
3112 } else {
3113 self.empty_bogus_node(
3114 SyntaxKind::BogusSelector,
3115 ParseErrorCode::ExpectedSelectorName,
3116 "expected class selector name",
3117 );
3118 }
3119 self.builder.finish_node();
3120 }
3121
3122 fn parse_id_selector(&mut self) {
3123 self.builder.start_node(SyntaxKind::IdSelector);
3124 self.token_current();
3125 self.builder.finish_node();
3126 }
3127
3128 fn parse_type_selector(&mut self) {
3129 self.builder.start_node(SyntaxKind::TypeSelector);
3130 self.token_current();
3131 self.builder.finish_node();
3132 }
3133
3134 fn parse_universal_selector(&mut self) {
3135 self.builder.start_node(SyntaxKind::UniversalSelector);
3136 self.token_current();
3137 self.builder.finish_node();
3138 }
3139
3140 fn parse_namespace_qualified_selector(&mut self) {
3141 let selector_kind =
3142 if self.namespace_qualified_selector_target_kind() == Some(SyntaxKind::Star) {
3143 SyntaxKind::UniversalSelector
3144 } else {
3145 SyntaxKind::TypeSelector
3146 };
3147 self.builder.start_node(selector_kind);
3148 self.builder.start_node(SyntaxKind::NamespacePrefix);
3149 if self.current_kind() != Some(SyntaxKind::Pipe) {
3150 self.token_current();
3151 }
3152 self.token_current();
3153 self.builder.finish_node();
3154 if matches!(
3155 self.current_kind(),
3156 Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star)
3157 ) {
3158 self.token_current();
3159 } else {
3160 self.empty_bogus_node(
3161 SyntaxKind::BogusSelector,
3162 ParseErrorCode::ExpectedSelectorName,
3163 "expected namespace-qualified selector name",
3164 );
3165 }
3166 self.builder.finish_node();
3167 }
3168
3169 fn parse_nesting_selector(&mut self) {
3170 self.builder.start_node(SyntaxKind::NestingSelectorNode);
3171 self.token_current();
3172 self.builder.finish_node();
3173 }
3174
3175 fn parse_scss_placeholder_selector(&mut self) {
3176 self.builder.start_node(SyntaxKind::ScssPlaceholderSelector);
3177 self.token_current();
3178 self.builder.finish_node();
3179 }
3180
3181 fn parse_attribute_selector(&mut self) {
3182 let kind = if self.find_before_recovery(
3183 SyntaxKind::RightBracket,
3184 &[
3185 SyntaxKind::Comma,
3186 SyntaxKind::LeftBrace,
3187 SyntaxKind::RightBrace,
3188 SyntaxKind::Semicolon,
3189 ],
3190 ) {
3191 SyntaxKind::AttributeSelector
3192 } else {
3193 SyntaxKind::BogusSelector
3194 };
3195 self.builder.start_node(kind);
3196 self.token_current();
3197 let mut saw_matcher = false;
3198 let mut saw_value = false;
3199 let mut closed = false;
3200 while !self.at_end() {
3201 match self.current_kind() {
3202 Some(SyntaxKind::RightBracket) => {
3203 self.token_current();
3204 closed = true;
3205 break;
3206 }
3207 Some(kind) if is_attribute_matcher(kind) => {
3208 self.parse_attribute_matcher();
3209 saw_matcher = true;
3210 }
3211 Some(kind) if is_selector_boundary(kind) => break,
3212 Some(kind) if !saw_matcher && attribute_name_token_can_start(kind) => {
3213 self.parse_attribute_name()
3214 }
3215 Some(kind)
3216 if saw_matcher && !saw_value && attribute_value_token_can_start(kind) =>
3217 {
3218 self.parse_attribute_value();
3219 saw_value = true;
3220 }
3221 Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) if saw_value => {
3222 self.parse_attribute_modifier()
3223 }
3224 Some(_) => self.token_current(),
3225 None => break,
3226 }
3227 }
3228 if !closed {
3229 self.error_at_current(
3230 ParseErrorCode::UnterminatedAttributeSelector,
3231 "unterminated attribute selector",
3232 );
3233 }
3234 self.builder.finish_node();
3235 }
3236
3237 fn parse_attribute_matcher(&mut self) {
3238 self.builder.start_node(SyntaxKind::AttributeMatcher);
3239 self.token_current();
3240 self.builder.finish_node();
3241 }
3242
3243 fn parse_attribute_name(&mut self) {
3244 self.builder.start_node(SyntaxKind::AttributeName);
3245 while !self.at_end() {
3246 match self.current_kind() {
3247 Some(SyntaxKind::RightBracket) => break,
3248 Some(kind) if is_attribute_matcher(kind) || is_selector_boundary(kind) => break,
3249 Some(kind) if attribute_name_token_can_continue(kind) => self.token_current(),
3250 Some(_) => break,
3251 None => break,
3252 }
3253 }
3254 self.builder.finish_node();
3255 }
3256
3257 fn parse_attribute_value(&mut self) {
3258 self.builder.start_node(SyntaxKind::AttributeValue);
3259 self.token_current();
3260 self.builder.finish_node();
3261 }
3262
3263 fn parse_attribute_modifier(&mut self) {
3264 self.builder.start_node(SyntaxKind::AttributeModifier);
3265 self.token_current();
3266 self.builder.finish_node();
3267 }
3268
3269 fn parse_pseudo_selector(&mut self, kind: SyntaxKind) {
3270 self.builder.start_node(kind);
3271 self.token_current();
3272 let pseudo_name = self.current_text().map(str::to_owned);
3273 let css_module_scope_kind = if kind == SyntaxKind::PseudoClassSelector {
3274 self.current_text().and_then(css_module_scope_function_kind)
3275 } else {
3276 None
3277 };
3278 if self.current_kind() == Some(SyntaxKind::Ident) {
3279 if let Some(kind) = css_module_scope_kind {
3280 self.builder.start_node(kind);
3281 }
3282 self.token_current();
3283 } else {
3284 self.empty_bogus_node(
3285 SyntaxKind::BogusSelector,
3286 ParseErrorCode::ExpectedSelectorName,
3287 "expected pseudo selector name",
3288 );
3289 }
3290 if self.current_kind() == Some(SyntaxKind::LeftParen) {
3291 self.token_current();
3292 self.builder.start_node(SyntaxKind::PseudoSelectorArgument);
3293 if kind == SyntaxKind::PseudoClassSelector
3294 && pseudo_name
3295 .as_deref()
3296 .is_some_and(is_selector_list_pseudo_class)
3297 {
3298 self.parse_selector_list_until(&[SyntaxKind::RightParen]);
3299 } else if kind == SyntaxKind::PseudoClassSelector
3300 && pseudo_name.as_deref() == Some("not")
3301 {
3302 self.parse_strict_selector_list_until(&[SyntaxKind::RightParen]);
3303 } else if kind == SyntaxKind::PseudoClassSelector
3304 && pseudo_name.as_deref() == Some("has")
3305 {
3306 self.parse_relative_selector_list_until(&[SyntaxKind::RightParen]);
3307 } else if kind == SyntaxKind::PseudoClassSelector
3308 && pseudo_name.as_deref().is_some_and(is_nth_pseudo_class)
3309 {
3310 self.parse_nth_selector_argument();
3311 } else if kind == SyntaxKind::PseudoClassSelector
3312 && pseudo_name.as_deref() == Some("lang")
3313 {
3314 self.parse_language_selector_argument();
3315 } else if kind == SyntaxKind::PseudoClassSelector
3316 && pseudo_name.as_deref() == Some("dir")
3317 {
3318 self.parse_directionality_selector_argument();
3319 } else {
3320 while !self.at_end() {
3321 match self.current_kind() {
3322 Some(SyntaxKind::RightParen) => break,
3323 Some(kind) if is_selector_boundary(kind) => break,
3324 Some(_) => self.token_current(),
3325 None => break,
3326 }
3327 }
3328 }
3329 self.builder.finish_node();
3330 if self.current_kind() == Some(SyntaxKind::RightParen) {
3331 self.token_current();
3332 }
3333 }
3334 if css_module_scope_kind.is_some() {
3335 self.builder.finish_node();
3336 }
3337 self.builder.finish_node();
3338 }
3339
3340 fn parse_nth_selector_argument(&mut self) {
3341 self.builder.start_node(SyntaxKind::NthSelectorArgument);
3342 self.builder.start_node(SyntaxKind::NthSelectorFormula);
3343 while !self.at_end() {
3344 match self.current_kind() {
3345 Some(SyntaxKind::RightParen) => break,
3346 Some(kind) if is_selector_boundary(kind) => break,
3347 Some(SyntaxKind::Ident) if self.current_text() == Some("of") => break,
3348 Some(_) => self.token_current(),
3349 None => break,
3350 }
3351 }
3352 self.builder.finish_node();
3353
3354 if self.current_kind() == Some(SyntaxKind::Ident) && self.current_text() == Some("of") {
3355 self.builder
3356 .start_node(SyntaxKind::NthSelectorOfSelectorList);
3357 self.token_current();
3358 self.parse_selector_list_until(&[SyntaxKind::RightParen]);
3359 self.builder.finish_node();
3360 }
3361
3362 self.builder.finish_node();
3363 }
3364
3365 fn parse_language_selector_argument(&mut self) {
3366 self.builder
3367 .start_node(SyntaxKind::LanguageSelectorArgument);
3368 while !self.at_end() {
3369 match self.current_kind() {
3370 Some(SyntaxKind::RightParen) => break,
3371 Some(SyntaxKind::Comma) => self.token_current(),
3372 Some(kind) if is_selector_boundary(kind) => break,
3373 Some(kind) if language_tag_token_can_start(kind) => self.parse_language_tag(),
3374 Some(_) => self.token_current(),
3375 None => break,
3376 }
3377 }
3378 self.builder.finish_node();
3379 }
3380
3381 fn parse_language_tag(&mut self) {
3382 self.builder.start_node(SyntaxKind::LanguageTag);
3383 self.token_current();
3384 self.builder.finish_node();
3385 }
3386
3387 fn parse_directionality_selector_argument(&mut self) {
3388 self.builder
3389 .start_node(SyntaxKind::DirectionalitySelectorArgument);
3390 if self
3391 .current_kind()
3392 .is_some_and(language_tag_token_can_start)
3393 {
3394 self.token_current();
3395 }
3396 while !self.at_end() {
3397 match self.current_kind() {
3398 Some(SyntaxKind::RightParen) => break,
3399 Some(kind) if is_selector_boundary(kind) => break,
3400 Some(_) => self.token_current(),
3401 None => break,
3402 }
3403 }
3404 self.builder.finish_node();
3405 }
3406
3407 fn parse_less_extend_rule(&mut self) {
3408 self.builder.start_node(SyntaxKind::LessExtendRule);
3409 if self.current_kind() == Some(SyntaxKind::Colon) {
3410 self.token_current();
3411 }
3412 if self.current_text() == Some("extend") {
3413 self.token_current();
3414 } else {
3415 self.empty_bogus_node(
3416 SyntaxKind::BogusSelector,
3417 ParseErrorCode::ExpectedSelectorName,
3418 "expected Less extend selector",
3419 );
3420 }
3421 if self.current_kind() == Some(SyntaxKind::LeftParen) {
3422 self.token_current();
3423 self.builder.start_node(SyntaxKind::PseudoSelectorArgument);
3424 while !self.at_end() {
3425 match self.current_kind() {
3426 Some(SyntaxKind::RightParen) => break,
3427 Some(kind) if is_selector_boundary(kind) => break,
3428 Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3429 kind,
3430 &[
3431 SyntaxKind::RightParen,
3432 SyntaxKind::Comma,
3433 SyntaxKind::LeftBrace,
3434 SyntaxKind::SassIndent,
3435 SyntaxKind::Semicolon,
3436 SyntaxKind::SassOptionalSemicolon,
3437 ],
3438 ),
3439 Some(_) => self.token_current(),
3440 None => break,
3441 }
3442 }
3443 self.builder.finish_node();
3444 if self.current_kind() == Some(SyntaxKind::RightParen) {
3445 self.token_current();
3446 }
3447 }
3448 self.builder.finish_node();
3449 }
3450
3451 fn parse_combinator(&mut self) {
3452 let has_rhs = self
3453 .next_non_trivia_kind()
3454 .is_some_and(|kind| selector_component_can_start(kind) || is_interpolation_start(kind));
3455 self.builder.start_node(if has_rhs {
3456 SyntaxKind::Combinator
3457 } else {
3458 SyntaxKind::BogusCombinator
3459 });
3460 self.token_current();
3461 if !has_rhs {
3462 self.error_at_current(
3463 ParseErrorCode::UnexpectedCharacter,
3464 "expected selector after combinator",
3465 );
3466 }
3467 self.builder.finish_node();
3468 }
3469
3470 fn parse_whitespace_combinator(&mut self) {
3471 self.builder.start_node(SyntaxKind::Combinator);
3472 while self.current_kind() == Some(SyntaxKind::Whitespace) {
3473 self.token_current();
3474 }
3475 self.builder.finish_node();
3476 }
3477
3478 fn parse_declaration_list(&mut self) {
3479 while !self.at_end() {
3480 self.eat_trivia();
3481 match self.current_kind() {
3482 Some(SyntaxKind::RightBrace | SyntaxKind::SassDedent) | None => break,
3483 Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) => {
3484 self.token_current()
3485 }
3486 Some(SyntaxKind::AtKeyword) if self.current_is_css_module_value_rule() => {
3487 self.parse_css_module_value_rule()
3488 }
3489 Some(SyntaxKind::AtKeyword) if self.current_dialect_at_rule_spec().is_some() => {
3490 self.parse_dialect_at_rule()
3491 }
3492 Some(SyntaxKind::AtKeyword) => self.parse_at_rule(),
3493 Some(_) if self.current_starts_less_namespace_access() => {
3494 self.parse_less_namespace_access()
3495 }
3496 Some(_) if self.current_starts_less_mixin_call() => self.parse_less_mixin_call(),
3497 Some(_) if self.current_starts_scss_nested_property() => {
3498 self.parse_scss_nested_property()
3499 }
3500 Some(_) if self.current_starts_nested_rule() => self.parse_rule(),
3501 Some(SyntaxKind::ScssVariable)
3502 if matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass) =>
3503 {
3504 self.parse_variable_declaration(SyntaxKind::ScssVariableDeclaration)
3505 }
3506 Some(SyntaxKind::LessVariable) if self.dialect == StyleDialect::Less => {
3507 self.parse_variable_declaration(SyntaxKind::LessVariableDeclaration)
3508 }
3509 Some(SyntaxKind::LeftBrace) => {
3510 self.builder.start_node(SyntaxKind::BogusDeclaration);
3511 self.token_current();
3512 self.builder.finish_node();
3513 }
3514 Some(_) => self.parse_declaration(),
3515 }
3516 }
3517 }
3518
3519 fn parse_scss_nested_property(&mut self) {
3520 self.builder.start_node(SyntaxKind::ScssNestedProperty);
3521 self.builder.start_node(SyntaxKind::PropertyName);
3522 while !self.at_end() {
3523 match self.current_kind() {
3524 Some(SyntaxKind::Colon) => break,
3525 Some(
3526 SyntaxKind::Semicolon
3527 | SyntaxKind::SassOptionalSemicolon
3528 | SyntaxKind::RightBrace
3529 | SyntaxKind::SassDedent,
3530 ) => break,
3531 Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3532 kind,
3533 &[
3534 SyntaxKind::Colon,
3535 SyntaxKind::Semicolon,
3536 SyntaxKind::SassOptionalSemicolon,
3537 SyntaxKind::RightBrace,
3538 SyntaxKind::SassDedent,
3539 ],
3540 ),
3541 Some(_) => self.token_current(),
3542 None => break,
3543 }
3544 }
3545 self.builder.finish_node();
3546
3547 if self.current_kind() == Some(SyntaxKind::Colon) {
3548 self.token_current();
3549 }
3550
3551 let block_recovery = [
3552 SyntaxKind::LeftBrace,
3553 SyntaxKind::SassIndent,
3554 SyntaxKind::Semicolon,
3555 SyntaxKind::SassOptionalSemicolon,
3556 SyntaxKind::RightBrace,
3557 SyntaxKind::SassDedent,
3558 ];
3559 if !matches!(
3560 self.current_kind(),
3561 Some(
3562 SyntaxKind::LeftBrace
3563 | SyntaxKind::SassIndent
3564 | SyntaxKind::Semicolon
3565 | SyntaxKind::SassOptionalSemicolon
3566 | SyntaxKind::RightBrace
3567 | SyntaxKind::SassDedent
3568 )
3569 ) {
3570 self.builder.start_node(SyntaxKind::Value);
3571 self.parse_value_or_value_list_until(&block_recovery);
3572 self.builder.finish_node();
3573 }
3574
3575 match self.current_kind() {
3576 Some(SyntaxKind::LeftBrace) => self.parse_declaration_block(),
3577 Some(SyntaxKind::SassIndent) => self.parse_sass_indented_nested_property_block(),
3578 Some(_) => self.consume_until_recovery(&[
3579 SyntaxKind::Semicolon,
3580 SyntaxKind::SassOptionalSemicolon,
3581 SyntaxKind::RightBrace,
3582 SyntaxKind::SassDedent,
3583 ]),
3584 None => {}
3585 }
3586
3587 if self.current_kind().is_some_and(is_statement_end) {
3588 self.token_current();
3589 }
3590 self.builder.finish_node();
3591 }
3592
3593 fn parse_sass_indented_nested_property_block(&mut self) {
3594 self.builder.start_node(SyntaxKind::SassIndentedBlock);
3595 if self.current_kind() == Some(SyntaxKind::SassIndent) {
3596 self.token_current();
3597 }
3598 self.builder.start_node(SyntaxKind::DeclarationList);
3599 self.parse_declaration_list();
3600 self.builder.finish_node();
3601 if self.current_kind() == Some(SyntaxKind::SassDedent) {
3602 self.token_current();
3603 } else {
3604 self.error_at_current(
3605 ParseErrorCode::UnexpectedCharacter,
3606 "unterminated Sass indented nested property block",
3607 );
3608 }
3609 self.builder.finish_node();
3610 }
3611
3612 fn parse_variable_declaration(&mut self, kind: SyntaxKind) {
3613 let has_colon = self.find_before_recovery(
3614 SyntaxKind::Colon,
3615 &[
3616 SyntaxKind::Semicolon,
3617 SyntaxKind::SassOptionalSemicolon,
3618 SyntaxKind::RightBrace,
3619 SyntaxKind::SassDedent,
3620 ],
3621 );
3622 self.builder
3623 .start_node(variable_declaration_node_kind(kind, has_colon));
3624 self.token_current();
3625 if self.current_kind() == Some(SyntaxKind::Colon) {
3626 self.token_current();
3627 self.eat_value_trivia();
3628 let value_recovery = [
3629 SyntaxKind::Semicolon,
3630 SyntaxKind::SassOptionalSemicolon,
3631 SyntaxKind::RightBrace,
3632 SyntaxKind::SassDedent,
3633 ];
3634 if kind == SyntaxKind::LessVariableDeclaration
3635 && self.current_kind() == Some(SyntaxKind::LeftBrace)
3636 {
3637 self.parse_less_detached_ruleset();
3638 } else {
3639 let has_value = self
3640 .non_trivia_token_from(self.position)
3641 .is_some_and(|(_, kind)| !value_recovery.contains(&kind));
3642 self.builder.start_node(SyntaxKind::Value);
3643 if has_value {
3644 self.parse_value_or_value_list_until(&value_recovery);
3645 } else {
3646 self.empty_bogus_node(
3647 SyntaxKind::BogusValue,
3648 ParseErrorCode::ExpectedValue,
3649 "expected variable value",
3650 );
3651 }
3652 self.builder.finish_node();
3653 }
3654 } else {
3655 self.error_at_current(
3656 ParseErrorCode::UnexpectedCharacter,
3657 "expected variable declaration colon",
3658 );
3659 self.consume_until_recovery(&[
3660 SyntaxKind::Semicolon,
3661 SyntaxKind::SassOptionalSemicolon,
3662 SyntaxKind::RightBrace,
3663 SyntaxKind::SassDedent,
3664 ]);
3665 }
3666 if self.current_kind().is_some_and(is_statement_end) {
3667 self.token_current();
3668 }
3669 self.builder.finish_node();
3670 }
3671
3672 fn parse_less_detached_ruleset(&mut self) {
3673 let closed = self.current_left_brace_has_match();
3674 self.builder.start_node(if closed {
3675 SyntaxKind::LessDetachedRulesetNode
3676 } else {
3677 SyntaxKind::BogusLessDetachedRuleset
3678 });
3679 if self.current_kind() == Some(SyntaxKind::LeftBrace) {
3680 self.token_current();
3681 self.builder.start_node(SyntaxKind::DeclarationList);
3682 self.parse_declaration_list();
3683 self.builder.finish_node();
3684 }
3685 if self.current_kind() == Some(SyntaxKind::RightBrace) {
3686 self.token_current();
3687 } else {
3688 self.error_at_current(
3689 ParseErrorCode::UnexpectedCharacter,
3690 "unterminated Less detached ruleset",
3691 );
3692 }
3693 self.builder.finish_node();
3694 }
3695
3696 fn parse_declaration(&mut self) {
3697 let starts_composes = self.current_text() == Some("composes");
3698 let starts_custom_property = self.current_kind() == Some(SyntaxKind::CustomPropertyName);
3699 let has_colon = self.find_before_recovery(
3700 SyntaxKind::Colon,
3701 &[
3702 SyntaxKind::Semicolon,
3703 SyntaxKind::SassOptionalSemicolon,
3704 SyntaxKind::RightBrace,
3705 SyntaxKind::SassDedent,
3706 SyntaxKind::LeftBrace,
3707 SyntaxKind::SassIndent,
3708 ],
3709 );
3710 let kind = if starts_composes && has_colon {
3711 SyntaxKind::CssModuleComposesDeclaration
3712 } else if starts_composes {
3713 SyntaxKind::BogusComposesDeclaration
3714 } else if has_colon {
3715 SyntaxKind::Declaration
3716 } else {
3717 SyntaxKind::BogusDeclaration
3718 };
3719 self.builder.start_node(kind);
3720 if kind == SyntaxKind::CssModuleComposesDeclaration
3721 && self.current_css_module_scope_context() == Some("global")
3722 {
3723 self.error_at_current(
3724 ParseErrorCode::UnexpectedCharacter,
3725 "composes is not allowed inside :global scope",
3726 );
3727 }
3728 let property_kind = if matches!(
3729 self.current_kind(),
3730 Some(
3731 SyntaxKind::Colon
3732 | SyntaxKind::Semicolon
3733 | SyntaxKind::SassOptionalSemicolon
3734 | SyntaxKind::LeftBrace
3735 | SyntaxKind::SassIndent
3736 | SyntaxKind::RightBrace
3737 | SyntaxKind::SassDedent
3738 )
3739 ) {
3740 SyntaxKind::BogusPropertyName
3741 } else {
3742 SyntaxKind::PropertyName
3743 };
3744 self.builder.start_node(property_kind);
3745 while !self.at_end() {
3746 match self.current_kind() {
3747 Some(
3748 SyntaxKind::Colon
3749 | SyntaxKind::Semicolon
3750 | SyntaxKind::SassOptionalSemicolon
3751 | SyntaxKind::RightBrace
3752 | SyntaxKind::SassDedent,
3753 ) => break,
3754 Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
3755 kind,
3756 &[
3757 SyntaxKind::Colon,
3758 SyntaxKind::Semicolon,
3759 SyntaxKind::SassOptionalSemicolon,
3760 SyntaxKind::RightBrace,
3761 SyntaxKind::SassDedent,
3762 ],
3763 ),
3764 Some(_) => self.token_current(),
3765 None => break,
3766 }
3767 }
3768 self.builder.finish_node();
3769 if property_kind == SyntaxKind::BogusPropertyName {
3770 self.error_at_current(
3771 ParseErrorCode::UnexpectedCharacter,
3772 "expected declaration property name",
3773 );
3774 }
3775
3776 if self.current_kind() == Some(SyntaxKind::Colon) {
3777 self.token_current();
3778 let value_recovery = [
3779 SyntaxKind::Semicolon,
3780 SyntaxKind::SassOptionalSemicolon,
3781 SyntaxKind::RightBrace,
3782 SyntaxKind::SassDedent,
3783 ];
3784 let has_value = self
3785 .non_trivia_token_from(self.position)
3786 .is_some_and(|(_, kind)| !value_recovery.contains(&kind));
3787 self.builder.start_node(SyntaxKind::Value);
3788 if kind == SyntaxKind::CssModuleComposesDeclaration {
3789 self.parse_composes_value_until(&value_recovery);
3790 } else if starts_custom_property {
3791 self.builder.start_node(SyntaxKind::CustomPropertyValue);
3792 self.parse_component_value_list_until(&value_recovery);
3793 self.builder.finish_node();
3794 } else if !has_value {
3795 self.empty_bogus_node(
3796 SyntaxKind::BogusValue,
3797 ParseErrorCode::ExpectedValue,
3798 "expected declaration value",
3799 );
3800 } else {
3801 self.parse_declaration_value_or_value_list_until(&value_recovery);
3802 }
3803 self.builder.finish_node();
3804 } else {
3805 self.consume_until_recovery(&[
3806 SyntaxKind::Semicolon,
3807 SyntaxKind::SassOptionalSemicolon,
3808 SyntaxKind::RightBrace,
3809 SyntaxKind::SassDedent,
3810 ]);
3811 }
3812
3813 if self.current_kind().is_some_and(is_statement_end) {
3814 self.token_current();
3815 }
3816 self.builder.finish_node();
3817 }
3818
3819 fn parse_composes_value_until(&mut self, recovery: &[SyntaxKind]) {
3820 let mut saw_target = false;
3821 if self.current_composes_value_has_multiple_from_clauses(recovery) {
3822 self.error_at_current(
3823 ParseErrorCode::UnexpectedCharacter,
3824 "multiple composes from clauses are not allowed",
3825 );
3826 }
3827 while !self.at_end() {
3828 self.eat_value_trivia();
3829 match self.current_kind() {
3830 Some(kind) if recovery.contains(&kind) => break,
3831 Some(SyntaxKind::Ident) if self.current_text() == Some("from") => {
3832 if !saw_target {
3833 self.empty_bogus_node(
3834 SyntaxKind::BogusComposesTarget,
3835 ParseErrorCode::UnexpectedCharacter,
3836 "expected composes target before from clause",
3837 );
3838 saw_target = true;
3839 }
3840 self.parse_css_module_from_clause(recovery);
3841 }
3842 Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
3843 self.builder.start_node(SyntaxKind::CssModuleComposesTarget);
3844 self.token_current();
3845 self.builder.finish_node();
3846 saw_target = true;
3847 }
3848 Some(kind) if is_interpolation_start(kind) => {
3849 self.parse_interpolation(kind, recovery)
3850 }
3851 Some(_) => self.token_current(),
3852 None => break,
3853 }
3854 }
3855 if !saw_target {
3856 self.empty_bogus_node(
3857 SyntaxKind::BogusComposesTarget,
3858 ParseErrorCode::UnexpectedCharacter,
3859 "expected composes target",
3860 );
3861 }
3862 }
3863
3864 fn current_composes_value_has_multiple_from_clauses(&self, recovery: &[SyntaxKind]) -> bool {
3865 let mut index = self.position;
3866 let mut paren_depth = 0usize;
3867 let mut bracket_depth = 0usize;
3868 let mut brace_depth = 0usize;
3869 let mut from_count = 0usize;
3870 while let Some(token) = self.tokens.get(index) {
3871 if paren_depth == 0
3872 && bracket_depth == 0
3873 && brace_depth == 0
3874 && recovery.contains(&token.kind)
3875 {
3876 break;
3877 }
3878 match token.kind {
3879 SyntaxKind::LeftParen => paren_depth += 1,
3880 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
3881 SyntaxKind::LeftBracket => bracket_depth += 1,
3882 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
3883 SyntaxKind::LeftBrace => brace_depth += 1,
3884 SyntaxKind::RightBrace => brace_depth = brace_depth.saturating_sub(1),
3885 SyntaxKind::Ident
3886 if paren_depth == 0
3887 && bracket_depth == 0
3888 && brace_depth == 0
3889 && token.text == "from" =>
3890 {
3891 from_count += 1;
3892 if from_count > 1 {
3893 return true;
3894 }
3895 }
3896 _ => {}
3897 }
3898 index += 1;
3899 }
3900 false
3901 }
3902
3903 fn parse_css_module_from_clause(&mut self, recovery: &[SyntaxKind]) {
3904 let source = self.non_trivia_token_from(self.position + 1);
3905 let has_source = source.is_some_and(|(_, kind)| !recovery.contains(&kind));
3906 let has_valid_source = source.is_some_and(|(index, kind)| {
3907 self.tokens
3908 .get(index)
3909 .is_some_and(|token| is_css_module_from_source_token(kind, token.text))
3910 });
3911 self.builder.start_node(if has_valid_source {
3912 SyntaxKind::CssModuleFromClause
3913 } else {
3914 SyntaxKind::BogusFromClause
3915 });
3916 self.token_current();
3917 while !self.at_end() {
3918 match self.current_kind() {
3919 Some(kind) if recovery.contains(&kind) => break,
3920 Some(_) => self.token_current(),
3921 None => break,
3922 }
3923 }
3924 if !has_source {
3925 self.error_at_current(
3926 ParseErrorCode::UnexpectedCharacter,
3927 "expected CSS Modules from-clause source",
3928 );
3929 } else if !has_valid_source {
3930 self.error_at_current(
3931 ParseErrorCode::ExpectedValue,
3932 "invalid CSS Modules from-clause source",
3933 );
3934 }
3935 self.builder.finish_node();
3936 }
3937
3938 fn current_css_module_scope_context(&self) -> Option<&'static str> {
3939 let mut open_blocks = Vec::new();
3940 for (index, token) in self.tokens.iter().take(self.position).enumerate() {
3941 match token.kind {
3942 SyntaxKind::LeftBrace | SyntaxKind::SassIndent => open_blocks.push(index),
3943 SyntaxKind::RightBrace | SyntaxKind::SassDedent => {
3944 open_blocks.pop();
3945 }
3946 _ => {}
3947 }
3948 }
3949
3950 if let Some(scope) = open_blocks.iter().copied().find_map(|block_start| {
3951 let header_start = self.header_start_for_block(block_start);
3952 css_module_block_scope_marker_in_header(&self.tokens, header_start, block_start)
3953 }) {
3954 return Some(scope);
3955 }
3956
3957 let block_start = open_blocks.last().copied()?;
3958 let header_start = self.header_start_for_block(block_start);
3959 css_module_header_is_global_only(&self.tokens, header_start, block_start)
3960 .then_some("global")
3961 }
3962
3963 fn header_start_for_block(&self, block_start: usize) -> usize {
3964 let mut index = block_start;
3965 while index > 0 {
3966 let previous = index - 1;
3967 if matches!(
3968 self.tokens[previous].kind,
3969 SyntaxKind::LeftBrace
3970 | SyntaxKind::RightBrace
3971 | SyntaxKind::SassIndent
3972 | SyntaxKind::SassDedent
3973 | SyntaxKind::Semicolon
3974 | SyntaxKind::SassOptionalSemicolon
3975 ) {
3976 break;
3977 }
3978 index = previous;
3979 }
3980 index
3981 }
3982
3983 fn parse_dialect_at_rule(&mut self) {
3984 let Some(spec) = self.current_dialect_at_rule_spec() else {
3985 self.parse_at_rule();
3986 return;
3987 };
3988
3989 self.builder
3990 .start_node(self.current_dialect_at_rule_node_kind(spec));
3991 if self.current_kind() == Some(SyntaxKind::AtKeyword) {
3992 self.token_current();
3993 }
3994 if matches!(
3995 spec.node_kind,
3996 SyntaxKind::ScssUseRule | SyntaxKind::ScssForwardRule
3997 ) {
3998 self.parse_scss_module_prelude(spec.node_kind);
3999 }
4000 if is_scss_control_rule_kind(spec.node_kind)
4001 && !self.current_scss_control_prelude_is_valid(spec.node_kind)
4002 {
4003 self.error_at_current(
4004 ParseErrorCode::ExpectedValue,
4005 "invalid SCSS control prelude",
4006 );
4007 }
4008 while !self.at_end() {
4009 match self.current_kind() {
4010 Some(kind) if is_statement_end(kind) => {
4011 self.token_current();
4012 break;
4013 }
4014 Some(SyntaxKind::LeftBrace) => {
4015 match spec.block_kind {
4016 AtRuleBlockKind::GroupRuleList => self.parse_group_at_rule_block(),
4017 AtRuleBlockKind::DeclarationList => self.parse_declaration_block(),
4018 AtRuleBlockKind::Keyframes => self.parse_keyframes_block(),
4019 AtRuleBlockKind::Raw => self.consume_balanced_block(),
4020 }
4021 break;
4022 }
4023 Some(SyntaxKind::SassIndent) => {
4024 self.parse_sass_indented_at_rule_block(spec.block_kind);
4025 break;
4026 }
4027 Some(_) => self.token_current(),
4028 None => break,
4029 }
4030 }
4031 self.builder.finish_node();
4032 }
4033
4034 fn parse_scss_module_prelude(&mut self, node_kind: SyntaxKind) {
4035 self.validate_scss_module_prelude(node_kind);
4036 while !self.at_end() {
4037 match self.current_kind() {
4038 Some(kind)
4039 if is_statement_end(kind)
4040 || kind == SyntaxKind::LeftBrace
4041 || kind == SyntaxKind::SassIndent =>
4042 {
4043 break;
4044 }
4045 Some(SyntaxKind::Ident | SyntaxKind::KeywordWith)
4046 if self.current_text() == Some("with")
4047 && self
4048 .non_trivia_token_from(self.position + 1)
4049 .is_some_and(|(_, kind)| kind == SyntaxKind::LeftParen) =>
4050 {
4051 self.parse_scss_module_config()
4052 }
4053 Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
4054 kind,
4055 &[
4056 SyntaxKind::Semicolon,
4057 SyntaxKind::SassOptionalSemicolon,
4058 SyntaxKind::LeftBrace,
4059 SyntaxKind::SassIndent,
4060 ],
4061 ),
4062 Some(_) => self.token_current(),
4063 None => break,
4064 }
4065 }
4066 }
4067
4068 fn validate_scss_module_prelude(&mut self, node_kind: SyntaxKind) {
4069 let recovery = [
4070 SyntaxKind::Semicolon,
4071 SyntaxKind::SassOptionalSemicolon,
4072 SyntaxKind::LeftBrace,
4073 SyntaxKind::SassIndent,
4074 ];
4075 let Some((source_index, source_kind)) = self.non_trivia_token_from(self.position) else {
4076 self.error_at_current(ParseErrorCode::ExpectedValue, "expected SCSS module source");
4077 return;
4078 };
4079 if recovery.contains(&source_kind) || !is_scss_module_source_token(source_kind) {
4080 let range = self
4081 .tokens
4082 .get(source_index)
4083 .map(|token| token.range)
4084 .unwrap_or_else(|| self.current_range());
4085 self.errors.push(ParseError {
4086 code: ParseErrorCode::ExpectedValue,
4087 range,
4088 message: "expected SCSS module source",
4089 });
4090 }
4091
4092 let mut index = source_index;
4093 while let Some(token) = self.tokens.get(index).copied() {
4094 if recovery.contains(&token.kind) {
4095 break;
4096 }
4097 if token.kind == SyntaxKind::Ident {
4098 if token.text.eq_ignore_ascii_case("as") {
4099 let next_kind = self.non_trivia_token_from(index + 1).map(|(_, kind)| kind);
4100 if next_kind.is_none_or(|kind| {
4101 recovery.contains(&kind) || !is_scss_module_namespace_token(kind)
4102 }) {
4103 self.errors.push(ParseError {
4104 code: ParseErrorCode::ExpectedValue,
4105 range: token.range,
4106 message: "expected SCSS module namespace",
4107 });
4108 }
4109 } else if token.text.eq_ignore_ascii_case("with") {
4110 let next_kind = self.non_trivia_token_from(index + 1).map(|(_, kind)| kind);
4111 if next_kind != Some(SyntaxKind::LeftParen) {
4112 self.errors.push(ParseError {
4113 code: ParseErrorCode::ExpectedValue,
4114 range: token.range,
4115 message: "expected SCSS module configuration",
4116 });
4117 }
4118 } else if matches_ignore_ascii_case(token.text, &["show", "hide"]) {
4119 if node_kind != SyntaxKind::ScssForwardRule {
4120 self.errors.push(ParseError {
4121 code: ParseErrorCode::UnexpectedCharacter,
4122 range: token.range,
4123 message: "unexpected SCSS module visibility clause",
4124 });
4125 }
4126 let next_kind = self.non_trivia_token_from(index + 1).map(|(_, kind)| kind);
4127 if next_kind.is_none_or(|kind| {
4128 recovery.contains(&kind) || !is_scss_module_visibility_name_token(kind)
4129 }) {
4130 self.errors.push(ParseError {
4131 code: ParseErrorCode::ExpectedValue,
4132 range: token.range,
4133 message: "expected SCSS module visibility name",
4134 });
4135 }
4136 }
4137 }
4138 index += 1;
4139 }
4140 }
4141
4142 fn current_scss_control_prelude_is_valid(&self, node_kind: SyntaxKind) -> bool {
4143 let recovery = [
4144 SyntaxKind::LeftBrace,
4145 SyntaxKind::SassIndent,
4146 SyntaxKind::Semicolon,
4147 SyntaxKind::SassOptionalSemicolon,
4148 SyntaxKind::RightBrace,
4149 SyntaxKind::SassDedent,
4150 ];
4151 match node_kind {
4152 SyntaxKind::ScssControlIf | SyntaxKind::ScssControlWhile => self
4153 .non_trivia_token_from(self.position)
4154 .is_some_and(|(_, kind)| !recovery.contains(&kind)),
4155 SyntaxKind::ScssControlFor => {
4156 self.non_trivia_token_from(self.position)
4157 .is_some_and(|(_, kind)| kind == SyntaxKind::ScssVariable)
4158 && self.find_text_before_recovery("from", &recovery)
4159 && (self.find_text_before_recovery("to", &recovery)
4160 || self.find_text_before_recovery("through", &recovery))
4161 }
4162 SyntaxKind::ScssControlEach => {
4163 self.non_trivia_token_from(self.position)
4164 .is_some_and(|(_, kind)| kind == SyntaxKind::ScssVariable)
4165 && self.find_text_before_recovery("in", &recovery)
4166 }
4167 SyntaxKind::ScssControlElse => true,
4168 _ => true,
4169 }
4170 }
4171
4172 fn parse_scss_module_config(&mut self) {
4173 let has_balanced_config = self.current_scss_module_config_has_balanced_parens();
4174 self.builder.start_node(if has_balanced_config {
4175 SyntaxKind::ScssModuleConfig
4176 } else {
4177 SyntaxKind::BogusScssModuleConfig
4178 });
4179 self.token_current();
4180 self.eat_trivia();
4181 if self.current_kind() == Some(SyntaxKind::LeftParen) {
4182 self.parse_balanced_parenthesized_prelude_until(
4183 None,
4184 &[
4185 SyntaxKind::LeftBrace,
4186 SyntaxKind::SassIndent,
4187 SyntaxKind::Semicolon,
4188 SyntaxKind::SassOptionalSemicolon,
4189 ],
4190 );
4191 }
4192 self.builder.finish_node();
4193 }
4194
4195 fn parse_css_module_value_rule(&mut self) {
4196 let has_name = self
4197 .non_trivia_token_from(self.position + 1)
4198 .and_then(|(index, kind)| {
4199 self.tokens
4200 .get(index)
4201 .map(|token| (kind, token.text != "from"))
4202 })
4203 .is_some_and(|(kind, allowed_name)| {
4204 allowed_name && matches!(kind, SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
4205 });
4206 let has_from = self.find_text_before_recovery(
4207 "from",
4208 &[
4209 SyntaxKind::Semicolon,
4210 SyntaxKind::SassOptionalSemicolon,
4211 SyntaxKind::LeftBrace,
4212 SyntaxKind::SassIndent,
4213 ],
4214 );
4215 let has_colon = self.find_before_recovery(
4216 SyntaxKind::Colon,
4217 &[
4218 SyntaxKind::Semicolon,
4219 SyntaxKind::SassOptionalSemicolon,
4220 SyntaxKind::LeftBrace,
4221 SyntaxKind::SassIndent,
4222 ],
4223 );
4224 let kind = if !has_name {
4225 SyntaxKind::BogusCssModuleBlock
4226 } else if has_from && !has_colon {
4227 SyntaxKind::CssModuleImportBlock
4228 } else {
4229 SyntaxKind::CssModuleExportBlock
4230 };
4231
4232 self.builder.start_node(kind);
4233 self.token_current();
4234 if !has_name {
4235 self.error_at_current(
4236 ParseErrorCode::UnexpectedCharacter,
4237 "expected CSS Modules @value name",
4238 );
4239 }
4240 if has_colon {
4241 self.parse_css_module_value_export();
4242 } else {
4243 self.parse_css_module_value_import_or_statement();
4244 }
4245 if self.current_kind().is_some_and(is_statement_end) {
4246 self.token_current();
4247 }
4248 self.builder.finish_node();
4249 }
4250
4251 fn parse_css_module_value_export(&mut self) {
4252 self.parse_css_module_token_definitions_until(&[
4253 SyntaxKind::Colon,
4254 SyntaxKind::Semicolon,
4255 SyntaxKind::SassOptionalSemicolon,
4256 ]);
4257 if self.current_kind() == Some(SyntaxKind::Colon) {
4258 self.token_current();
4259 self.builder.start_node(SyntaxKind::Value);
4260 self.parse_css_module_token_references_until(&[
4261 SyntaxKind::Semicolon,
4262 SyntaxKind::SassOptionalSemicolon,
4263 ]);
4264 self.builder.finish_node();
4265 }
4266 }
4267
4268 fn parse_css_module_value_import_or_statement(&mut self) {
4269 self.parse_css_module_token_definitions_until(&[
4270 SyntaxKind::Semicolon,
4271 SyntaxKind::SassOptionalSemicolon,
4272 ]);
4273 }
4274
4275 fn parse_css_module_token_definitions_until(&mut self, recovery: &[SyntaxKind]) {
4276 while !self.at_end() {
4277 match self.current_kind() {
4278 Some(kind) if recovery.contains(&kind) => break,
4279 Some(SyntaxKind::Ident) if self.current_text() == Some("from") => {
4280 self.parse_css_module_from_clause(recovery);
4281 break;
4282 }
4283 Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
4284 self.builder.start_node(SyntaxKind::TokenDefinition);
4285 self.token_current();
4286 self.builder.finish_node();
4287 }
4288 Some(_) => self.token_current(),
4289 None => break,
4290 }
4291 }
4292 }
4293
4294 fn parse_css_module_token_references_until(&mut self, recovery: &[SyntaxKind]) {
4295 while !self.at_end() {
4296 self.eat_value_trivia();
4297 match self.current_kind() {
4298 Some(kind) if recovery.contains(&kind) => break,
4299 Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
4300 self.builder.start_node(SyntaxKind::TokenReference);
4301 self.token_current();
4302 self.builder.finish_node();
4303 }
4304 Some(kind) if is_interpolation_start(kind) => {
4305 self.parse_interpolation(kind, recovery)
4306 }
4307 Some(_) => self.token_current(),
4308 None => break,
4309 }
4310 }
4311 }
4312
4313 fn parse_less_mixin_header(&mut self) {
4314 self.builder.start_node(SyntaxKind::SelectorList);
4315 self.parse_until_recovery_with_optional_less_guard(&[SyntaxKind::LeftBrace]);
4316 self.builder.finish_node();
4317 }
4318
4319 fn parse_less_mixin_call(&mut self) {
4320 self.builder.start_node(SyntaxKind::LessMixinCall);
4321 self.parse_until_recovery_with_optional_less_guard(&[
4322 SyntaxKind::Semicolon,
4323 SyntaxKind::SassOptionalSemicolon,
4324 SyntaxKind::RightBrace,
4325 SyntaxKind::SassDedent,
4326 ]);
4327 if self.current_kind().is_some_and(is_statement_end) {
4328 self.token_current();
4329 }
4330 self.builder.finish_node();
4331 }
4332
4333 fn parse_less_namespace_access(&mut self) {
4334 self.builder.start_node(SyntaxKind::LessNamespaceAccess);
4335 while !self.at_end() {
4336 match self.current_kind() {
4337 Some(
4338 SyntaxKind::Semicolon
4339 | SyntaxKind::SassOptionalSemicolon
4340 | SyntaxKind::RightBrace
4341 | SyntaxKind::SassDedent
4342 | SyntaxKind::LeftBrace
4343 | SyntaxKind::SassIndent,
4344 ) => break,
4345 Some(_) if self.current_starts_less_mixin_call() => {
4346 self.parse_less_mixin_call();
4347 break;
4348 }
4349 Some(_) => self.token_current(),
4350 None => break,
4351 }
4352 }
4353 if self.current_kind().is_some_and(is_statement_end) {
4354 self.token_current();
4355 }
4356 self.builder.finish_node();
4357 }
4358
4359 fn parse_until_recovery_with_optional_less_guard(&mut self, recovery: &[SyntaxKind]) {
4360 let mut guard_open = false;
4361 while !self.at_end() {
4362 match self.current_kind() {
4363 Some(kind) if recovery.contains(&kind) => break,
4364 Some(SyntaxKind::Ident) if self.current_text() == Some("when") && !guard_open => {
4365 self.builder.start_node(
4366 if self.current_less_guard_has_condition_before(recovery) {
4367 SyntaxKind::LessMixinGuard
4368 } else {
4369 SyntaxKind::BogusLessGuard
4370 },
4371 );
4372 guard_open = true;
4373 self.token_current();
4374 }
4375 Some(_) => self.token_current(),
4376 None => break,
4377 }
4378 }
4379 if guard_open {
4380 self.builder.finish_node();
4381 }
4382 }
4383
4384 fn parse_value_until(&mut self, recovery: &[SyntaxKind]) {
4385 while !self.at_end() {
4386 self.eat_value_trivia();
4387 if matches!(self.current_kind(), Some(kind) if recovery.contains(&kind)) {
4388 break;
4389 }
4390 if self.at_end() {
4391 break;
4392 }
4393 self.parse_value_expression(0, recovery);
4394 }
4395 }
4396
4397 fn parse_value_or_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4398 if self.current_value_has_top_level_comma_before(recovery) {
4399 self.parse_value_list_until(recovery);
4400 } else {
4401 self.parse_value_until(recovery);
4402 }
4403 }
4404
4405 fn parse_declaration_value_or_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4406 if self.current_value_has_top_level_comma_before(recovery) {
4407 self.parse_declaration_value_list_until(recovery);
4408 } else {
4409 self.parse_declaration_value_until(recovery);
4410 }
4411 }
4412
4413 fn parse_declaration_value_until(&mut self, recovery: &[SyntaxKind]) {
4414 let mut saw_value = false;
4415 while !self.at_end() {
4416 self.eat_value_trivia();
4417 if matches!(self.current_kind(), Some(kind) if recovery.contains(&kind)) {
4418 break;
4419 }
4420 if saw_value && self.current_starts_missing_semicolon_declaration(recovery) {
4421 self.error_at_current(
4422 ParseErrorCode::UnexpectedCharacter,
4423 "expected semicolon between declarations",
4424 );
4425 break;
4426 }
4427 if self.at_end() {
4428 break;
4429 }
4430 self.parse_value_expression(0, recovery);
4431 saw_value = true;
4432 }
4433 }
4434
4435 fn parse_declaration_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4436 self.builder
4437 .start_node(if self.current_value_list_is_bogus(recovery) {
4438 SyntaxKind::BogusValueList
4439 } else {
4440 SyntaxKind::ValueList
4441 });
4442 let item_recovery = value_list_item_recovery(recovery);
4443 let mut saw_item = false;
4444 while !self.at_end() {
4445 self.eat_value_trivia();
4446 match self.current_kind() {
4447 Some(kind) if recovery.contains(&kind) => break,
4448 Some(SyntaxKind::Comma) => self.token_current(),
4449 Some(_)
4450 if saw_item && self.current_starts_missing_semicolon_declaration(recovery) =>
4451 {
4452 self.error_at_current(
4453 ParseErrorCode::UnexpectedCharacter,
4454 "expected semicolon between declarations",
4455 );
4456 break;
4457 }
4458 Some(_) => {
4459 self.parse_value_expression(0, &item_recovery);
4460 saw_item = true;
4461 }
4462 None => break,
4463 }
4464 }
4465 self.builder.finish_node();
4466 }
4467
4468 fn parse_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4469 self.builder
4470 .start_node(if self.current_value_list_is_bogus(recovery) {
4471 SyntaxKind::BogusValueList
4472 } else {
4473 SyntaxKind::ValueList
4474 });
4475 let item_recovery = value_list_item_recovery(recovery);
4476 while !self.at_end() {
4477 self.eat_value_trivia();
4478 match self.current_kind() {
4479 Some(kind) if recovery.contains(&kind) => break,
4480 Some(SyntaxKind::Comma) => self.token_current(),
4481 Some(_) => self.parse_value_expression(0, &item_recovery),
4482 None => break,
4483 }
4484 }
4485 self.builder.finish_node();
4486 }
4487
4488 fn parse_component_value(&mut self, recovery: &[SyntaxKind]) {
4489 self.builder.start_node(SyntaxKind::ComponentValue);
4490 self.parse_component_value_inner(recovery);
4491 self.builder.finish_node();
4492 }
4493
4494 fn parse_component_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4495 self.builder.start_node(SyntaxKind::ComponentValueList);
4496 while !self.at_end() {
4497 self.eat_value_trivia();
4498 match self.current_kind() {
4499 Some(kind) if recovery.contains(&kind) => break,
4500 Some(_) => self.parse_component_value(recovery),
4501 None => break,
4502 }
4503 }
4504 self.builder.finish_node();
4505 }
4506
4507 fn parse_comma_separated_component_value_list_until(&mut self, recovery: &[SyntaxKind]) {
4508 self.builder
4509 .start_node(SyntaxKind::CommaSeparatedComponentValueList);
4510 let item_recovery = comma_separated_component_value_list_item_recovery(recovery);
4511 while !self.at_end() {
4512 self.eat_value_trivia();
4513 match self.current_kind() {
4514 Some(kind) if recovery.contains(&kind) => break,
4515 Some(SyntaxKind::Comma) => self.token_current(),
4516 Some(_) => self.parse_component_value(&item_recovery),
4517 None => break,
4518 }
4519 }
4520 self.builder.finish_node();
4521 }
4522
4523 fn parse_component_value_inner(&mut self, recovery: &[SyntaxKind]) {
4524 self.eat_value_trivia();
4525 match self.current_kind() {
4526 Some(kind) if recovery.contains(&kind) => {
4527 self.empty_bogus_node(
4528 SyntaxKind::BogusValue,
4529 ParseErrorCode::ExpectedValue,
4530 "expected component value",
4531 );
4532 }
4533 Some(SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen) => {
4534 self.parse_simple_block(recovery)
4535 }
4536 Some(SyntaxKind::Ident) if self.next_kind() == Some(SyntaxKind::LeftParen) => {
4537 self.parse_function_call(recovery)
4538 }
4539 Some(kind) if is_component_value_atom_start(kind) => self.parse_value_prefix(recovery),
4540 Some(_) => self.token_current(),
4541 None => {
4542 self.empty_bogus_node(
4543 SyntaxKind::BogusValue,
4544 ParseErrorCode::ExpectedValue,
4545 "expected component value",
4546 );
4547 }
4548 }
4549 }
4550
4551 fn parse_simple_block_entry_point(&mut self, recovery: &[SyntaxKind]) {
4552 self.eat_value_trivia();
4553 match self.current_kind() {
4554 Some(SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen) => {
4555 self.parse_simple_block(recovery)
4556 }
4557 Some(_) | None => {
4558 self.empty_bogus_node(
4559 SyntaxKind::BogusSimpleBlock,
4560 ParseErrorCode::ExpectedValue,
4561 "expected simple block",
4562 );
4563 }
4564 }
4565 }
4566
4567 fn parse_simple_block(&mut self, recovery: &[SyntaxKind]) {
4568 let Some(open_kind) = self.current_kind() else {
4569 self.empty_bogus_node(
4570 SyntaxKind::BogusSimpleBlock,
4571 ParseErrorCode::ExpectedValue,
4572 "expected simple block",
4573 );
4574 return;
4575 };
4576 let Some(close_kind) = matching_simple_block_close(open_kind) else {
4577 self.empty_bogus_node(
4578 SyntaxKind::BogusSimpleBlock,
4579 ParseErrorCode::ExpectedValue,
4580 "expected simple block",
4581 );
4582 return;
4583 };
4584
4585 let block_kind = if self.current_simple_block_has_matching_close(recovery) {
4586 SyntaxKind::SimpleBlock
4587 } else {
4588 SyntaxKind::BogusSimpleBlock
4589 };
4590 self.builder.start_node(block_kind);
4591 self.token_current();
4592
4593 let block_recovery = simple_block_recovery(close_kind, recovery);
4594 while !self.at_end() {
4595 self.eat_value_trivia();
4596 match self.current_kind() {
4597 Some(kind) if kind == close_kind => break,
4598 Some(kind) if recovery.contains(&kind) => break,
4599 Some(_) => self.parse_component_value(&block_recovery),
4600 None => break,
4601 }
4602 }
4603
4604 if self.current_kind() == Some(close_kind) {
4605 self.token_current();
4606 } else {
4607 self.error_at_current(
4608 ParseErrorCode::UnexpectedCharacter,
4609 "unterminated simple block",
4610 );
4611 }
4612 self.builder.finish_node();
4613 }
4614
4615 fn parse_value_expression(&mut self, min_binding_power: u8, recovery: &[SyntaxKind]) {
4616 self.eat_value_trivia();
4617 let checkpoint = self.builder.checkpoint();
4618 self.parse_value_prefix(recovery);
4619
4620 loop {
4621 self.eat_value_trivia();
4622 let Some(operator) = self.current_kind() else {
4623 break;
4624 };
4625 if recovery.contains(&operator) {
4626 break;
4627 }
4628 let Some((left_binding_power, right_binding_power)) = infix_binding_power(operator)
4629 else {
4630 break;
4631 };
4632 if left_binding_power < min_binding_power {
4633 break;
4634 }
4635
4636 self.builder
4637 .start_node_at(checkpoint, SyntaxKind::BinaryExpression);
4638 self.token_current();
4639 self.parse_value_expression(right_binding_power, recovery);
4640 self.builder.finish_node();
4641 }
4642 }
4643
4644 fn parse_value_prefix(&mut self, recovery: &[SyntaxKind]) {
4645 match self.current_kind() {
4646 Some(SyntaxKind::Plus | SyntaxKind::Minus) => {
4647 self.builder.start_node(SyntaxKind::UnaryExpression);
4648 self.token_current();
4649 self.parse_value_expression(5, recovery);
4650 self.builder.finish_node();
4651 }
4652 Some(SyntaxKind::Ident)
4653 if self
4654 .current_text()
4655 .is_some_and(|text| text.eq_ignore_ascii_case("url"))
4656 && self.next_kind() == Some(SyntaxKind::LeftParen) =>
4657 {
4658 self.builder.start_node(SyntaxKind::UrlValue);
4659 self.parse_function_call(recovery);
4660 self.builder.finish_node();
4661 }
4662 Some(SyntaxKind::Ident) if self.next_kind() == Some(SyntaxKind::LeftParen) => {
4663 self.parse_function_call(recovery)
4664 }
4665 Some(SyntaxKind::Number) => {
4666 self.builder.start_node(SyntaxKind::NumberValue);
4667 self.token_current();
4668 self.builder.finish_node();
4669 }
4670 Some(SyntaxKind::Percentage) => {
4671 self.builder.start_node(SyntaxKind::PercentageValue);
4672 self.token_current();
4673 self.builder.finish_node();
4674 }
4675 Some(SyntaxKind::Dimension) => {
4676 self.builder.start_node(SyntaxKind::DimensionValue);
4677 self.token_current();
4678 self.builder.finish_node();
4679 }
4680 Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {
4681 self.builder.start_node(SyntaxKind::IdentifierValue);
4682 self.token_current();
4683 self.builder.finish_node();
4684 }
4685 Some(SyntaxKind::String | SyntaxKind::LessEscapedString) => {
4686 self.builder.start_node(SyntaxKind::StringValue);
4687 self.token_current();
4688 self.builder.finish_node();
4689 }
4690 Some(SyntaxKind::UnicodeRange) => {
4691 self.builder.start_node(SyntaxKind::UnicodeRangeValue);
4692 self.token_current();
4693 self.builder.finish_node();
4694 }
4695 Some(SyntaxKind::Hash) => {
4696 self.builder.start_node(SyntaxKind::ColorValue);
4697 self.token_current();
4698 self.builder.finish_node();
4699 }
4700 Some(SyntaxKind::Url) => {
4701 self.builder.start_node(SyntaxKind::UrlValue);
4702 self.token_current();
4703 self.builder.finish_node();
4704 }
4705 Some(SyntaxKind::BadUrl) => {
4706 self.builder.start_node(SyntaxKind::BogusValue);
4707 self.token_current();
4708 self.builder.finish_node();
4709 }
4710 Some(SyntaxKind::BadString) => {
4711 self.builder.start_node(SyntaxKind::BogusValue);
4712 self.token_current();
4713 self.builder.finish_node();
4714 }
4715 Some(SyntaxKind::Important) => {
4716 self.builder.start_node(SyntaxKind::ImportantAnnotation);
4717 self.token_current();
4718 self.builder.finish_node();
4719 }
4720 Some(SyntaxKind::Delim) if self.current_split_important_annotation() => {
4721 self.parse_split_important_annotation()
4722 }
4723 Some(SyntaxKind::Delim) if self.current_scss_variable_flag_annotation() => {
4724 self.parse_scss_variable_flag_annotation()
4725 }
4726 Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(kind, recovery),
4727 Some(SyntaxKind::ScssVariable) => {
4728 self.builder.start_node(SyntaxKind::ScssVariableReference);
4729 self.token_current();
4730 self.builder.finish_node();
4731 }
4732 Some(SyntaxKind::LessVariable) => {
4733 self.builder.start_node(SyntaxKind::LessVariableReference);
4734 self.token_current();
4735 self.builder.finish_node();
4736 }
4737 Some(SyntaxKind::LessPropertyVariableToken) => {
4738 self.builder.start_node(SyntaxKind::LessPropertyVariable);
4739 self.token_current();
4740 self.builder.finish_node();
4741 }
4742 Some(SyntaxKind::LeftBrace) => self.parse_simple_block(recovery),
4743 Some(SyntaxKind::LeftParen) => self.parse_parenthesized_expression(recovery),
4744 Some(SyntaxKind::LeftBracket) => self.parse_bracketed_value(recovery),
4745 Some(kind) if recovery.contains(&kind) => {
4746 self.empty_bogus_node(
4747 SyntaxKind::BogusValue,
4748 ParseErrorCode::ExpectedValue,
4749 "expected value",
4750 );
4751 }
4752 Some(SyntaxKind::Delim) => {
4753 self.builder.start_node(SyntaxKind::BogusToken);
4754 self.token_current();
4755 self.builder.finish_node();
4756 }
4757 Some(_) => {
4758 self.builder.start_node(SyntaxKind::BogusValue);
4759 self.error_at_current(ParseErrorCode::ExpectedValue, "expected value");
4760 self.token_current();
4761 self.builder.finish_node();
4762 }
4763 None => {
4764 self.empty_bogus_node(
4765 SyntaxKind::BogusValue,
4766 ParseErrorCode::ExpectedValue,
4767 "expected value",
4768 );
4769 }
4770 }
4771 }
4772
4773 fn parse_split_important_annotation(&mut self) {
4774 self.builder.start_node(SyntaxKind::ImportantAnnotation);
4775 self.token_current();
4776 self.eat_value_trivia();
4777 if self
4778 .current_text()
4779 .is_some_and(|text| text.eq_ignore_ascii_case("important"))
4780 {
4781 self.token_current();
4782 }
4783 self.builder.finish_node();
4784 }
4785
4786 fn parse_scss_variable_flag_annotation(&mut self) {
4787 self.builder.start_node(SyntaxKind::ScssVariableFlag);
4788 self.token_current();
4789 self.eat_value_trivia();
4790 self.token_current();
4791 self.builder.finish_node();
4792 }
4793
4794 fn eat_value_trivia(&mut self) {
4795 while matches!(self.current_kind(), Some(kind) if kind.is_trivia()) {
4796 self.token_current();
4797 }
4798 }
4799
4800 fn parse_function_call(&mut self, recovery: &[SyntaxKind]) {
4801 let function_name = self.current_text().map(str::to_owned);
4802 let function_range = self.current_range();
4803 let argument_count = self.current_function_top_level_argument_count_before(recovery);
4804 let has_empty_argument_slot =
4805 self.current_function_has_empty_top_level_argument_slot_before(recovery);
4806 let argument_head = self.current_function_first_argument_token_before(recovery);
4807 let specialized_kind = function_name.as_deref().and_then(specialized_function_kind);
4808 let closed = self.current_function_has_closing_paren_before(recovery);
4809 let function_kind = if closed {
4810 SyntaxKind::FunctionCall
4811 } else {
4812 SyntaxKind::BogusFunctionCall
4813 };
4814 let arguments_kind = if closed {
4815 SyntaxKind::FunctionArguments
4816 } else {
4817 SyntaxKind::BogusFunctionArguments
4818 };
4819
4820 self.builder.start_node(function_kind);
4821 if let Some(kind) = specialized_kind {
4822 self.builder.start_node(kind);
4823 }
4824 self.token_current();
4825 if self.current_kind() == Some(SyntaxKind::LeftParen) {
4826 self.token_current();
4827 self.builder.start_node(arguments_kind);
4828 let argument_recovery = function_argument_recovery(recovery);
4829 self.parse_value_or_value_list_until(&argument_recovery);
4830 self.builder.finish_node();
4831 if self.current_kind() == Some(SyntaxKind::RightParen) {
4832 self.token_current();
4833 } else {
4834 self.error_at_current(
4835 ParseErrorCode::UnexpectedCharacter,
4836 "unterminated function call",
4837 );
4838 }
4839 }
4840 if let Some(function_name) = function_name {
4841 if let Some(argument_count) = argument_count {
4842 self.validate_function_argument_count(
4843 &function_name,
4844 argument_count,
4845 function_range,
4846 );
4847 }
4848 if let Some(true) = has_empty_argument_slot {
4849 self.validate_function_argument_slots(&function_name, function_range);
4850 }
4851 self.validate_function_argument_head(&function_name, argument_head, function_range);
4852 }
4853 if specialized_kind.is_some() {
4854 self.builder.finish_node();
4855 }
4856 self.builder.finish_node();
4857 }
4858
4859 fn current_function_top_level_argument_count_before(
4860 &self,
4861 recovery: &[SyntaxKind],
4862 ) -> Option<usize> {
4863 if self.next_kind() != Some(SyntaxKind::LeftParen) {
4864 return None;
4865 }
4866
4867 let mut index = self.position + 2;
4868 let mut depth = 0usize;
4869 let mut comma_count = 0usize;
4870 let mut saw_argument = false;
4871 while let Some(token) = self.tokens.get(index) {
4872 match token.kind {
4873 kind if depth == 0 && recovery.contains(&kind) => return None,
4874 SyntaxKind::RightParen if depth == 0 => {
4875 return Some(if saw_argument { comma_count + 1 } else { 0 });
4876 }
4877 SyntaxKind::Comma if depth == 0 => {
4878 comma_count += 1;
4879 saw_argument = false;
4880 }
4881 kind if kind.is_trivia() => {}
4882 SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen => {
4883 depth += 1;
4884 saw_argument = true;
4885 }
4886 SyntaxKind::RightBrace | SyntaxKind::RightBracket | SyntaxKind::RightParen => {
4887 depth = depth.saturating_sub(1);
4888 saw_argument = true;
4889 }
4890 _ => saw_argument = true,
4891 }
4892 index += 1;
4893 }
4894 None
4895 }
4896
4897 fn current_function_has_empty_top_level_argument_slot_before(
4898 &self,
4899 recovery: &[SyntaxKind],
4900 ) -> Option<bool> {
4901 if self.next_kind() != Some(SyntaxKind::LeftParen) {
4902 return None;
4903 }
4904
4905 let mut index = self.position + 2;
4906 let mut depth = 0usize;
4907 let mut expecting_argument = true;
4908 let mut saw_argument = false;
4909 while let Some(token) = self.tokens.get(index) {
4910 match token.kind {
4911 kind if depth == 0 && recovery.contains(&kind) => return None,
4912 SyntaxKind::RightParen if depth == 0 => {
4913 return Some(expecting_argument && saw_argument);
4914 }
4915 SyntaxKind::Comma if depth == 0 => {
4916 if expecting_argument {
4917 return Some(true);
4918 }
4919 expecting_argument = true;
4920 }
4921 kind if kind.is_trivia() => {}
4922 SyntaxKind::LeftBrace | SyntaxKind::LeftBracket | SyntaxKind::LeftParen => {
4923 depth += 1;
4924 expecting_argument = false;
4925 saw_argument = true;
4926 }
4927 SyntaxKind::RightBrace | SyntaxKind::RightBracket | SyntaxKind::RightParen => {
4928 depth = depth.saturating_sub(1);
4929 expecting_argument = false;
4930 saw_argument = true;
4931 }
4932 _ => {
4933 expecting_argument = false;
4934 saw_argument = true;
4935 }
4936 }
4937 index += 1;
4938 }
4939 None
4940 }
4941
4942 fn current_function_first_argument_token_before(
4943 &self,
4944 recovery: &[SyntaxKind],
4945 ) -> Option<Token<'text>> {
4946 if self.next_kind() != Some(SyntaxKind::LeftParen) {
4947 return None;
4948 }
4949
4950 let mut index = self.position + 2;
4951 while let Some(token) = self.tokens.get(index).copied() {
4952 match token.kind {
4953 kind if recovery.contains(&kind) => return None,
4954 SyntaxKind::RightParen => return None,
4955 kind if kind.is_trivia() => {}
4956 _ => return Some(token),
4957 }
4958 index += 1;
4959 }
4960 None
4961 }
4962
4963 fn validate_function_argument_count(
4964 &mut self,
4965 function_name: &str,
4966 argument_count: usize,
4967 range: TextRange,
4968 ) {
4969 if function_argument_count_is_valid(function_name, argument_count) {
4970 return;
4971 }
4972 self.errors.push(ParseError {
4973 code: ParseErrorCode::ExpectedValue,
4974 range,
4975 message: "invalid function argument count",
4976 });
4977 }
4978
4979 fn validate_function_argument_slots(&mut self, function_name: &str, range: TextRange) {
4980 if !function_requires_filled_top_level_arguments(function_name) {
4981 return;
4982 }
4983 self.errors.push(ParseError {
4984 code: ParseErrorCode::ExpectedValue,
4985 range,
4986 message: "empty function argument",
4987 });
4988 }
4989
4990 fn validate_function_argument_head(
4991 &mut self,
4992 function_name: &str,
4993 argument_head: Option<Token<'text>>,
4994 range: TextRange,
4995 ) {
4996 let head_kind = argument_head.map(|token| token.kind);
4997 let valid = if function_name.eq_ignore_ascii_case("var") {
4998 matches!(head_kind, Some(SyntaxKind::CustomPropertyName))
4999 || head_kind.is_some_and(is_dynamic_function_argument_head)
5000 } else if function_name.eq_ignore_ascii_case("env") {
5001 matches!(
5002 head_kind,
5003 Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
5004 ) || head_kind.is_some_and(is_dynamic_function_argument_head)
5005 } else if function_name.eq_ignore_ascii_case("attr") {
5006 matches!(head_kind, Some(SyntaxKind::Ident))
5007 || head_kind.is_some_and(is_dynamic_function_argument_head)
5008 } else if function_name.eq_ignore_ascii_case("color-mix") {
5009 argument_head.is_some_and(|token| token.text.eq_ignore_ascii_case("in"))
5010 || head_kind.is_some_and(is_dynamic_function_argument_head)
5011 } else {
5012 true
5013 };
5014
5015 if valid {
5016 return;
5017 }
5018 self.errors.push(ParseError {
5019 code: ParseErrorCode::ExpectedValue,
5020 range,
5021 message: "invalid function argument head",
5022 });
5023 }
5024
5025 fn parse_bracketed_value(&mut self, recovery: &[SyntaxKind]) {
5026 let closed = self.current_bracketed_value_has_closing_bracket_before(recovery);
5027 self.builder.start_node(if closed {
5028 SyntaxKind::BracketedValue
5029 } else {
5030 SyntaxKind::BogusBracketedValue
5031 });
5032 self.token_current();
5033 let bracket_recovery = bracketed_value_recovery(recovery);
5034 self.parse_value_until(&bracket_recovery);
5035 if self.current_kind() == Some(SyntaxKind::RightBracket) {
5036 self.token_current();
5037 } else {
5038 self.error_at_current(
5039 ParseErrorCode::UnexpectedCharacter,
5040 "unterminated bracketed value",
5041 );
5042 }
5043 self.builder.finish_node();
5044 }
5045
5046 fn parse_parenthesized_expression(&mut self, recovery: &[SyntaxKind]) {
5047 self.builder.start_node(SyntaxKind::ParenthesizedExpression);
5048 self.token_current();
5049 let paren_recovery = function_argument_recovery(recovery);
5050 self.parse_value_until(&paren_recovery);
5051 if self.current_kind() == Some(SyntaxKind::RightParen) {
5052 self.token_current();
5053 }
5054 self.builder.finish_node();
5055 }
5056
5057 fn parse_at_rule(&mut self) {
5058 let spec = self.current_text().and_then(at_rule_spec);
5059 let at_rule_kind = if spec.is_none() && self.current_text() == Some("@") {
5060 SyntaxKind::BogusAtRule
5061 } else {
5062 SyntaxKind::AtRule
5063 };
5064 self.builder.start_node(at_rule_kind);
5065 if at_rule_kind == SyntaxKind::BogusAtRule {
5066 self.error_at_current(ParseErrorCode::UnexpectedCharacter, "expected at-rule name");
5067 }
5068 if let Some(spec) = spec {
5069 self.builder.start_node(spec.node_kind);
5070 }
5071
5072 if self.current_kind() == Some(SyntaxKind::AtKeyword) {
5073 self.token_current();
5074 }
5075 if let Some(spec) = spec {
5076 self.parse_at_rule_prelude(spec.node_kind);
5077 } else {
5078 self.consume_at_rule_prelude_tokens();
5079 }
5080
5081 while !self.at_end() {
5082 match self.current_kind() {
5083 Some(kind) if is_statement_end(kind) => {
5084 self.token_current();
5085 break;
5086 }
5087 Some(SyntaxKind::LeftBrace) => {
5088 match spec
5089 .map(|spec| spec.block_kind)
5090 .unwrap_or(AtRuleBlockKind::Raw)
5091 {
5092 AtRuleBlockKind::GroupRuleList => self.parse_group_at_rule_block(),
5093 AtRuleBlockKind::DeclarationList => self.parse_declaration_block(),
5094 AtRuleBlockKind::Keyframes => self.parse_keyframes_block(),
5095 AtRuleBlockKind::Raw => self.consume_balanced_block(),
5096 }
5097 break;
5098 }
5099 Some(SyntaxKind::SassIndent) => {
5100 self.parse_sass_indented_at_rule_block(
5101 spec.map(|spec| spec.block_kind)
5102 .unwrap_or(AtRuleBlockKind::Raw),
5103 );
5104 break;
5105 }
5106 Some(_) => self.token_current(),
5107 None => break,
5108 }
5109 }
5110
5111 if spec.is_some() {
5112 self.builder.finish_node();
5113 }
5114 self.builder.finish_node();
5115 }
5116
5117 fn parse_at_rule_prelude(&mut self, node_kind: SyntaxKind) {
5118 match node_kind {
5119 SyntaxKind::MediaRule => self.parse_media_query_list(),
5120 SyntaxKind::SupportsRule => self.parse_supports_rule_prelude(),
5121 SyntaxKind::ContainerRule => self.parse_container_rule_prelude(),
5122 SyntaxKind::ImportRule => self.parse_import_prelude(),
5123 SyntaxKind::CharsetRule => self.parse_charset_rule_prelude(),
5124 SyntaxKind::NamespaceRule => self.parse_namespace_rule_prelude(),
5125 SyntaxKind::KeyframesRule => self.parse_keyframes_rule_prelude(),
5126 SyntaxKind::PageRule => self.parse_page_rule_prelude(),
5127 SyntaxKind::FontFaceRule
5128 | SyntaxKind::StartingStyleRule
5129 | SyntaxKind::PageMarginRule
5130 | SyntaxKind::FontFeatureValuesStylisticRule
5131 | SyntaxKind::FontFeatureValuesStylesetRule
5132 | SyntaxKind::FontFeatureValuesCharacterVariantRule
5133 | SyntaxKind::FontFeatureValuesSwashRule
5134 | SyntaxKind::FontFeatureValuesOrnamentsRule
5135 | SyntaxKind::FontFeatureValuesAnnotationRule
5136 | SyntaxKind::FontFeatureValuesHistoricalFormsRule
5137 | SyntaxKind::ViewTransitionRule => {
5138 self.parse_empty_at_rule_prelude("unexpected at-rule prelude")
5139 }
5140 SyntaxKind::PropertyRule => self.parse_named_at_rule_prelude(
5141 at_rule_prelude_head_is_custom_property_name,
5142 "invalid @property name",
5143 ),
5144 SyntaxKind::FontPaletteValuesRule
5145 | SyntaxKind::ColorProfileRule
5146 | SyntaxKind::PositionTryRule => self.parse_named_at_rule_prelude(
5147 at_rule_prelude_head_is_custom_property_name,
5148 "invalid at-rule custom property name",
5149 ),
5150 SyntaxKind::CustomMediaRule => self.parse_custom_media_rule_prelude(),
5151 SyntaxKind::CounterStyleRule => self.parse_named_at_rule_prelude(
5152 at_rule_prelude_head_is_custom_ident,
5153 "invalid @counter-style name",
5154 ),
5155 SyntaxKind::FontFeatureValuesRule => self.parse_font_feature_values_prelude(),
5156 SyntaxKind::LayerRule => self.parse_layer_rule_prelude(),
5157 SyntaxKind::ScopeRule => self.parse_scope_rule_prelude(),
5158 _ => self.consume_at_rule_prelude_tokens(),
5159 }
5160 }
5161
5162 fn parse_media_query_list(&mut self) {
5163 self.builder.start_node(SyntaxKind::MediaQueryList);
5164 let mut saw_query = false;
5165 let mut expecting_query = true;
5166 while !self.at_end() {
5167 match self.current_kind() {
5168 Some(kind) if is_at_rule_prelude_boundary(kind) => break,
5169 Some(SyntaxKind::Comma) => {
5170 if expecting_query {
5171 self.error_at_current(
5172 ParseErrorCode::ExpectedValue,
5173 "invalid @media prelude",
5174 );
5175 self.builder.start_node(SyntaxKind::BogusMediaQuery);
5176 self.token_current();
5177 self.builder.finish_node();
5178 } else {
5179 self.token_current();
5180 expecting_query = true;
5181 }
5182 }
5183 Some(_) => {
5184 let valid = self.current_media_query_is_valid();
5185 if !valid {
5186 self.error_at_current(
5187 ParseErrorCode::ExpectedValue,
5188 "invalid @media prelude",
5189 );
5190 }
5191 self.parse_media_query(valid);
5192 saw_query = true;
5193 expecting_query = false;
5194 }
5195 None => break,
5196 }
5197 }
5198 if !saw_query || expecting_query {
5199 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @media prelude");
5200 self.builder.start_node(SyntaxKind::BogusMediaQuery);
5201 self.builder.finish_node();
5202 }
5203 self.builder.finish_node();
5204 }
5205
5206 fn parse_media_query(&mut self, valid: bool) {
5207 self.builder.start_node(if valid {
5208 SyntaxKind::MediaQuery
5209 } else {
5210 SyntaxKind::BogusMediaQuery
5211 });
5212 while !self.at_end() {
5213 match self.current_kind() {
5214 Some(kind) if is_at_rule_prelude_boundary(kind) || kind == SyntaxKind::Comma => {
5215 break;
5216 }
5217 Some(SyntaxKind::LeftParen) => self.parse_balanced_parenthesized_prelude_until(
5218 Some(SyntaxKind::MediaFeature),
5219 &[
5220 SyntaxKind::Comma,
5221 SyntaxKind::LeftBrace,
5222 SyntaxKind::Semicolon,
5223 ],
5224 ),
5225 Some(kind) if is_interpolation_start(kind) => self.parse_interpolation(
5226 kind,
5227 &[
5228 SyntaxKind::Comma,
5229 SyntaxKind::LeftBrace,
5230 SyntaxKind::Semicolon,
5231 ],
5232 ),
5233 Some(_) => self.token_current(),
5234 None => break,
5235 }
5236 }
5237 self.builder.finish_node();
5238 }
5239
5240 fn current_media_query_is_valid(&self) -> bool {
5241 let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5242 return false;
5243 };
5244 if is_at_rule_prelude_boundary(first_kind) || first_kind == SyntaxKind::Comma {
5245 return false;
5246 }
5247 if !self.current_prelude_parentheses_are_balanced_until(&[
5248 SyntaxKind::Comma,
5249 SyntaxKind::LeftBrace,
5250 SyntaxKind::SassIndent,
5251 SyntaxKind::Semicolon,
5252 SyntaxKind::SassOptionalSemicolon,
5253 ]) {
5254 return false;
5255 }
5256 self.media_query_starts_at(first_index, first_kind)
5257 }
5258
5259 fn media_query_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5260 match kind {
5261 SyntaxKind::Ident | SyntaxKind::LeftParen => true,
5262 SyntaxKind::KeywordNot | SyntaxKind::KeywordOnly => self
5263 .non_trivia_token_from(index + 1)
5264 .is_some_and(|(_, next_kind)| {
5265 matches!(next_kind, SyntaxKind::Ident | SyntaxKind::LeftParen)
5266 || is_interpolation_start(next_kind)
5267 }),
5268 kind if is_interpolation_start(kind) => true,
5269 _ => false,
5270 }
5271 }
5272
5273 fn parse_charset_rule_prelude(&mut self) {
5274 if !self.charset_rule_prelude_is_valid() {
5275 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @charset prelude");
5276 }
5277 self.consume_at_rule_prelude_tokens();
5278 }
5279
5280 fn charset_rule_prelude_is_valid(&self) -> bool {
5281 let Some((source_index, SyntaxKind::String)) = self.non_trivia_token_from(self.position)
5282 else {
5283 return false;
5284 };
5285 self.non_trivia_token_from(source_index + 1)
5286 .is_none_or(|(_, kind)| is_at_rule_prelude_boundary(kind))
5287 }
5288
5289 fn parse_namespace_rule_prelude(&mut self) {
5290 if !self.namespace_rule_prelude_is_valid() {
5291 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @namespace prelude");
5292 }
5293 self.consume_at_rule_prelude_tokens();
5294 }
5295
5296 fn parse_custom_media_rule_prelude(&mut self) {
5297 self.eat_trivia();
5298 let valid = self.custom_media_rule_prelude_is_valid();
5299 if !valid {
5300 self.error_at_current(
5301 ParseErrorCode::ExpectedValue,
5302 "invalid @custom-media prelude",
5303 );
5304 }
5305 self.builder.start_node(if valid {
5306 SyntaxKind::AtRulePrelude
5307 } else {
5308 SyntaxKind::BogusAtRulePrelude
5309 });
5310 self.consume_at_rule_prelude_tokens_without_wrapping();
5311 self.builder.finish_node();
5312 }
5313
5314 fn custom_media_rule_prelude_is_valid(&self) -> bool {
5315 let Some((name_index, name_kind)) = self.non_trivia_token_from(self.position) else {
5316 return false;
5317 };
5318 if !self.current_prelude_parentheses_are_balanced_until(&[
5319 SyntaxKind::Semicolon,
5320 SyntaxKind::SassOptionalSemicolon,
5321 ]) {
5322 return false;
5323 }
5324 let tail = if name_kind == SyntaxKind::CustomPropertyName {
5325 self.non_trivia_token_from(name_index + 1)
5326 } else if is_interpolation_start(name_kind) {
5327 self.non_trivia_token_after_interpolation(name_index, name_kind)
5328 } else {
5329 return false;
5330 };
5331 let Some((tail_index, tail_kind)) = tail else {
5332 return false;
5333 };
5334 if is_at_rule_prelude_boundary(tail_kind) {
5335 return false;
5336 }
5337 self.media_query_starts_at(tail_index, tail_kind)
5338 }
5339
5340 fn namespace_rule_prelude_is_valid(&self) -> bool {
5341 let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5342 return false;
5343 };
5344
5345 if self.namespace_source_starts_at(first_index, first_kind) {
5346 return true;
5347 }
5348 if !matches!(
5349 first_kind,
5350 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
5351 ) {
5352 return false;
5353 }
5354 self.non_trivia_token_from(first_index + 1)
5355 .is_some_and(|(source_index, source_kind)| {
5356 self.namespace_source_starts_at(source_index, source_kind)
5357 })
5358 }
5359
5360 fn namespace_source_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5361 matches!(kind, SyntaxKind::String | SyntaxKind::Url)
5362 || is_interpolation_start(kind)
5363 || self.token_starts_url_function(index, kind)
5364 }
5365
5366 fn token_starts_url_function(&self, index: usize, kind: SyntaxKind) -> bool {
5367 kind == SyntaxKind::Ident
5368 && self
5369 .tokens
5370 .get(index)
5371 .is_some_and(|token| token.text.eq_ignore_ascii_case("url"))
5372 && self
5373 .non_trivia_token_from(index + 1)
5374 .is_some_and(|(_, next_kind)| next_kind == SyntaxKind::LeftParen)
5375 }
5376
5377 fn parse_keyframes_rule_prelude(&mut self) {
5378 if !self.keyframes_rule_prelude_is_valid() {
5379 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @keyframes name");
5380 }
5381 self.consume_at_rule_prelude_tokens();
5382 }
5383
5384 fn keyframes_rule_prelude_is_valid(&self) -> bool {
5385 let Some((name_index, name_kind)) = self.non_trivia_token_from(self.position) else {
5386 return false;
5387 };
5388 if is_interpolation_start(name_kind) {
5389 return true;
5390 }
5391 if !matches!(name_kind, SyntaxKind::Ident | SyntaxKind::String) {
5392 return false;
5393 }
5394 self.non_trivia_token_from(name_index + 1)
5395 .is_none_or(|(_, kind)| is_at_rule_prelude_boundary(kind))
5396 }
5397
5398 fn parse_empty_at_rule_prelude(&mut self, message: &'static str) {
5399 self.eat_trivia();
5400 if self
5401 .current_kind()
5402 .is_some_and(|kind| !is_at_rule_prelude_boundary(kind))
5403 {
5404 self.error_at_current(ParseErrorCode::ExpectedValue, message);
5405 self.consume_at_rule_prelude_tokens();
5406 }
5407 }
5408
5409 fn parse_font_feature_values_prelude(&mut self) {
5410 if !self.font_feature_values_prelude_is_valid() {
5411 self.error_at_current(
5412 ParseErrorCode::ExpectedValue,
5413 "invalid @font-feature-values family name",
5414 );
5415 }
5416 self.consume_at_rule_prelude_tokens();
5417 }
5418
5419 fn font_feature_values_prelude_is_valid(&self) -> bool {
5420 self.non_trivia_token_from(self.position)
5421 .is_some_and(|(_, kind)| {
5422 matches!(kind, SyntaxKind::Ident | SyntaxKind::String)
5423 || is_interpolation_start(kind)
5424 })
5425 }
5426
5427 fn parse_layer_rule_prelude(&mut self) {
5428 self.eat_trivia();
5429 match self.current_kind() {
5430 Some(SyntaxKind::LeftBrace | SyntaxKind::SassIndent) => return,
5431 Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) | None => {
5432 self.empty_bogus_node(
5433 SyntaxKind::BogusLayerName,
5434 ParseErrorCode::ExpectedValue,
5435 "invalid @layer prelude",
5436 );
5437 return;
5438 }
5439 Some(_) => {}
5440 }
5441
5442 let valid = self.layer_rule_prelude_is_valid();
5443 if !valid {
5444 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @layer prelude");
5445 }
5446 self.builder.start_node(if valid {
5447 SyntaxKind::LayerName
5448 } else {
5449 SyntaxKind::BogusLayerName
5450 });
5451 self.consume_at_rule_prelude_tokens_without_wrapping();
5452 self.builder.finish_node();
5453 }
5454
5455 fn layer_rule_prelude_is_valid(&self) -> bool {
5456 let mut saw_name = false;
5457 let mut expecting_segment = true;
5458 let mut index = self.position;
5459
5460 while let Some(token) = self.tokens.get(index) {
5461 if token.kind.is_trivia() {
5462 index += 1;
5463 continue;
5464 }
5465 if is_at_rule_prelude_boundary(token.kind) {
5466 return saw_name && !expecting_segment;
5467 }
5468 if is_interpolation_start(token.kind) {
5469 return true;
5470 }
5471 match token.kind {
5472 SyntaxKind::Ident if expecting_segment => {
5473 saw_name = true;
5474 expecting_segment = false;
5475 }
5476 SyntaxKind::Comma if saw_name && !expecting_segment => {
5477 expecting_segment = true;
5478 }
5479 SyntaxKind::Dot if saw_name && !expecting_segment => {
5480 expecting_segment = true;
5481 }
5482 _ => return false,
5483 }
5484 index += 1;
5485 }
5486
5487 saw_name && !expecting_segment
5488 }
5489
5490 fn parse_container_rule_prelude(&mut self) {
5491 self.eat_trivia();
5492 let valid = self.container_rule_prelude_is_valid();
5493 if !valid {
5494 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @container prelude");
5495 }
5496 self.builder.start_node(if valid {
5497 SyntaxKind::ContainerCondition
5498 } else {
5499 SyntaxKind::BogusContainerCondition
5500 });
5501 self.consume_at_rule_prelude_tokens_without_wrapping();
5502 self.builder.finish_node();
5503 }
5504
5505 fn container_rule_prelude_is_valid(&self) -> bool {
5506 let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5507 return false;
5508 };
5509 if is_at_rule_prelude_boundary(first_kind) {
5510 return false;
5511 }
5512 if !self.current_prelude_parentheses_are_balanced_until(&[
5513 SyntaxKind::LeftBrace,
5514 SyntaxKind::SassIndent,
5515 SyntaxKind::Semicolon,
5516 SyntaxKind::SassOptionalSemicolon,
5517 ]) {
5518 return false;
5519 }
5520 if self.container_condition_starts_at(first_index, first_kind) {
5521 return true;
5522 }
5523 if first_kind != SyntaxKind::Ident {
5524 return false;
5525 }
5526 self.non_trivia_token_from(first_index + 1).is_some_and(
5527 |(condition_index, condition_kind)| {
5528 self.container_condition_starts_at(condition_index, condition_kind)
5529 },
5530 )
5531 }
5532
5533 fn container_condition_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5534 if matches!(kind, SyntaxKind::LeftParen | SyntaxKind::KeywordNot)
5535 || is_interpolation_start(kind)
5536 {
5537 return true;
5538 }
5539 kind == SyntaxKind::Ident
5540 && self
5541 .non_trivia_token_from(index + 1)
5542 .is_some_and(|(_, next_kind)| next_kind == SyntaxKind::LeftParen)
5543 }
5544
5545 fn parse_supports_rule_prelude(&mut self) {
5546 self.eat_trivia();
5547 let valid = self.supports_rule_prelude_is_valid();
5548 if !valid {
5549 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @supports prelude");
5550 }
5551 self.builder.start_node(if valid {
5552 SyntaxKind::SupportsCondition
5553 } else {
5554 SyntaxKind::BogusSupportsCondition
5555 });
5556 self.consume_at_rule_prelude_tokens_without_wrapping();
5557 self.builder.finish_node();
5558 }
5559
5560 fn supports_rule_prelude_is_valid(&self) -> bool {
5561 let Some((first_index, first_kind)) = self.non_trivia_token_from(self.position) else {
5562 return false;
5563 };
5564 if is_at_rule_prelude_boundary(first_kind) {
5565 return false;
5566 }
5567 if !self.current_prelude_parentheses_are_balanced_until(&[
5568 SyntaxKind::LeftBrace,
5569 SyntaxKind::SassIndent,
5570 SyntaxKind::Semicolon,
5571 SyntaxKind::SassOptionalSemicolon,
5572 ]) {
5573 return false;
5574 }
5575 self.supports_condition_starts_at(first_index, first_kind)
5576 }
5577
5578 fn supports_condition_starts_at(&self, index: usize, kind: SyntaxKind) -> bool {
5579 if kind == SyntaxKind::KeywordNot {
5580 return self
5581 .non_trivia_token_from(index + 1)
5582 .is_some_and(|(next_index, next_kind)| {
5583 self.supports_condition_starts_at(next_index, next_kind)
5584 });
5585 }
5586 if kind == SyntaxKind::LeftParen || is_interpolation_start(kind) {
5587 return true;
5588 }
5589 kind == SyntaxKind::Ident
5590 && self
5591 .non_trivia_token_from(index + 1)
5592 .is_some_and(|(_, next_kind)| next_kind == SyntaxKind::LeftParen)
5593 }
5594
5595 fn parse_scope_rule_prelude(&mut self) {
5596 self.eat_trivia();
5597 let valid = self.scope_rule_prelude_is_valid();
5598 if !valid {
5599 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @scope prelude");
5600 }
5601 self.builder.start_node(if valid {
5602 SyntaxKind::ScopeRange
5603 } else {
5604 SyntaxKind::BogusScopeRange
5605 });
5606 self.consume_at_rule_prelude_tokens_without_wrapping();
5607 self.builder.finish_node();
5608 }
5609
5610 fn scope_rule_prelude_is_valid(&self) -> bool {
5611 let Some((start_index, start_kind)) = self.non_trivia_token_from(self.position) else {
5612 return false;
5613 };
5614 if is_at_rule_prelude_boundary(start_kind) {
5615 return false;
5616 }
5617 if !self.current_prelude_parentheses_are_balanced_until(&[
5618 SyntaxKind::LeftBrace,
5619 SyntaxKind::SassIndent,
5620 SyntaxKind::Semicolon,
5621 SyntaxKind::SassOptionalSemicolon,
5622 ]) {
5623 return false;
5624 }
5625 if is_interpolation_start(start_kind) {
5626 return true;
5627 }
5628 if start_kind != SyntaxKind::LeftParen {
5629 return false;
5630 }
5631
5632 let Some(start_close_index) = self.parenthesized_prelude_close_index(start_index) else {
5633 return false;
5634 };
5635 let Some((after_start_index, after_start_kind)) =
5636 self.non_trivia_token_from(start_close_index + 1)
5637 else {
5638 return true;
5639 };
5640 if is_at_rule_prelude_boundary(after_start_kind) {
5641 return true;
5642 }
5643 if after_start_kind != SyntaxKind::Ident
5644 || !self
5645 .tokens
5646 .get(after_start_index)
5647 .is_some_and(|token| token.text.eq_ignore_ascii_case("to"))
5648 {
5649 return false;
5650 }
5651
5652 let Some((end_index, end_kind)) = self.non_trivia_token_from(after_start_index + 1) else {
5653 return false;
5654 };
5655 if is_interpolation_start(end_kind) {
5656 return true;
5657 }
5658 if end_kind != SyntaxKind::LeftParen {
5659 return false;
5660 }
5661 let Some(end_close_index) = self.parenthesized_prelude_close_index(end_index) else {
5662 return false;
5663 };
5664 self.non_trivia_token_from(end_close_index + 1)
5665 .is_none_or(|(_, kind)| is_at_rule_prelude_boundary(kind))
5666 }
5667
5668 fn parenthesized_prelude_close_index(&self, open_index: usize) -> Option<usize> {
5669 let mut depth = 0usize;
5670 for (index, token) in self.tokens.iter().enumerate().skip(open_index) {
5671 match token.kind {
5672 SyntaxKind::LeftParen => depth += 1,
5673 SyntaxKind::RightParen => {
5674 depth = depth.saturating_sub(1);
5675 if depth == 0 {
5676 return Some(index);
5677 }
5678 }
5679 kind if depth == 0 && is_at_rule_prelude_boundary(kind) => return None,
5680 _ => {}
5681 }
5682 }
5683 None
5684 }
5685
5686 fn parse_page_rule_prelude(&mut self) {
5687 self.eat_trivia();
5688 if self.current_kind().is_none_or(is_at_rule_prelude_boundary) {
5689 return;
5690 }
5691 let valid = self.page_rule_prelude_is_valid();
5692 if !valid {
5693 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @page prelude");
5694 }
5695 self.builder.start_node(if valid {
5696 SyntaxKind::AtRulePrelude
5697 } else {
5698 SyntaxKind::BogusAtRulePrelude
5699 });
5700 self.consume_at_rule_prelude_tokens_without_wrapping();
5701 self.builder.finish_node();
5702 }
5703
5704 fn page_rule_prelude_is_valid(&self) -> bool {
5705 let mut expecting_selector = true;
5706 let mut expecting_pseudo_name = false;
5707 let mut saw_selector = false;
5708
5709 for token in self.tokens.iter().skip(self.position) {
5710 if token.kind.is_trivia() {
5711 continue;
5712 }
5713 if is_at_rule_prelude_boundary(token.kind) {
5714 return saw_selector && !expecting_selector && !expecting_pseudo_name;
5715 }
5716 if is_interpolation_start(token.kind) {
5717 return true;
5718 }
5719 if expecting_pseudo_name {
5720 if token.kind != SyntaxKind::Ident {
5721 return false;
5722 }
5723 saw_selector = true;
5724 expecting_selector = false;
5725 expecting_pseudo_name = false;
5726 continue;
5727 }
5728 match token.kind {
5729 SyntaxKind::Ident if expecting_selector => {
5730 saw_selector = true;
5731 expecting_selector = false;
5732 }
5733 SyntaxKind::Colon => {
5734 expecting_pseudo_name = true;
5735 }
5736 SyntaxKind::Comma if saw_selector && !expecting_selector => {
5737 expecting_selector = true;
5738 }
5739 _ => return false,
5740 }
5741 }
5742
5743 saw_selector && !expecting_selector && !expecting_pseudo_name
5744 }
5745
5746 fn parse_import_prelude(&mut self) {
5747 self.eat_trivia();
5748 if self.dialect == StyleDialect::Less && self.current_kind() == Some(SyntaxKind::LeftParen)
5749 {
5750 self.builder.start_node(SyntaxKind::AtRulePrelude);
5751 self.parse_balanced_parenthesized_prelude(None);
5752 self.builder.finish_node();
5753 self.eat_trivia();
5754 }
5755 if !self.parse_import_source() {
5756 self.parse_bogus_import_prelude();
5757 return;
5758 }
5759 while !self.at_end() {
5760 match self.current_kind() {
5761 Some(kind) if is_at_rule_prelude_boundary(kind) => break,
5762 Some(kind) if kind.is_trivia() => self.token_current(),
5763 Some(SyntaxKind::Ident) if self.current_text() == Some("layer") => {
5764 self.parse_import_layer_tail_node()
5765 }
5766 Some(SyntaxKind::Ident) if self.current_text() == Some("supports") => {
5767 self.parse_import_supports_tail_node()
5768 }
5769 Some(_) => {
5770 self.parse_media_query_list();
5771 break;
5772 }
5773 None => break,
5774 }
5775 }
5776 }
5777
5778 fn parse_import_source(&mut self) -> bool {
5779 match self.current_kind() {
5780 Some(SyntaxKind::Url) => {
5781 self.builder.start_node(SyntaxKind::UrlValue);
5782 self.token_current();
5783 self.builder.finish_node();
5784 true
5785 }
5786 Some(SyntaxKind::Ident)
5787 if self
5788 .current_text()
5789 .is_some_and(|text| text.eq_ignore_ascii_case("url"))
5790 && self.next_kind() == Some(SyntaxKind::LeftParen) =>
5791 {
5792 self.builder.start_node(SyntaxKind::UrlValue);
5793 self.parse_function_call(&[SyntaxKind::LeftBrace, SyntaxKind::Semicolon]);
5794 self.builder.finish_node();
5795 true
5796 }
5797 Some(SyntaxKind::String) => {
5798 self.token_current();
5799 true
5800 }
5801 Some(kind) if is_interpolation_start(kind) => {
5802 self.parse_interpolation(kind, &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon]);
5803 true
5804 }
5805 Some(_) | None => false,
5806 }
5807 }
5808
5809 fn parse_bogus_import_prelude(&mut self) {
5810 self.builder.start_node(SyntaxKind::BogusAtRulePrelude);
5811 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @import source");
5812 self.consume_at_rule_prelude_tokens_without_wrapping();
5813 self.builder.finish_node();
5814 }
5815
5816 fn parse_named_at_rule_prelude(
5817 &mut self,
5818 valid_head: fn(SyntaxKind) -> bool,
5819 message: &'static str,
5820 ) {
5821 if self.current_kind().is_none_or(is_at_rule_prelude_boundary) {
5822 return;
5823 }
5824 let valid_name = self
5825 .non_trivia_token_from(self.position)
5826 .is_some_and(|(_, kind)| valid_head(kind));
5827 if !valid_name {
5828 self.error_at_current(ParseErrorCode::ExpectedValue, message);
5829 }
5830 self.consume_at_rule_prelude_tokens();
5831 }
5832
5833 fn parse_import_layer_tail_node(&mut self) {
5834 let valid = self.import_layer_tail_is_valid();
5835 if !valid {
5836 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid @import layer tail");
5837 }
5838 self.builder.start_node(if valid {
5839 SyntaxKind::LayerName
5840 } else {
5841 SyntaxKind::BogusLayerName
5842 });
5843 self.token_current();
5844 if self.current_kind() == Some(SyntaxKind::LeftParen) {
5845 self.parse_balanced_parenthesized_prelude(None);
5846 }
5847 self.builder.finish_node();
5848 }
5849
5850 fn import_layer_tail_is_valid(&self) -> bool {
5851 let Some((open_index, next_kind)) = self.non_trivia_token_from(self.position + 1) else {
5852 return true;
5853 };
5854 if next_kind != SyntaxKind::LeftParen {
5855 return true;
5856 }
5857 let Some(close_index) = self.parenthesized_prelude_close_index(open_index) else {
5858 return false;
5859 };
5860 self.layer_name_is_valid_between(open_index + 1, close_index)
5861 }
5862
5863 fn layer_name_is_valid_between(&self, start: usize, end: usize) -> bool {
5864 let mut saw_name = false;
5865 let mut expecting_segment = true;
5866
5867 for token in self.tokens[start..end]
5868 .iter()
5869 .filter(|token| !token.kind.is_trivia())
5870 {
5871 if is_interpolation_start(token.kind) {
5872 return true;
5873 }
5874 match token.kind {
5875 SyntaxKind::Ident if expecting_segment => {
5876 saw_name = true;
5877 expecting_segment = false;
5878 }
5879 SyntaxKind::Dot if saw_name && !expecting_segment => {
5880 expecting_segment = true;
5881 }
5882 _ => return false,
5883 }
5884 }
5885
5886 saw_name && !expecting_segment
5887 }
5888
5889 fn parse_import_supports_tail_node(&mut self) {
5890 let valid = self.import_supports_tail_is_valid();
5891 if !valid {
5892 self.error_at_current(
5893 ParseErrorCode::ExpectedValue,
5894 "invalid @import supports tail",
5895 );
5896 }
5897 self.builder.start_node(if valid {
5898 SyntaxKind::SupportsCondition
5899 } else {
5900 SyntaxKind::BogusSupportsCondition
5901 });
5902 self.token_current();
5903 if self.current_kind() == Some(SyntaxKind::LeftParen) {
5904 self.parse_balanced_parenthesized_prelude(None);
5905 }
5906 self.builder.finish_node();
5907 }
5908
5909 fn import_supports_tail_is_valid(&self) -> bool {
5910 let Some((open_index, SyntaxKind::LeftParen)) =
5911 self.non_trivia_token_from(self.position + 1)
5912 else {
5913 return false;
5914 };
5915 let Some(close_index) = self.parenthesized_prelude_close_index(open_index) else {
5916 return false;
5917 };
5918 self.non_trivia_token_from(open_index + 1)
5919 .is_some_and(|(inner_index, inner_kind)| {
5920 inner_index < close_index && inner_kind != SyntaxKind::RightParen
5921 })
5922 }
5923
5924 fn consume_at_rule_prelude_tokens(&mut self) {
5925 if self.current_kind().is_none_or(is_at_rule_prelude_boundary) {
5926 return;
5927 }
5928 self.builder
5929 .start_node(self.current_generic_at_rule_prelude_node_kind());
5930 self.consume_at_rule_prelude_tokens_without_wrapping();
5931 self.builder.finish_node();
5932 }
5933
5934 fn consume_at_rule_prelude_tokens_without_wrapping(&mut self) {
5935 while !self.at_end() {
5936 match self.current_kind() {
5937 Some(kind) if is_at_rule_prelude_boundary(kind) => break,
5938 Some(SyntaxKind::LeftParen) => self.parse_balanced_parenthesized_prelude(None),
5939 Some(kind) if is_interpolation_start(kind) => {
5940 self.parse_interpolation(kind, &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon])
5941 }
5942 Some(_) => self.token_current(),
5943 None => break,
5944 }
5945 }
5946 }
5947
5948 fn parse_balanced_parenthesized_prelude(&mut self, node_kind: Option<SyntaxKind>) {
5949 self.parse_balanced_parenthesized_prelude_until(
5950 node_kind,
5951 &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon],
5952 );
5953 }
5954
5955 fn parse_balanced_parenthesized_prelude_until(
5956 &mut self,
5957 node_kind: Option<SyntaxKind>,
5958 recovery: &[SyntaxKind],
5959 ) {
5960 if let Some(kind) = node_kind {
5961 self.builder.start_node(kind);
5962 }
5963 let mut depth = 0usize;
5964 let mut closed = false;
5965 while !self.at_end() {
5966 match self.current_kind() {
5967 Some(SyntaxKind::LeftParen) => {
5968 depth += 1;
5969 self.token_current();
5970 }
5971 Some(SyntaxKind::RightParen) => {
5972 self.token_current();
5973 depth = depth.saturating_sub(1);
5974 if depth == 0 {
5975 closed = true;
5976 break;
5977 }
5978 }
5979 Some(kind) if recovery.contains(&kind) => break,
5980 Some(kind) if is_interpolation_start(kind) => {
5981 self.parse_interpolation(kind, &[SyntaxKind::LeftBrace, SyntaxKind::Semicolon])
5982 }
5983 Some(_) => self.token_current(),
5984 None => break,
5985 }
5986 }
5987 if node_kind.is_some() {
5988 self.builder.finish_node();
5989 }
5990 if !closed {
5991 self.error_at_current(
5992 ParseErrorCode::UnexpectedCharacter,
5993 "unterminated parenthesized prelude",
5994 );
5995 }
5996 }
5997
5998 fn parse_interpolation(&mut self, start_kind: SyntaxKind, recovery: &[SyntaxKind]) {
5999 let Some(end_kind) = interpolation_end_kind(start_kind) else {
6000 self.token_current();
6001 return;
6002 };
6003 let closed = self.find_before_recovery(end_kind, recovery);
6004 self.builder.start_node(if closed {
6005 SyntaxKind::Interpolation
6006 } else {
6007 SyntaxKind::BogusInterpolation
6008 });
6009 if self.current_kind() == Some(start_kind) {
6010 self.token_current();
6011 }
6012 while !self.at_end() {
6013 match self.current_kind() {
6014 Some(kind) if kind == end_kind => {
6015 self.token_current();
6016 break;
6017 }
6018 Some(kind) if !closed && recovery.contains(&kind) => break,
6019 Some(_) => self.token_current(),
6020 None => break,
6021 }
6022 }
6023 if !closed {
6024 self.error_at_current(
6025 ParseErrorCode::UnexpectedCharacter,
6026 "unterminated interpolation",
6027 );
6028 }
6029 self.builder.finish_node();
6030 }
6031
6032 fn parse_group_at_rule_block(&mut self) {
6033 self.token_current();
6034 self.builder.start_node(SyntaxKind::RuleList);
6035 self.parse_rule_list_items();
6036 self.builder.finish_node();
6037 if self.current_kind() == Some(SyntaxKind::RightBrace) {
6038 self.token_current();
6039 }
6040 }
6041
6042 fn parse_rule_list_items(&mut self) {
6043 while !self.at_end() {
6044 self.eat_trivia();
6045 match self.current_kind() {
6046 Some(SyntaxKind::RightBrace | SyntaxKind::SassDedent) | None => break,
6047 Some(SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon) => {
6048 self.token_current()
6049 }
6050 Some(SyntaxKind::AtKeyword) if self.current_is_css_module_value_rule() => {
6051 self.parse_css_module_value_rule()
6052 }
6053 Some(SyntaxKind::AtKeyword) if self.current_dialect_at_rule_spec().is_some() => {
6054 self.parse_dialect_at_rule()
6055 }
6056 Some(SyntaxKind::AtKeyword) => self.parse_at_rule(),
6057 Some(_) => self.parse_rule(),
6058 }
6059 }
6060 }
6061
6062 fn parse_declaration_block(&mut self) {
6063 self.token_current();
6064 self.builder
6065 .start_node(if self.previous_left_brace_has_match() {
6066 SyntaxKind::DeclarationList
6067 } else {
6068 SyntaxKind::BogusDeclarationList
6069 });
6070 self.parse_declaration_list();
6071 self.builder.finish_node();
6072 if self.current_kind() == Some(SyntaxKind::RightBrace) {
6073 self.token_current();
6074 } else {
6075 self.missing_token_bogus_trivia(
6076 ParseErrorCode::UnexpectedCharacter,
6077 "unterminated declaration block",
6078 );
6079 }
6080 }
6081
6082 fn parse_sass_indented_at_rule_block(&mut self, block_kind: AtRuleBlockKind) {
6083 self.builder.start_node(SyntaxKind::SassIndentedBlock);
6084 if self.current_kind() == Some(SyntaxKind::SassIndent) {
6085 self.token_current();
6086 }
6087 match block_kind {
6088 AtRuleBlockKind::GroupRuleList => {
6089 self.builder.start_node(SyntaxKind::RuleList);
6090 self.parse_rule_list_items();
6091 self.builder.finish_node();
6092 }
6093 AtRuleBlockKind::DeclarationList | AtRuleBlockKind::Keyframes => {
6094 self.builder.start_node(SyntaxKind::DeclarationList);
6095 self.parse_declaration_list();
6096 self.builder.finish_node();
6097 }
6098 AtRuleBlockKind::Raw => self.consume_sass_indented_raw_body(),
6099 }
6100 if self.current_kind() == Some(SyntaxKind::SassDedent) {
6101 self.token_current();
6102 } else {
6103 self.error_at_current(
6104 ParseErrorCode::UnexpectedCharacter,
6105 "unterminated Sass indented at-rule block",
6106 );
6107 }
6108 self.builder.finish_node();
6109 }
6110
6111 fn consume_sass_indented_raw_body(&mut self) {
6112 let mut depth = 0usize;
6113 while !self.at_end() {
6114 match self.current_kind() {
6115 Some(SyntaxKind::SassIndent) => {
6116 depth += 1;
6117 self.token_current();
6118 }
6119 Some(SyntaxKind::SassDedent) if depth == 0 => break,
6120 Some(SyntaxKind::SassDedent) => {
6121 depth = depth.saturating_sub(1);
6122 self.token_current();
6123 }
6124 Some(_) => self.token_current(),
6125 None => break,
6126 }
6127 }
6128 }
6129
6130 fn parse_keyframes_block(&mut self) {
6131 self.token_current();
6132 while !self.at_end() {
6133 self.eat_trivia();
6134 match self.current_kind() {
6135 Some(SyntaxKind::RightBrace) | None => break,
6136 Some(_) => self.parse_keyframe_block(),
6137 }
6138 }
6139 if self.current_kind() == Some(SyntaxKind::RightBrace) {
6140 self.token_current();
6141 }
6142 }
6143
6144 fn parse_keyframe_block(&mut self) {
6145 let has_block = self.find_before_recovery(SyntaxKind::LeftBrace, &[SyntaxKind::RightBrace]);
6146 self.builder.start_node(if has_block {
6147 SyntaxKind::KeyframeBlock
6148 } else {
6149 SyntaxKind::BogusKeyframeBlock
6150 });
6151 if has_block && !self.keyframe_selector_list_is_valid() {
6152 self.error_at_current(ParseErrorCode::ExpectedValue, "invalid keyframe selector");
6153 }
6154 while !self.at_end() {
6155 match self.current_kind() {
6156 Some(SyntaxKind::LeftBrace) => {
6157 self.parse_declaration_block();
6158 break;
6159 }
6160 Some(SyntaxKind::RightBrace) | None => break,
6161 Some(_) => self.token_current(),
6162 }
6163 }
6164 if !has_block {
6165 self.error_at_current(
6166 ParseErrorCode::UnexpectedCharacter,
6167 "expected keyframe declaration block",
6168 );
6169 }
6170 self.builder.finish_node();
6171 }
6172
6173 fn keyframe_selector_list_is_valid(&self) -> bool {
6174 let mut index = self.position;
6175 let mut saw_selector = false;
6176 let mut expect_selector = true;
6177 loop {
6178 let Some((token_index, kind)) = self.non_trivia_token_from(index) else {
6179 return false;
6180 };
6181 if kind == SyntaxKind::LeftBrace {
6182 return saw_selector && !expect_selector;
6183 }
6184 if expect_selector {
6185 if is_interpolation_start(kind) {
6186 return true;
6187 }
6188 if !keyframe_selector_token_is_valid(self.tokens[token_index]) {
6189 return false;
6190 }
6191 saw_selector = true;
6192 expect_selector = false;
6193 index = token_index + 1;
6194 continue;
6195 }
6196 if kind != SyntaxKind::Comma {
6197 return false;
6198 }
6199 expect_selector = true;
6200 index = token_index + 1;
6201 }
6202 }
6203
6204 fn consume_balanced_block(&mut self) {
6205 let mut depth = 0usize;
6206 while !self.at_end() {
6207 match self.current_kind() {
6208 Some(SyntaxKind::LeftBrace) => {
6209 depth += 1;
6210 self.token_current();
6211 }
6212 Some(SyntaxKind::RightBrace) => {
6213 self.token_current();
6214 depth = depth.saturating_sub(1);
6215 if depth == 0 {
6216 break;
6217 }
6218 }
6219 Some(_) => self.token_current(),
6220 None => break,
6221 }
6222 }
6223 }
6224
6225 fn eat_trivia(&mut self) {
6226 while matches!(self.current_kind(), Some(kind) if kind.is_trivia()) {
6227 self.token_current();
6228 }
6229 }
6230
6231 fn consume_until_recovery(&mut self, recovery: &[SyntaxKind]) {
6232 let should_wrap = self
6233 .current_kind()
6234 .is_some_and(|kind| !recovery.contains(&kind));
6235 if should_wrap {
6236 self.builder.start_node(SyntaxKind::BogusRecovery);
6237 }
6238 while !self.at_end() {
6239 match self.current_kind() {
6240 Some(kind) if recovery.contains(&kind) => break,
6241 Some(_) => self.token_current(),
6242 None => break,
6243 }
6244 }
6245 if should_wrap {
6246 self.builder.finish_node();
6247 }
6248 }
6249
6250 fn find_before_recovery(&self, target: SyntaxKind, recovery: &[SyntaxKind]) -> bool {
6251 let mut index = self.position;
6252 while let Some(token) = self.tokens.get(index) {
6253 if token.kind == target {
6254 return true;
6255 }
6256 if recovery.contains(&token.kind) {
6257 return false;
6258 }
6259 index += 1;
6260 }
6261 false
6262 }
6263
6264 fn find_rule_block_open_before_recovery(&self, recovery: &[SyntaxKind]) -> bool {
6265 let mut index = self.position;
6266 while let Some(token) = self.tokens.get(index) {
6267 if token.kind == SyntaxKind::LeftBrace
6268 || (self.dialect == StyleDialect::Sass && token.kind == SyntaxKind::SassIndent)
6269 {
6270 return true;
6271 }
6272 if recovery.contains(&token.kind) {
6273 return false;
6274 }
6275 index += 1;
6276 }
6277 false
6278 }
6279
6280 fn find_text_before_recovery(&self, target: &str, recovery: &[SyntaxKind]) -> bool {
6281 let mut index = self.position;
6282 while let Some(token) = self.tokens.get(index) {
6283 if token.text == target {
6284 return true;
6285 }
6286 if recovery.contains(&token.kind) {
6287 return false;
6288 }
6289 index += 1;
6290 }
6291 false
6292 }
6293
6294 fn current_function_has_closing_paren_before(&self, recovery: &[SyntaxKind]) -> bool {
6295 let Some(open_index) = self.position.checked_add(1) else {
6296 return false;
6297 };
6298 if self
6299 .tokens
6300 .get(open_index)
6301 .is_none_or(|token| token.kind != SyntaxKind::LeftParen)
6302 {
6303 return false;
6304 }
6305
6306 let mut depth = 0usize;
6307 for token in self.tokens.iter().skip(open_index) {
6308 match token.kind {
6309 SyntaxKind::LeftParen => depth += 1,
6310 SyntaxKind::RightParen => {
6311 depth = depth.saturating_sub(1);
6312 if depth == 0 {
6313 return true;
6314 }
6315 }
6316 kind if depth == 1 && recovery.contains(&kind) => return false,
6317 _ => {}
6318 }
6319 }
6320 false
6321 }
6322
6323 fn current_split_important_annotation(&self) -> bool {
6324 self.current_text() == Some("!")
6325 && self
6326 .non_trivia_token_from(self.position + 1)
6327 .is_some_and(|(index, kind)| {
6328 matches!(kind, SyntaxKind::Ident | SyntaxKind::KeywordImportant)
6329 && self
6330 .tokens
6331 .get(index)
6332 .is_some_and(|token| token.text.eq_ignore_ascii_case("important"))
6333 })
6334 }
6335
6336 fn current_scss_variable_flag_annotation(&self) -> bool {
6337 matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass)
6338 && self.current_text() == Some("!")
6339 && self
6340 .non_trivia_token_from(self.position + 1)
6341 .is_some_and(|(index, kind)| {
6342 kind == SyntaxKind::Ident
6343 && self.tokens.get(index).is_some_and(|token| {
6344 token.text.eq_ignore_ascii_case("default")
6345 || token.text.eq_ignore_ascii_case("global")
6346 })
6347 })
6348 }
6349
6350 fn current_bracketed_value_has_closing_bracket_before(&self, recovery: &[SyntaxKind]) -> bool {
6351 let mut depth = 0usize;
6352 for token in self.tokens.iter().skip(self.position) {
6353 match token.kind {
6354 SyntaxKind::LeftBracket => depth += 1,
6355 SyntaxKind::RightBracket => {
6356 depth = depth.saturating_sub(1);
6357 if depth == 0 {
6358 return true;
6359 }
6360 }
6361 kind if depth == 1 && recovery.contains(&kind) => return false,
6362 _ => {}
6363 }
6364 }
6365 false
6366 }
6367
6368 fn current_simple_block_has_matching_close(&self, recovery: &[SyntaxKind]) -> bool {
6369 let Some(open_kind) = self.current_kind() else {
6370 return false;
6371 };
6372 if matching_simple_block_close(open_kind).is_none() {
6373 return false;
6374 }
6375
6376 let mut expected_closes = Vec::new();
6377 for token in self.tokens.iter().skip(self.position) {
6378 if let Some(close_kind) = matching_simple_block_close(token.kind) {
6379 expected_closes.push(close_kind);
6380 continue;
6381 }
6382
6383 if expected_closes.last().copied() == Some(token.kind) {
6384 expected_closes.pop();
6385 if expected_closes.is_empty() {
6386 return true;
6387 }
6388 continue;
6389 }
6390
6391 if expected_closes.len() == 1 && recovery.contains(&token.kind) {
6392 return false;
6393 }
6394 }
6395 false
6396 }
6397
6398 fn current_dialect_at_rule_node_kind(&self, spec: AtRuleSpec) -> SyntaxKind {
6399 if !self.find_rule_block_open_before_recovery(&[
6400 SyntaxKind::Semicolon,
6401 SyntaxKind::SassOptionalSemicolon,
6402 SyntaxKind::RightBrace,
6403 SyntaxKind::SassDedent,
6404 ]) {
6405 return match spec.node_kind {
6406 SyntaxKind::ScssMixinDeclaration => SyntaxKind::BogusScssMixin,
6407 SyntaxKind::ScssFunctionDeclaration => SyntaxKind::BogusScssFunction,
6408 SyntaxKind::ScssControlIf
6409 | SyntaxKind::ScssControlElse
6410 | SyntaxKind::ScssControlEach
6411 | SyntaxKind::ScssControlFor
6412 | SyntaxKind::ScssControlWhile => SyntaxKind::BogusScssControl,
6413 _ => spec.node_kind,
6414 };
6415 }
6416 spec.node_kind
6417 }
6418
6419 fn current_less_guard_has_condition_before(&self, recovery: &[SyntaxKind]) -> bool {
6420 let mut index = self.position + 1;
6421 while let Some(token) = self.tokens.get(index) {
6422 if recovery.contains(&token.kind) {
6423 return false;
6424 }
6425 if token.kind == SyntaxKind::LeftParen {
6426 return true;
6427 }
6428 index += 1;
6429 }
6430 false
6431 }
6432
6433 fn current_scss_module_config_has_balanced_parens(&self) -> bool {
6434 let Some((_, SyntaxKind::LeftParen)) = self.non_trivia_token_from(self.position + 1) else {
6435 return false;
6436 };
6437 self.current_prelude_parentheses_are_balanced_until(&[
6438 SyntaxKind::Semicolon,
6439 SyntaxKind::SassOptionalSemicolon,
6440 SyntaxKind::LeftBrace,
6441 SyntaxKind::SassIndent,
6442 ])
6443 }
6444
6445 fn current_value_has_top_level_comma_before(&self, recovery: &[SyntaxKind]) -> bool {
6446 let mut paren_depth = 0usize;
6447 let mut bracket_depth = 0usize;
6448 for token in self.tokens.iter().skip(self.position) {
6449 match token.kind {
6450 kind if paren_depth == 0 && bracket_depth == 0 && recovery.contains(&kind) => {
6451 return false;
6452 }
6453 SyntaxKind::LeftParen => paren_depth += 1,
6454 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
6455 SyntaxKind::LeftBracket => bracket_depth += 1,
6456 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
6457 SyntaxKind::Comma if paren_depth == 0 && bracket_depth == 0 => return true,
6458 _ => {}
6459 }
6460 }
6461 false
6462 }
6463
6464 fn current_value_list_is_bogus(&self, recovery: &[SyntaxKind]) -> bool {
6465 let mut paren_depth = 0usize;
6466 let mut bracket_depth = 0usize;
6467 let mut expecting_item = true;
6468 for token in self.tokens.iter().skip(self.position) {
6469 if token.kind.is_trivia() {
6470 continue;
6471 }
6472 match token.kind {
6473 kind if paren_depth == 0 && bracket_depth == 0 && recovery.contains(&kind) => {
6474 return expecting_item;
6475 }
6476 SyntaxKind::LeftParen => {
6477 paren_depth += 1;
6478 expecting_item = false;
6479 }
6480 SyntaxKind::RightParen => {
6481 paren_depth = paren_depth.saturating_sub(1);
6482 expecting_item = false;
6483 }
6484 SyntaxKind::LeftBracket => {
6485 bracket_depth += 1;
6486 expecting_item = false;
6487 }
6488 SyntaxKind::RightBracket => {
6489 bracket_depth = bracket_depth.saturating_sub(1);
6490 expecting_item = false;
6491 }
6492 SyntaxKind::Comma if paren_depth == 0 && bracket_depth == 0 => {
6493 if expecting_item {
6494 return true;
6495 }
6496 expecting_item = true;
6497 }
6498 _ => expecting_item = false,
6499 }
6500 }
6501 expecting_item
6502 }
6503
6504 fn current_starts_missing_semicolon_declaration(&self, recovery: &[SyntaxKind]) -> bool {
6505 match self.current_kind() {
6506 Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName) => {}
6507 _ => return false,
6508 }
6509
6510 let mut index = self.position + 1;
6511 while let Some(token) = self.tokens.get(index) {
6512 if token.kind.is_trivia() {
6513 index += 1;
6514 continue;
6515 }
6516 if recovery.contains(&token.kind) {
6517 return false;
6518 }
6519 return token.kind == SyntaxKind::Colon;
6520 }
6521 false
6522 }
6523
6524 fn current_selector_item_is_bogus(&self, recovery: &[SyntaxKind]) -> bool {
6525 self.selector_item_is_bogus_from(self.position, recovery)
6526 }
6527
6528 fn selector_item_is_bogus_from(&self, start: usize, recovery: &[SyntaxKind]) -> bool {
6529 let mut paren_depth = 0usize;
6530 let mut bracket_depth = 0usize;
6531 let mut saw_selector_token = false;
6532
6533 for token in self.tokens.iter().skip(start) {
6534 if token.kind.is_trivia() {
6535 continue;
6536 }
6537 if paren_depth == 0
6538 && bracket_depth == 0
6539 && (token.kind == SyntaxKind::Comma
6540 || is_selector_boundary_until(token.kind, recovery))
6541 {
6542 break;
6543 }
6544
6545 match token.kind {
6546 SyntaxKind::LeftParen => paren_depth += 1,
6547 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
6548 SyntaxKind::LeftBracket => bracket_depth += 1,
6549 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
6550 _ => {}
6551 }
6552
6553 if !selector_item_token_is_recoverable(token.kind) {
6554 return true;
6555 }
6556 saw_selector_token = true;
6557 }
6558
6559 !saw_selector_token
6560 }
6561
6562 fn selector_list_contains_bogus_item_until(&self, recovery: &[SyntaxKind]) -> bool {
6563 let mut index = self.position;
6564 while let Some(token) = self.tokens.get(index) {
6565 if token.kind.is_trivia() || token.kind == SyntaxKind::Comma {
6566 index += 1;
6567 continue;
6568 }
6569 if is_selector_boundary_until(token.kind, recovery) {
6570 return false;
6571 }
6572 if self.selector_item_is_bogus_from(index, recovery) {
6573 return true;
6574 }
6575
6576 let mut paren_depth = 0usize;
6577 let mut bracket_depth = 0usize;
6578 while let Some(token) = self.tokens.get(index) {
6579 if paren_depth == 0
6580 && bracket_depth == 0
6581 && (token.kind == SyntaxKind::Comma
6582 || is_selector_boundary_until(token.kind, recovery))
6583 {
6584 break;
6585 }
6586 match token.kind {
6587 SyntaxKind::LeftParen => paren_depth += 1,
6588 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
6589 SyntaxKind::LeftBracket => bracket_depth += 1,
6590 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
6591 _ => {}
6592 }
6593 index += 1;
6594 }
6595 }
6596 false
6597 }
6598
6599 fn current_generic_at_rule_prelude_node_kind(&self) -> SyntaxKind {
6600 if self.current_prelude_parentheses_are_balanced_until(&[
6601 SyntaxKind::LeftBrace,
6602 SyntaxKind::Semicolon,
6603 ]) {
6604 SyntaxKind::AtRulePrelude
6605 } else {
6606 SyntaxKind::BogusAtRulePrelude
6607 }
6608 }
6609
6610 fn current_prelude_parentheses_are_balanced_until(&self, recovery: &[SyntaxKind]) -> bool {
6611 let mut depth = 0usize;
6612 for token in self.tokens.iter().skip(self.position) {
6613 match token.kind {
6614 kind if depth == 0 && recovery.contains(&kind) => return true,
6615 SyntaxKind::LeftParen => depth += 1,
6616 SyntaxKind::RightParen => {
6617 if depth == 0 {
6618 return false;
6619 }
6620 depth -= 1;
6621 }
6622 _ => {}
6623 }
6624 }
6625 depth == 0
6626 }
6627
6628 fn previous_left_brace_has_match(&self) -> bool {
6629 let Some(open_index) = self.position.checked_sub(1) else {
6630 return false;
6631 };
6632 let Some(open) = self.tokens.get(open_index) else {
6633 return false;
6634 };
6635 if open.kind != SyntaxKind::LeftBrace {
6636 return false;
6637 }
6638
6639 let mut depth = 0usize;
6640 for token in self.tokens.iter().skip(open_index) {
6641 match token.kind {
6642 SyntaxKind::LeftBrace => depth += 1,
6643 SyntaxKind::RightBrace => {
6644 depth = depth.saturating_sub(1);
6645 if depth == 0 {
6646 return true;
6647 }
6648 }
6649 _ => {}
6650 }
6651 }
6652 false
6653 }
6654
6655 fn current_starts_nested_rule(&self) -> bool {
6656 matches!(
6657 self.current_kind(),
6658 Some(
6659 SyntaxKind::Dot
6660 | SyntaxKind::Hash
6661 | SyntaxKind::Ampersand
6662 | SyntaxKind::Colon
6663 | SyntaxKind::DoubleColon
6664 | SyntaxKind::LeftBracket
6665 )
6666 ) && self.find_rule_block_open_before_recovery(&[
6667 SyntaxKind::Colon,
6668 SyntaxKind::Semicolon,
6669 SyntaxKind::SassOptionalSemicolon,
6670 SyntaxKind::RightBrace,
6671 SyntaxKind::SassDedent,
6672 ])
6673 }
6674
6675 fn current_starts_scss_nested_property(&self) -> bool {
6676 if !matches!(self.dialect, StyleDialect::Scss | StyleDialect::Sass) {
6677 return false;
6678 }
6679 if !matches!(
6680 self.current_kind(),
6681 Some(SyntaxKind::Ident | SyntaxKind::CustomPropertyName)
6682 ) {
6683 return false;
6684 }
6685
6686 let mut saw_colon = false;
6687 for token in self.tokens.iter().skip(self.position) {
6688 match token.kind {
6689 SyntaxKind::Colon => saw_colon = true,
6690 SyntaxKind::LeftBrace if saw_colon => return true,
6691 SyntaxKind::SassIndent if saw_colon && self.dialect == StyleDialect::Sass => {
6692 return true;
6693 }
6694 SyntaxKind::Semicolon
6695 | SyntaxKind::SassOptionalSemicolon
6696 | SyntaxKind::RightBrace
6697 | SyntaxKind::SassDedent => return false,
6698 _ => {}
6699 }
6700 }
6701 false
6702 }
6703
6704 fn current_starts_less_mixin_declaration(&self) -> bool {
6705 self.dialect == StyleDialect::Less
6706 && self.current_starts_less_callable_signature()
6707 && self.find_before_recovery(
6708 SyntaxKind::LeftBrace,
6709 &[SyntaxKind::Semicolon, SyntaxKind::RightBrace],
6710 )
6711 }
6712
6713 fn current_starts_less_mixin_call(&self) -> bool {
6714 self.dialect == StyleDialect::Less
6715 && self.current_starts_less_callable_signature()
6716 && !self.find_before_recovery(
6717 SyntaxKind::LeftBrace,
6718 &[SyntaxKind::Semicolon, SyntaxKind::RightBrace],
6719 )
6720 }
6721
6722 fn current_starts_less_callable_signature(&self) -> bool {
6723 match self.current_kind() {
6724 Some(SyntaxKind::Dot) => {
6725 let Some((index, SyntaxKind::Ident | SyntaxKind::CustomPropertyName)) =
6726 self.non_trivia_token_from(self.position + 1)
6727 else {
6728 return false;
6729 };
6730 self.non_trivia_token_from(index + 1)
6731 .is_some_and(|(_, kind)| kind == SyntaxKind::LeftParen)
6732 }
6733 Some(SyntaxKind::Hash) => self
6734 .non_trivia_token_from(self.position + 1)
6735 .is_some_and(|(_, kind)| kind == SyntaxKind::LeftParen),
6736 _ => false,
6737 }
6738 }
6739
6740 fn current_starts_less_extend_rule(&self) -> bool {
6741 self.dialect == StyleDialect::Less
6742 && self.current_kind() == Some(SyntaxKind::Colon)
6743 && self
6744 .non_trivia_token_from(self.position + 1)
6745 .is_some_and(|(index, kind)| {
6746 kind == SyntaxKind::Ident
6747 && self
6748 .tokens
6749 .get(index)
6750 .is_some_and(|token| token.text == "extend")
6751 })
6752 }
6753
6754 fn current_starts_less_namespace_access(&self) -> bool {
6755 self.dialect == StyleDialect::Less
6756 && matches!(
6757 self.current_kind(),
6758 Some(SyntaxKind::Dot | SyntaxKind::Hash)
6759 )
6760 && self.find_before_recovery(
6761 SyntaxKind::GreaterThan,
6762 &[
6763 SyntaxKind::Semicolon,
6764 SyntaxKind::LeftBrace,
6765 SyntaxKind::RightBrace,
6766 ],
6767 )
6768 && self.find_before_recovery(
6769 SyntaxKind::LeftParen,
6770 &[
6771 SyntaxKind::Semicolon,
6772 SyntaxKind::LeftBrace,
6773 SyntaxKind::RightBrace,
6774 ],
6775 )
6776 }
6777
6778 fn current_left_brace_has_match(&self) -> bool {
6779 let mut depth = 0usize;
6780 for token in self.tokens.iter().skip(self.position) {
6781 match token.kind {
6782 SyntaxKind::LeftBrace => depth += 1,
6783 SyntaxKind::RightBrace => {
6784 depth = depth.saturating_sub(1);
6785 if depth == 0 {
6786 return true;
6787 }
6788 }
6789 _ => {}
6790 }
6791 }
6792 false
6793 }
6794
6795 fn token_current(&mut self) {
6796 if let Some(token) = self.tokens.get(self.position).copied() {
6797 self.builder.token(token.kind, token.text);
6798 self.position += 1;
6799 }
6800 }
6801
6802 fn empty_bogus_node(&mut self, kind: SyntaxKind, code: ParseErrorCode, message: &'static str) {
6803 self.builder.start_node(kind);
6804 self.builder.finish_node();
6805 self.error_at_current(code, message);
6806 }
6807
6808 fn missing_token_bogus_trivia(&mut self, code: ParseErrorCode, message: &'static str) {
6809 self.builder.start_node(SyntaxKind::BogusTrivia);
6810 self.builder.finish_node();
6811 self.error_at_current(code, message);
6812 }
6813
6814 fn error_at_current(&mut self, code: ParseErrorCode, message: &'static str) {
6815 self.errors.push(ParseError {
6816 code,
6817 range: self.current_range(),
6818 message,
6819 });
6820 }
6821
6822 fn current_kind(&self) -> Option<SyntaxKind> {
6823 self.tokens.get(self.position).map(|token| token.kind)
6824 }
6825
6826 fn current_range(&self) -> TextRange {
6827 if let Some(token) = self.tokens.get(self.position) {
6828 return token.range;
6829 }
6830 let end = self
6831 .tokens
6832 .last()
6833 .map(|token| token.range.end())
6834 .unwrap_or_else(|| TextSize::from(0));
6835 TextRange::new(end, end)
6836 }
6837
6838 fn current_text(&self) -> Option<&'text str> {
6839 self.tokens.get(self.position).map(|token| token.text)
6840 }
6841
6842 fn current_dialect_at_rule_spec(&self) -> Option<AtRuleSpec> {
6843 let text = self.current_text()?;
6844 match self.dialect {
6845 StyleDialect::Scss | StyleDialect::Sass => scss_at_rule_spec(text),
6846 StyleDialect::Css | StyleDialect::Less => None,
6847 }
6848 }
6849
6850 fn current_is_css_module_value_rule(&self) -> bool {
6851 self.current_text() == Some("@value")
6852 }
6853
6854 fn next_kind(&self) -> Option<SyntaxKind> {
6855 self.tokens.get(self.position + 1).map(|token| token.kind)
6856 }
6857
6858 fn next_non_trivia_kind(&self) -> Option<SyntaxKind> {
6859 let mut index = self.position + 1;
6860 while let Some(token) = self.tokens.get(index) {
6861 if !token.kind.is_trivia() {
6862 return Some(token.kind);
6863 }
6864 index += 1;
6865 }
6866 None
6867 }
6868
6869 fn non_trivia_token_from(&self, mut index: usize) -> Option<(usize, SyntaxKind)> {
6870 while let Some(token) = self.tokens.get(index) {
6871 if !token.kind.is_trivia() {
6872 return Some((index, token.kind));
6873 }
6874 index += 1;
6875 }
6876 None
6877 }
6878
6879 fn non_trivia_token_after_interpolation(
6880 &self,
6881 mut index: usize,
6882 start_kind: SyntaxKind,
6883 ) -> Option<(usize, SyntaxKind)> {
6884 let end_kind = interpolation_end_kind(start_kind)?;
6885 index += 1;
6886 while let Some(token) = self.tokens.get(index) {
6887 if token.kind == end_kind {
6888 return self.non_trivia_token_from(index + 1);
6889 }
6890 if is_at_rule_prelude_boundary(token.kind) {
6891 return None;
6892 }
6893 index += 1;
6894 }
6895 None
6896 }
6897
6898 fn current_starts_namespace_qualified_selector(&self, kind: SyntaxKind) -> bool {
6899 match kind {
6900 SyntaxKind::Ident | SyntaxKind::Star => {
6901 self.next_kind() == Some(SyntaxKind::Pipe)
6902 && self
6903 .tokens
6904 .get(self.position + 2)
6905 .is_some_and(|token| namespace_selector_target_can_start(token.kind))
6906 }
6907 SyntaxKind::Pipe => self
6908 .tokens
6909 .get(self.position + 1)
6910 .is_some_and(|token| namespace_selector_target_can_start(token.kind)),
6911 _ => false,
6912 }
6913 }
6914
6915 fn namespace_qualified_selector_target_kind(&self) -> Option<SyntaxKind> {
6916 let target_index = if self.current_kind() == Some(SyntaxKind::Pipe) {
6917 self.position + 1
6918 } else {
6919 self.position + 2
6920 };
6921 self.tokens.get(target_index).map(|token| token.kind)
6922 }
6923
6924 fn at_end(&self) -> bool {
6925 self.position >= self.tokens.len()
6926 }
6927}
6928
6929impl<'text, 'extension, E> Tokenizer<'text, 'extension, E>
6930where
6931 E: DialectExtension,
6932{
6933 fn new(text: &'text str, extension: &'extension E) -> Self {
6934 Self {
6935 text,
6936 extension,
6937 offset: 0,
6938 scss_interpolation_depth: 0,
6939 less_interpolation_depth: 0,
6940 sass_indent_stack: vec![0],
6941 tokens: Vec::new(),
6942 errors: Vec::new(),
6943 }
6944 }
6945
6946 fn tokenize(&mut self) {
6947 while let Some(current) = self.current_char() {
6948 let start = self.offset;
6949 match current {
6950 '\u{feff}' if start == 0 => self.bump_current(),
6951 '\r' | '\n' if self.extension.dialect() == StyleDialect::Sass => {
6952 self.consume_sass_indented_newline(start)
6953 }
6954 char if char.is_whitespace() => {
6955 self.consume_while(SyntaxKind::Whitespace, |c| c.is_whitespace())
6956 }
6957 '/' if self.starts_with("/*") => self.consume_block_comment(),
6958 '/' if self.starts_with("//") && self.extension.dialect() != StyleDialect::Css => {
6959 self.consume_line_comment()
6960 }
6961 '#' if self.starts_with("#{") && self.supports_scss_interpolation() => {
6962 self.consume_scss_interpolation_start(start)
6963 }
6964 '@' if self.starts_with("@{") && self.supports_less_interpolation() => {
6965 self.consume_less_interpolation_start(start)
6966 }
6967 '!' if self.starts_with_ascii_keyword("!important") => {
6968 self.consume_static(SyntaxKind::Important, start, "!important".len())
6969 }
6970 '<' if self.starts_with("<!--") => {
6971 self.consume_static(SyntaxKind::Cdo, start, "<!--".len())
6972 }
6973 '-' if self.starts_with("-->") => {
6974 self.consume_static(SyntaxKind::Cdc, start, "-->".len())
6975 }
6976 '"' | '\'' => self.consume_string(current),
6977 'u' | 'U' if self.starts_unicode_range() => self.consume_unicode_range(),
6978 '0'..='9' => self.consume_number(),
6979 '$' if matches!(
6980 self.extension.dialect(),
6981 StyleDialect::Scss | StyleDialect::Sass
6982 ) =>
6983 {
6984 self.consume_prefixed_name(SyntaxKind::ScssVariable)
6985 }
6986 '@' if self.extension.dialect() == StyleDialect::Less => {
6987 self.consume_less_at_name()
6988 }
6989 '@' => self.consume_at_keyword(),
6990 '!' => self.consume_static(SyntaxKind::Delim, start, 1),
6991 '.' if self.current_starts_number() => self.consume_number(),
6992 '.' => self.consume_static(SyntaxKind::Dot, start, 1),
6993 ',' => self.consume_static(SyntaxKind::Comma, start, 1),
6994 ':' if self.starts_with("::") => {
6995 self.consume_static(SyntaxKind::DoubleColon, start, 2)
6996 }
6997 ':' => self.consume_static(SyntaxKind::Colon, start, 1),
6998 ';' => self.consume_static(SyntaxKind::Semicolon, start, 1),
6999 '{' => self.consume_static(SyntaxKind::LeftBrace, start, 1),
7000 '}' if self.scss_interpolation_depth > 0 => {
7001 self.consume_scss_interpolation_end(start)
7002 }
7003 '}' if self.less_interpolation_depth > 0 => {
7004 self.consume_less_interpolation_end(start)
7005 }
7006 '}' => self.consume_static(SyntaxKind::RightBrace, start, 1),
7007 '(' => self.consume_static(SyntaxKind::LeftParen, start, 1),
7008 ')' => self.consume_static(SyntaxKind::RightParen, start, 1),
7009 '[' => self.consume_static(SyntaxKind::LeftBracket, start, 1),
7010 ']' => self.consume_static(SyntaxKind::RightBracket, start, 1),
7011 '+' if self.starts_with("+=") => {
7012 self.consume_static(SyntaxKind::PlusEquals, start, 2)
7013 }
7014 '+' if self.current_starts_number() => self.consume_number(),
7015 '+' => self.consume_static(SyntaxKind::Plus, start, 1),
7016 '-' if self.starts_with("-=") => {
7017 self.consume_static(SyntaxKind::MinusEquals, start, 2)
7018 }
7019 '-' if self.current_starts_number() => self.consume_number(),
7020 '-' if self.current_starts_ident_sequence() => self.consume_ident_like(),
7021 '-' => self.consume_static(SyntaxKind::Minus, start, 1),
7022 '*' if self.starts_with("*=") => {
7023 self.consume_static(SyntaxKind::SubstringMatch, start, 2)
7024 }
7025 '*' => self.consume_static(SyntaxKind::Star, start, 1),
7026 '/' if self.starts_with("/=") => {
7027 self.consume_static(SyntaxKind::SlashEquals, start, 2)
7028 }
7029 '/' => self.consume_static(SyntaxKind::Slash, start, 1),
7030 '%' if self.starts_scss_placeholder() => {
7031 self.consume_prefixed_name(SyntaxKind::ScssPlaceholder)
7032 }
7033 '%' => self.consume_static(SyntaxKind::Percent, start, 1),
7034 '=' if self.starts_with("=>") => self.consume_static(SyntaxKind::Arrow, start, 2),
7035 '=' => self.consume_static(SyntaxKind::Equals, start, 1),
7036 '~' if self.starts_less_escaped_string() => self.consume_less_escaped_string(start),
7037 '~' if self.starts_with("~=") => {
7038 self.consume_static(SyntaxKind::IncludesMatch, start, 2)
7039 }
7040 '~' => self.consume_static(SyntaxKind::Tilde, start, 1),
7041 '|' if self.starts_with("|=") => {
7042 self.consume_static(SyntaxKind::DashMatch, start, 2)
7043 }
7044 '|' if self.starts_with("||") => {
7045 self.consume_static(SyntaxKind::ColumnCombinator, start, 2)
7046 }
7047 '|' => self.consume_static(SyntaxKind::Pipe, start, 1),
7048 '^' if self.starts_with("^=") => {
7049 self.consume_static(SyntaxKind::PrefixMatch, start, 2)
7050 }
7051 '^' => self.consume_static(SyntaxKind::Caret, start, 1),
7052 '$' if self.starts_with("$=") => {
7053 self.consume_static(SyntaxKind::SuffixMatch, start, 2)
7054 }
7055 '$' if self.starts_less_property_variable() => {
7056 self.consume_prefixed_name(SyntaxKind::LessPropertyVariableToken)
7057 }
7058 '&' if self.starts_with("&&") => {
7059 self.consume_static(SyntaxKind::DoubleAmpersand, start, 2)
7060 }
7061 '&' => self.consume_static(SyntaxKind::Ampersand, start, 1),
7062 '>' => self.consume_static(SyntaxKind::GreaterThan, start, 1),
7063 '<' => self.consume_static(SyntaxKind::LessThan, start, 1),
7064 '#' if self.current_hash_starts_name() => self.consume_name_like(SyntaxKind::Hash),
7065 '#' => self.consume_static(SyntaxKind::Delim, start, 1),
7066 '\\' if self.current_starts_valid_escape() => {
7067 self.consume_name_like(SyntaxKind::Ident)
7068 }
7069 char if is_name_start(char) => self.consume_ident_like(),
7070 char => self.consume_unexpected(char),
7071 }
7072 }
7073 self.consume_pending_sass_dedents();
7074 }
7075
7076 fn consume_static(&mut self, kind: SyntaxKind, start: usize, byte_len: usize) {
7077 self.offset += byte_len;
7078 self.push(kind, start, self.offset);
7079 }
7080
7081 fn consume_while(&mut self, kind: SyntaxKind, predicate: impl Fn(char) -> bool) {
7082 let start = self.offset;
7083 while let Some(char) = self.current_char() {
7084 if !predicate(char) {
7085 break;
7086 }
7087 self.bump_char(char);
7088 }
7089 self.push(kind, start, self.offset);
7090 }
7091
7092 fn consume_block_comment(&mut self) {
7093 let start = self.offset;
7094 self.offset += 2;
7095 while self.offset < self.text.len() {
7096 if self.starts_with("*/") {
7097 self.offset += 2;
7098 self.push(SyntaxKind::BlockComment, start, self.offset);
7099 return;
7100 }
7101 match self.current_char() {
7102 Some(char) => self.bump_char(char),
7103 None => break,
7104 }
7105 }
7106 self.push(SyntaxKind::BlockComment, start, self.offset);
7107 self.error(
7108 ParseErrorCode::UnterminatedBlockComment,
7109 start,
7110 self.offset,
7111 "unterminated block comment",
7112 );
7113 }
7114
7115 fn consume_line_comment(&mut self) {
7116 let start = self.offset;
7117 while let Some(char) = self.current_char() {
7118 if char == '\n' {
7119 break;
7120 }
7121 if char == '\r' {
7122 break;
7123 }
7124 self.bump_char(char);
7125 }
7126 self.push(SyntaxKind::LineComment, start, self.offset);
7127 }
7128
7129 fn consume_sass_indented_newline(&mut self, start: usize) {
7130 self.consume_line_break();
7131 let indent = self.consume_sass_line_indent();
7132 let line_start = self.offset;
7133 let current_indent = self.sass_indent_stack.last().copied().unwrap_or(0);
7134
7135 if indent > current_indent {
7136 self.push(SyntaxKind::SassIndentedNewline, start, line_start);
7137 self.sass_indent_stack.push(indent);
7138 self.push(SyntaxKind::SassIndent, line_start, line_start);
7139 return;
7140 }
7141
7142 if self.previous_significant_sass_token_can_end_statement() {
7143 self.push(SyntaxKind::SassOptionalSemicolon, start, start);
7144 }
7145 self.push(SyntaxKind::SassIndentedNewline, start, line_start);
7146
7147 while self.sass_indent_stack.len() > 1
7148 && self
7149 .sass_indent_stack
7150 .last()
7151 .is_some_and(|current| indent < *current)
7152 {
7153 self.sass_indent_stack.pop();
7154 self.push(SyntaxKind::SassDedent, line_start, line_start);
7155 }
7156
7157 if self
7158 .sass_indent_stack
7159 .last()
7160 .is_some_and(|current| indent != *current)
7161 {
7162 self.error(
7163 ParseErrorCode::UnexpectedCharacter,
7164 line_start,
7165 line_start,
7166 "inconsistent Sass indentation",
7167 );
7168 }
7169 }
7170
7171 fn consume_line_break(&mut self) {
7172 if self.starts_with("\r\n") {
7173 self.offset += "\r\n".len();
7174 return;
7175 }
7176 if let Some(char @ ('\r' | '\n')) = self.current_char() {
7177 self.bump_char(char);
7178 }
7179 }
7180
7181 fn consume_sass_line_indent(&mut self) -> usize {
7182 let mut indent = 0usize;
7183 while let Some(char) = self.current_char() {
7184 match char {
7185 ' ' => {
7186 indent += 1;
7187 self.bump_char(char);
7188 }
7189 '\t' => {
7190 indent += 4;
7191 self.bump_char(char);
7192 }
7193 _ => break,
7194 }
7195 }
7196 indent
7197 }
7198
7199 fn consume_pending_sass_dedents(&mut self) {
7200 if self.extension.dialect() != StyleDialect::Sass {
7201 return;
7202 }
7203 while self.sass_indent_stack.len() > 1 {
7204 self.sass_indent_stack.pop();
7205 self.push(SyntaxKind::SassDedent, self.offset, self.offset);
7206 }
7207 }
7208
7209 fn previous_significant_sass_token_can_end_statement(&self) -> bool {
7210 self.tokens
7211 .iter()
7212 .rev()
7213 .find(|token| !token.kind.is_trivia())
7214 .is_some_and(|token| sass_token_can_end_statement(token.kind))
7215 }
7216
7217 fn consume_scss_interpolation_start(&mut self, start: usize) {
7218 self.offset += "#{".len();
7219 self.scss_interpolation_depth += 1;
7220 self.push(SyntaxKind::ScssInterpolationStart, start, self.offset);
7221 }
7222
7223 fn consume_scss_interpolation_end(&mut self, start: usize) {
7224 self.offset += '}'.len_utf8();
7225 self.scss_interpolation_depth = self.scss_interpolation_depth.saturating_sub(1);
7226 self.push(SyntaxKind::ScssInterpolationEnd, start, self.offset);
7227 }
7228
7229 fn consume_less_interpolation_start(&mut self, start: usize) {
7230 self.offset += "@{".len();
7231 self.less_interpolation_depth += 1;
7232 self.push(SyntaxKind::LessInterpolationStart, start, self.offset);
7233 }
7234
7235 fn consume_less_interpolation_end(&mut self, start: usize) {
7236 self.offset += '}'.len_utf8();
7237 self.less_interpolation_depth = self.less_interpolation_depth.saturating_sub(1);
7238 self.push(SyntaxKind::LessInterpolationEnd, start, self.offset);
7239 }
7240
7241 fn consume_string(&mut self, quote: char) {
7242 let start = self.offset;
7243 self.bump_char(quote);
7244 while let Some(char) = self.current_char() {
7245 self.bump_char(char);
7246 if matches!(char, '\n' | '\r' | '\u{000c}') {
7247 self.push(SyntaxKind::BadString, start, self.offset);
7248 self.error(
7249 ParseErrorCode::UnterminatedString,
7250 start,
7251 self.offset,
7252 "unterminated string",
7253 );
7254 return;
7255 }
7256 if char == quote {
7257 self.push(SyntaxKind::String, start, self.offset);
7258 return;
7259 }
7260 if char == '\\'
7261 && let Some(escaped) = self.current_char()
7262 {
7263 self.bump_char(escaped);
7264 }
7265 }
7266 self.push(SyntaxKind::BadString, start, self.offset);
7267 self.error(
7268 ParseErrorCode::UnterminatedString,
7269 start,
7270 self.offset,
7271 "unterminated string",
7272 );
7273 }
7274
7275 fn consume_less_escaped_string(&mut self, start: usize) {
7276 self.offset += '~'.len_utf8();
7277 let Some(quote @ ('"' | '\'')) = self.current_char() else {
7278 self.push(SyntaxKind::Tilde, start, self.offset);
7279 return;
7280 };
7281 self.bump_char(quote);
7282 while let Some(char) = self.current_char() {
7283 self.bump_char(char);
7284 if matches!(char, '\n' | '\r' | '\u{000c}') {
7285 self.push(SyntaxKind::BadString, start, self.offset);
7286 self.error(
7287 ParseErrorCode::UnterminatedString,
7288 start,
7289 self.offset,
7290 "unterminated Less escaped string",
7291 );
7292 return;
7293 }
7294 if char == quote {
7295 self.push(SyntaxKind::LessEscapedString, start, self.offset);
7296 return;
7297 }
7298 if char == '\\'
7299 && let Some(escaped) = self.current_char()
7300 {
7301 self.bump_char(escaped);
7302 }
7303 }
7304 self.push(SyntaxKind::BadString, start, self.offset);
7305 self.error(
7306 ParseErrorCode::UnterminatedString,
7307 start,
7308 self.offset,
7309 "unterminated Less escaped string",
7310 );
7311 }
7312
7313 fn consume_number(&mut self) {
7314 let start = self.offset;
7315 if matches!(self.current_char(), Some('+' | '-')) {
7316 self.bump_current();
7317 }
7318 self.consume_digits();
7319 if self.current_char() == Some('.') && self.char_after_current_is_ascii_digit() {
7320 self.bump_current();
7321 self.consume_digits();
7322 }
7323 if self.current_starts_number_exponent() {
7324 self.bump_current();
7325 if matches!(self.current_char(), Some('+' | '-')) {
7326 self.bump_current();
7327 }
7328 self.consume_digits();
7329 }
7330 if self.current_char() == Some('%') {
7331 self.offset += 1;
7332 self.push(SyntaxKind::Percentage, start, self.offset);
7333 return;
7334 }
7335 if self.current_starts_ident_sequence() {
7336 self.consume_name_continue_sequence();
7337 self.push(SyntaxKind::Dimension, start, self.offset);
7338 return;
7339 }
7340 self.push(SyntaxKind::Number, start, self.offset);
7341 }
7342
7343 fn consume_unicode_range(&mut self) {
7344 let start = self.offset;
7345 self.bump_current();
7346 self.offset += '+'.len_utf8();
7347 self.consume_unicode_range_codepoints(true);
7348 if self.current_char() == Some('-') && self.next_char_is_hex_digit() {
7349 self.bump_current();
7350 self.consume_unicode_range_codepoints(false);
7351 }
7352 self.push(SyntaxKind::UnicodeRange, start, self.offset);
7353 }
7354
7355 fn consume_unicode_range_codepoints(&mut self, allow_question_mark: bool) {
7356 let mut consumed = 0usize;
7357 while consumed < 6 {
7358 match self.current_char() {
7359 Some(char) if char.is_ascii_hexdigit() => {
7360 self.bump_char(char);
7361 consumed += 1;
7362 }
7363 Some('?') if allow_question_mark => {
7364 self.bump_current();
7365 consumed += 1;
7366 }
7367 _ => break,
7368 }
7369 }
7370 }
7371
7372 fn consume_digits(&mut self) {
7373 while matches!(self.current_char(), Some('0'..='9')) {
7374 self.offset += 1;
7375 }
7376 }
7377
7378 fn consume_prefixed_name(&mut self, preferred_kind: SyntaxKind) {
7379 let start = self.offset;
7380 self.bump_current();
7381 while matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7382 self.bump_current();
7383 }
7384 let text = &self.text[start..self.offset];
7385 let kind = self
7386 .extension
7387 .classify_variable_token(text)
7388 .unwrap_or(preferred_kind);
7389 self.push(kind, start, self.offset);
7390 }
7391
7392 fn consume_less_at_name(&mut self) {
7393 let start = self.offset;
7394 self.bump_current();
7395 while matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7396 self.bump_current();
7397 }
7398 let text = &self.text[start..self.offset];
7399 let kind = if is_css_at_rule_name(text) {
7400 SyntaxKind::AtKeyword
7401 } else {
7402 self.extension
7403 .classify_variable_token(text)
7404 .unwrap_or(SyntaxKind::LessVariable)
7405 };
7406 self.push(kind, start, self.offset);
7407 }
7408
7409 fn consume_at_keyword(&mut self) {
7410 let start = self.offset;
7411 self.bump_current();
7412 while matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7413 self.bump_current();
7414 }
7415 self.push(SyntaxKind::AtKeyword, start, self.offset);
7416 }
7417
7418 fn consume_name_like(&mut self, kind: SyntaxKind) {
7419 let start = self.offset;
7420 self.consume_name_start();
7421 self.consume_name_continue_sequence();
7422 self.push(kind, start, self.offset);
7423 }
7424
7425 fn consume_ident_like(&mut self) {
7426 let start = self.offset;
7427 self.consume_name_continue_sequence();
7428 let ident = &self.text[start..self.offset];
7429 if ident.eq_ignore_ascii_case("url")
7430 && self.current_char() == Some('(')
7431 && !self.url_starts_with_quoted_argument()
7432 {
7433 self.consume_url_token(start);
7434 return;
7435 }
7436 let kind = if is_custom_property_name_text(ident) {
7437 SyntaxKind::CustomPropertyName
7438 } else {
7439 SyntaxKind::Ident
7440 };
7441 self.push(kind, start, self.offset);
7442 }
7443
7444 fn consume_name_start(&mut self) {
7445 if self.current_starts_valid_escape() {
7446 self.consume_name_escape();
7447 } else {
7448 self.bump_current();
7449 }
7450 }
7451
7452 fn consume_name_continue_sequence(&mut self) {
7453 loop {
7454 if self.current_starts_valid_escape() {
7455 self.consume_name_escape();
7456 } else if matches!(self.current_char(), Some(char) if is_name_continue(char)) {
7457 self.bump_current();
7458 } else {
7459 break;
7460 }
7461 }
7462 }
7463
7464 fn consume_name_escape(&mut self) {
7465 self.bump_current();
7466 let mut hex_digits = 0usize;
7467 while hex_digits < 6
7468 && matches!(self.current_char(), Some(char) if char.is_ascii_hexdigit())
7469 {
7470 self.bump_current();
7471 hex_digits += 1;
7472 }
7473 if hex_digits > 0 {
7474 if matches!(self.current_char(), Some(char) if char.is_whitespace()) {
7475 self.bump_current();
7476 }
7477 } else if self.current_char().is_some() {
7478 self.bump_current();
7479 }
7480 }
7481
7482 fn consume_url_token(&mut self, start: usize) {
7483 self.bump_current();
7484 while matches!(self.current_char(), Some(char) if char.is_whitespace()) {
7485 self.bump_current();
7486 }
7487 while let Some(char) = self.current_char() {
7488 match char {
7489 ')' => {
7490 self.bump_current();
7491 self.push(SyntaxKind::Url, start, self.offset);
7492 return;
7493 }
7494 char if char.is_whitespace() => {
7495 self.bump_current();
7496 while matches!(self.current_char(), Some(char) if char.is_whitespace()) {
7497 self.bump_current();
7498 }
7499 if self.current_char() == Some(')') {
7500 self.bump_current();
7501 self.push(SyntaxKind::Url, start, self.offset);
7502 return;
7503 }
7504 self.consume_bad_url(start);
7505 return;
7506 }
7507 '"' | '\'' | '(' => {
7508 self.consume_bad_url(start);
7509 return;
7510 }
7511 '\\' if self.current_starts_valid_escape() => {
7512 self.consume_name_escape();
7513 }
7514 '\\' => {
7515 self.consume_bad_url(start);
7516 return;
7517 }
7518 char if is_non_printable_code_point(char) => {
7519 self.consume_bad_url(start);
7520 return;
7521 }
7522 _ => self.bump_current(),
7523 }
7524 }
7525 self.push(SyntaxKind::BadUrl, start, self.offset);
7526 self.error(
7527 ParseErrorCode::UnexpectedCharacter,
7528 start,
7529 self.offset,
7530 "unterminated url token",
7531 );
7532 }
7533
7534 fn consume_bad_url(&mut self, start: usize) {
7535 while let Some(char) = self.current_char() {
7536 if char == ')' {
7537 self.bump_current();
7538 break;
7539 }
7540 if self.current_starts_valid_escape() {
7541 self.consume_name_escape();
7542 } else {
7543 self.bump_current();
7544 }
7545 }
7546 self.push(SyntaxKind::BadUrl, start, self.offset);
7547 self.error(
7548 ParseErrorCode::UnexpectedCharacter,
7549 start,
7550 self.offset,
7551 "bad url token",
7552 );
7553 }
7554
7555 fn url_starts_with_quoted_argument(&self) -> bool {
7556 let Some(mut rest) = self.text.get(self.offset + '('.len_utf8()..) else {
7557 return false;
7558 };
7559 rest = rest.trim_start_matches(char::is_whitespace);
7560 matches!(rest.chars().next(), Some('"' | '\''))
7561 }
7562
7563 fn starts_less_property_variable(&self) -> bool {
7564 self.extension.dialect() == StyleDialect::Less
7565 && self.text[self.offset + '$'.len_utf8()..]
7566 .chars()
7567 .next()
7568 .is_some_and(is_name_start)
7569 }
7570
7571 fn starts_scss_placeholder(&self) -> bool {
7572 matches!(
7573 self.extension.dialect(),
7574 StyleDialect::Scss | StyleDialect::Sass
7575 ) && self.text[self.offset + '%'.len_utf8()..]
7576 .chars()
7577 .next()
7578 .is_some_and(is_name_start)
7579 }
7580
7581 fn current_hash_starts_name(&self) -> bool {
7582 if self.current_char() != Some('#') {
7583 return false;
7584 }
7585 let next_offset = self.offset + '#'.len_utf8();
7586 self.text[next_offset..]
7587 .chars()
7588 .next()
7589 .is_some_and(is_name_continue)
7590 || self.escape_starts_at(next_offset)
7591 }
7592
7593 fn consume_unexpected(&mut self, char: char) {
7594 let start = self.offset;
7595 self.bump_char(char);
7596 self.push(SyntaxKind::Delim, start, self.offset);
7597 self.error(
7598 ParseErrorCode::UnexpectedCharacter,
7599 start,
7600 self.offset,
7601 "unexpected character",
7602 );
7603 }
7604
7605 fn push(&mut self, kind: SyntaxKind, start: usize, end: usize) {
7606 self.tokens.push(Token {
7607 kind,
7608 text: &self.text[start..end],
7609 range: text_range(start, end),
7610 });
7611 }
7612
7613 fn error(&mut self, code: ParseErrorCode, start: usize, end: usize, message: &'static str) {
7614 self.errors.push(ParseError {
7615 code,
7616 range: text_range(start, end),
7617 message,
7618 });
7619 }
7620
7621 fn starts_with(&self, pattern: &str) -> bool {
7622 self.text[self.offset..].starts_with(pattern)
7623 }
7624
7625 fn current_starts_valid_escape(&self) -> bool {
7626 self.escape_starts_at(self.offset)
7627 }
7628
7629 fn current_starts_number(&self) -> bool {
7630 self.starts_number_at(self.offset)
7631 }
7632
7633 fn current_starts_number_exponent(&self) -> bool {
7634 let Some('e' | 'E') = self.current_char() else {
7635 return false;
7636 };
7637 let exponent_offset = self.offset + 'e'.len_utf8();
7638 self.char_at(exponent_offset)
7639 .is_some_and(|char| char.is_ascii_digit())
7640 || (matches!(self.char_at(exponent_offset), Some('+' | '-'))
7641 && self.char_after_offset_is_ascii_digit(exponent_offset))
7642 }
7643
7644 fn starts_number_at(&self, offset: usize) -> bool {
7645 let Some(first) = self.char_at(offset) else {
7646 return false;
7647 };
7648 let second_offset = offset + first.len_utf8();
7649 match first {
7650 '+' | '-' => {
7651 self.char_at(second_offset)
7652 .is_some_and(|char| char.is_ascii_digit())
7653 || (self.char_at(second_offset) == Some('.')
7654 && self.char_after_offset_is_ascii_digit(second_offset))
7655 }
7656 '.' => self.char_after_offset_is_ascii_digit(offset),
7657 char => char.is_ascii_digit(),
7658 }
7659 }
7660
7661 fn current_starts_ident_sequence(&self) -> bool {
7662 self.starts_ident_sequence_at(self.offset)
7663 }
7664
7665 fn starts_ident_sequence_at(&self, offset: usize) -> bool {
7666 let Some(first) = self.char_at(offset) else {
7667 return false;
7668 };
7669 let second_offset = offset + first.len_utf8();
7670 match first {
7671 '-' => {
7672 self.char_at(second_offset)
7673 .is_some_and(|char| char == '-' || is_name_start(char))
7674 || self.escape_starts_at(second_offset)
7675 }
7676 '\\' => self.escape_starts_at(offset),
7677 char => is_name_start(char),
7678 }
7679 }
7680
7681 fn escape_starts_at(&self, offset: usize) -> bool {
7682 if !self
7683 .text
7684 .get(offset..)
7685 .is_some_and(|remaining| remaining.starts_with('\\'))
7686 {
7687 return false;
7688 }
7689 self.text[offset + '\\'.len_utf8()..]
7690 .chars()
7691 .next()
7692 .is_some_and(|char| !matches!(char, '\n' | '\r' | '\u{000c}'))
7693 }
7694
7695 fn char_at(&self, offset: usize) -> Option<char> {
7696 self.text.get(offset..)?.chars().next()
7697 }
7698
7699 fn char_after_current_is_ascii_digit(&self) -> bool {
7700 self.char_after_offset_is_ascii_digit(self.offset)
7701 }
7702
7703 fn char_after_offset_is_ascii_digit(&self, offset: usize) -> bool {
7704 let Some(char) = self.char_at(offset) else {
7705 return false;
7706 };
7707 self.char_at(offset + char.len_utf8())
7708 .is_some_and(|char| char.is_ascii_digit())
7709 }
7710
7711 fn starts_with_ascii_keyword(&self, keyword: &str) -> bool {
7712 let remaining = &self.text[self.offset..];
7713 let Some(prefix) = remaining.get(..keyword.len()) else {
7714 return false;
7715 };
7716 if !prefix.eq_ignore_ascii_case(keyword) {
7717 return false;
7718 }
7719 remaining[keyword.len()..]
7720 .chars()
7721 .next()
7722 .is_none_or(|char| !is_name_continue(char))
7723 }
7724
7725 fn supports_scss_interpolation(&self) -> bool {
7726 matches!(
7727 self.extension.dialect(),
7728 StyleDialect::Scss | StyleDialect::Sass
7729 )
7730 }
7731
7732 fn supports_less_interpolation(&self) -> bool {
7733 self.extension.dialect() == StyleDialect::Less
7734 }
7735
7736 fn starts_less_escaped_string(&self) -> bool {
7737 self.extension.dialect() == StyleDialect::Less
7738 && (self.starts_with("~\"") || self.starts_with("~'"))
7739 }
7740
7741 fn starts_unicode_range(&self) -> bool {
7742 let mut chars = self.text[self.offset..].chars();
7743 matches!(chars.next(), Some('u' | 'U'))
7744 && chars.next() == Some('+')
7745 && chars
7746 .next()
7747 .is_some_and(|char| char.is_ascii_hexdigit() || char == '?')
7748 }
7749
7750 fn current_char(&self) -> Option<char> {
7751 self.text[self.offset..].chars().next()
7752 }
7753
7754 fn next_char_is_hex_digit(&self) -> bool {
7755 let offset = self.offset + '-'.len_utf8();
7756 self.text
7757 .get(offset..)
7758 .and_then(|tail| tail.chars().next())
7759 .is_some_and(|char| char.is_ascii_hexdigit())
7760 }
7761
7762 fn bump_current(&mut self) {
7763 if let Some(char) = self.current_char() {
7764 self.bump_char(char);
7765 }
7766 }
7767
7768 fn bump_char(&mut self, char: char) {
7769 self.offset += char.len_utf8();
7770 }
7771}
7772
7773fn public_token_text(text: &str) -> String {
7774 text.chars()
7775 .map(css_syntax_preprocessed_char)
7776 .collect::<String>()
7777}
7778
7779fn css_syntax_preprocessed_char(char: char) -> char {
7780 if char == '\0' { '\u{fffd}' } else { char }
7781}
7782
7783fn is_name_start(char: char) -> bool {
7784 let char = css_syntax_preprocessed_char(char);
7785 char == '_' || char == '-' || char.is_alphabetic() || !char.is_ascii()
7786}
7787
7788fn is_name_continue(char: char) -> bool {
7789 is_name_start(char) || char.is_ascii_digit()
7790}
7791
7792fn is_non_printable_code_point(char: char) -> bool {
7793 let char = css_syntax_preprocessed_char(char);
7794 matches!(char, '\u{0000}'..='\u{0008}' | '\u{000b}' | '\u{000e}'..='\u{001f}' | '\u{007f}')
7795}
7796
7797fn is_custom_property_name_text(text: &str) -> bool {
7798 let Some(rest) = text.strip_prefix("--") else {
7799 return false;
7800 };
7801 let Some(first) = rest.chars().next() else {
7802 return false;
7803 };
7804 first == '-' || is_name_start(first) || starts_valid_escape_text(rest)
7805}
7806
7807fn starts_valid_escape_text(text: &str) -> bool {
7808 text.starts_with('\\')
7809 && text['\\'.len_utf8()..]
7810 .chars()
7811 .next()
7812 .is_some_and(|char| !matches!(char, '\n' | '\r' | '\u{000c}'))
7813}
7814
7815fn is_css_at_rule_name(text: &str) -> bool {
7816 matches_ignore_ascii_case(
7817 text,
7818 &[
7819 "@charset",
7820 "@container",
7821 "@font-face",
7822 "@font-feature-values",
7823 "@font-palette-values",
7824 "@import",
7825 "@keyframes",
7826 "@layer",
7827 "@media",
7828 "@namespace",
7829 "@page",
7830 "@property",
7831 "@scope",
7832 "@starting-style",
7833 "@supports",
7834 "@counter-style",
7835 "@custom-media",
7836 "@color-profile",
7837 "@nest",
7838 "@position-try",
7839 "@view-transition",
7840 "@stylistic",
7841 "@styleset",
7842 "@character-variant",
7843 "@swash",
7844 "@ornaments",
7845 "@annotation",
7846 "@historical-forms",
7847 "@when",
7848 "@else",
7849 ],
7850 )
7851}
7852
7853fn is_interpolation_start(kind: SyntaxKind) -> bool {
7854 matches!(
7855 kind,
7856 SyntaxKind::ScssInterpolationStart | SyntaxKind::LessInterpolationStart
7857 )
7858}
7859
7860fn is_component_value_atom_start(kind: SyntaxKind) -> bool {
7861 matches!(
7862 kind,
7863 SyntaxKind::Ident
7864 | SyntaxKind::CustomPropertyName
7865 | SyntaxKind::Number
7866 | SyntaxKind::Percentage
7867 | SyntaxKind::Dimension
7868 | SyntaxKind::String
7869 | SyntaxKind::LessEscapedString
7870 | SyntaxKind::UnicodeRange
7871 | SyntaxKind::Hash
7872 | SyntaxKind::Url
7873 | SyntaxKind::BadUrl
7874 | SyntaxKind::BadString
7875 | SyntaxKind::Important
7876 | SyntaxKind::ScssVariable
7877 | SyntaxKind::LessVariable
7878 | SyntaxKind::LessPropertyVariableToken
7879 | SyntaxKind::ScssInterpolationStart
7880 | SyntaxKind::LessInterpolationStart
7881 )
7882}
7883
7884fn interpolation_end_kind(start_kind: SyntaxKind) -> Option<SyntaxKind> {
7885 match start_kind {
7886 SyntaxKind::ScssInterpolationStart => Some(SyntaxKind::ScssInterpolationEnd),
7887 SyntaxKind::LessInterpolationStart => Some(SyntaxKind::LessInterpolationEnd),
7888 _ => None,
7889 }
7890}
7891
7892#[derive(Debug, Clone, PartialEq, Eq)]
7893struct SelectorBranch {
7894 name: String,
7895 range: TextRange,
7896 bare_suffix_base: bool,
7897}
7898
7899fn collect_selector_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedSelectorFact> {
7900 let mut selectors = Vec::new();
7901 let mut seen = BTreeSet::new();
7902 collect_selector_facts_in_range(
7903 tokens,
7904 0,
7905 tokens.len(),
7906 &[],
7907 None,
7908 &mut seen,
7909 &mut selectors,
7910 );
7911 selectors
7912}
7913
7914fn collect_selector_facts_in_range(
7915 tokens: &[Token<'_>],
7916 start: usize,
7917 end: usize,
7918 parent_branches: &[SelectorBranch],
7919 css_module_scope: Option<&'static str>,
7920 seen: &mut BTreeSet<(ParsedSelectorFactKind, String, u32, u32)>,
7921 selectors: &mut Vec<ParsedSelectorFact>,
7922) {
7923 let mut index = start;
7924 while index < end {
7925 index = skip_trivia_tokens(tokens, index, end);
7926 if index >= end {
7927 break;
7928 }
7929
7930 if tokens[index].kind == SyntaxKind::AtKeyword {
7931 let block = find_block_after_header(tokens, index, end);
7932 if let Some((open, close)) = block {
7933 if tokens[index].text == "@nest" {
7934 if css_module_scope == Some("global") {
7935 collect_selector_facts_in_range(
7936 tokens,
7937 open + 1,
7938 close,
7939 &[],
7940 css_module_scope,
7941 seen,
7942 selectors,
7943 );
7944 } else {
7945 let branches =
7946 resolve_selector_header(tokens, index + 1, open, parent_branches);
7947 push_class_selector_facts_from_header(
7948 selectors,
7949 seen,
7950 tokens,
7951 index + 1,
7952 open,
7953 );
7954 for branch in &branches {
7955 push_selector_fact(
7956 selectors,
7957 seen,
7958 ParsedSelectorFactKind::Class,
7959 branch.name.clone(),
7960 branch.range,
7961 );
7962 }
7963 collect_selector_facts_in_range(
7964 tokens,
7965 open + 1,
7966 close,
7967 &branches,
7968 css_module_scope,
7969 seen,
7970 selectors,
7971 );
7972 }
7973 } else if style_wrapper_at_rule(tokens[index].text) {
7974 collect_selector_facts_in_range(
7975 tokens,
7976 open + 1,
7977 close,
7978 parent_branches,
7979 css_module_scope,
7980 seen,
7981 selectors,
7982 );
7983 }
7984 index = close + 1;
7985 } else {
7986 index = skip_statement(tokens, index, end);
7987 }
7988 continue;
7989 }
7990
7991 let Some((open, close)) = find_block_after_header(tokens, index, end) else {
7992 index = skip_statement(tokens, index, end);
7993 continue;
7994 };
7995
7996 let effective_scope = css_module_scope
7997 .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
7998 if effective_scope == Some("global") {
7999 collect_selector_facts_in_range(
8000 tokens,
8001 open + 1,
8002 close,
8003 &[],
8004 effective_scope,
8005 seen,
8006 selectors,
8007 );
8008 } else {
8009 let branches = resolve_selector_header(tokens, index, open, parent_branches);
8010 push_class_selector_facts_from_header(selectors, seen, tokens, index, open);
8011 for branch in &branches {
8012 push_selector_fact(
8013 selectors,
8014 seen,
8015 ParsedSelectorFactKind::Class,
8016 branch.name.clone(),
8017 branch.range,
8018 );
8019 }
8020 for id in collect_id_selector_facts_from_header(tokens, index, open)
8021 .into_iter()
8022 .chain(collect_local_function_id_selector_facts_from_header(
8023 tokens, index, open,
8024 ))
8025 {
8026 push_selector_fact(selectors, seen, ParsedSelectorFactKind::Id, id.0, id.1);
8027 }
8028 for placeholder in collect_placeholder_selector_facts_from_header(tokens, index, open) {
8029 push_selector_fact(
8030 selectors,
8031 seen,
8032 ParsedSelectorFactKind::Placeholder,
8033 placeholder.0,
8034 placeholder.1,
8035 );
8036 }
8037
8038 collect_selector_facts_in_range(
8039 tokens,
8040 open + 1,
8041 close,
8042 &branches,
8043 effective_scope,
8044 seen,
8045 selectors,
8046 );
8047 }
8048 index = close + 1;
8049 }
8050}
8051
8052fn push_class_selector_facts_from_header(
8053 selectors: &mut Vec<ParsedSelectorFact>,
8054 seen: &mut BTreeSet<(ParsedSelectorFactKind, String, u32, u32)>,
8055 tokens: &[Token<'_>],
8056 start: usize,
8057 end: usize,
8058) {
8059 for (name, range) in collect_class_selector_names_from_header(tokens, start, end) {
8060 push_selector_fact(selectors, seen, ParsedSelectorFactKind::Class, name, range);
8061 }
8062}
8063
8064fn push_selector_fact(
8065 selectors: &mut Vec<ParsedSelectorFact>,
8066 seen: &mut BTreeSet<(ParsedSelectorFactKind, String, u32, u32)>,
8067 kind: ParsedSelectorFactKind,
8068 name: String,
8069 range: TextRange,
8070) {
8071 if seen.insert((
8072 kind,
8073 name.clone(),
8074 u32::from(range.start()),
8075 u32::from(range.end()),
8076 )) {
8077 selectors.push(ParsedSelectorFact { kind, name, range });
8078 }
8079}
8080
8081fn resolve_selector_header(
8082 tokens: &[Token<'_>],
8083 start: usize,
8084 end: usize,
8085 parent_branches: &[SelectorBranch],
8086) -> Vec<SelectorBranch> {
8087 split_selector_groups(tokens, start, end)
8088 .into_iter()
8089 .flat_map(|(group_start, group_end)| {
8090 resolve_selector_group(tokens, group_start, group_end, parent_branches)
8091 })
8092 .collect()
8093}
8094
8095fn resolve_selector_group(
8096 tokens: &[Token<'_>],
8097 start: usize,
8098 end: usize,
8099 parent_branches: &[SelectorBranch],
8100) -> Vec<SelectorBranch> {
8101 if let Some(mut local_names) = collect_local_function_selector_names(tokens, start, end) {
8102 local_names.extend(collect_class_selector_names_from_header(tokens, start, end));
8103 let bare_suffix_base = parent_branches.is_empty() && local_names.len() == 1;
8104 return local_names
8105 .into_iter()
8106 .map(|(name, range)| SelectorBranch {
8107 name,
8108 range,
8109 bare_suffix_base,
8110 })
8111 .collect();
8112 }
8113
8114 let (tail_start, tail_end) = selector_group_tail_range(tokens, start, end);
8115 let tail_start = skip_trivia_tokens(tokens, tail_start, tail_end);
8116
8117 if let Some((suffix, range)) = ampersand_suffix_selector(tokens, tail_start, tail_end) {
8118 let bases: Vec<&SelectorBranch> = if parent_branches.is_empty() {
8119 Vec::new()
8120 } else {
8121 parent_branches
8122 .iter()
8123 .filter(|parent| parent.bare_suffix_base)
8124 .collect()
8125 };
8126 return bases
8127 .into_iter()
8128 .map(|parent| SelectorBranch {
8129 name: format!("{}{}", parent.name, suffix),
8130 range,
8131 bare_suffix_base: parent.bare_suffix_base,
8132 })
8133 .collect();
8134 }
8135
8136 let class_names = collect_class_selector_names_from_header(tokens, tail_start, tail_end);
8137 if class_names.is_empty() {
8138 return Vec::new();
8139 }
8140
8141 let bare_suffix_base = parent_branches.is_empty()
8142 && class_names.len() == 1
8143 && is_bare_class_selector_group(tokens, tail_start, tail_end);
8144 class_names
8145 .into_iter()
8146 .map(|(name, range)| SelectorBranch {
8147 name,
8148 range,
8149 bare_suffix_base,
8150 })
8151 .collect()
8152}
8153
8154fn is_bare_class_selector_group(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
8155 let dot_index = skip_trivia_tokens(tokens, start, end);
8156 if tokens.get(dot_index).map(|token| token.kind) != Some(SyntaxKind::Dot) {
8157 return false;
8158 }
8159 let name_index = skip_trivia_tokens(tokens, dot_index + 1, end);
8160 if !tokens.get(name_index).is_some_and(|token| {
8161 matches!(
8162 token.kind,
8163 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
8164 )
8165 }) {
8166 return false;
8167 }
8168 skip_trivia_tokens(tokens, name_index + 1, end) >= end
8169}
8170
8171fn split_selector_groups(tokens: &[Token<'_>], start: usize, end: usize) -> Vec<(usize, usize)> {
8172 let mut groups = Vec::new();
8173 let mut group_start = start;
8174 let mut paren_depth = 0usize;
8175 let mut bracket_depth = 0usize;
8176 let mut index = start;
8177 while index < end {
8178 match tokens[index].kind {
8179 SyntaxKind::LeftParen => paren_depth += 1,
8180 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8181 SyntaxKind::LeftBracket => bracket_depth += 1,
8182 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8183 SyntaxKind::Comma if paren_depth == 0 && bracket_depth == 0 => {
8184 groups.push((group_start, index));
8185 group_start = index + 1;
8186 }
8187 _ => {}
8188 }
8189 index += 1;
8190 }
8191 groups.push((group_start, end));
8192 groups
8193}
8194
8195fn selector_group_tail_range(tokens: &[Token<'_>], start: usize, end: usize) -> (usize, usize) {
8196 let mut paren_depth = 0usize;
8197 let mut bracket_depth = 0usize;
8198 let mut tail_start = start;
8199 let mut index = start;
8200 while index < end {
8201 match tokens[index].kind {
8202 SyntaxKind::LeftParen => paren_depth += 1,
8203 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8204 SyntaxKind::LeftBracket => bracket_depth += 1,
8205 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8206 kind if paren_depth == 0 && bracket_depth == 0 && is_selector_combinator_kind(kind) => {
8207 tail_start = index + 1;
8208 }
8209 SyntaxKind::Whitespace if paren_depth == 0 && bracket_depth == 0 => {
8210 let previous = previous_non_trivia_token(tokens, start, index);
8211 let next = next_non_trivia_token_until(tokens, index + 1, end);
8212 if previous.is_some_and(|token| selector_component_can_end(token.kind))
8213 && next.is_some_and(|token| selector_component_can_start(token.kind))
8214 {
8215 tail_start = index + 1;
8216 }
8217 }
8218 _ => {}
8219 }
8220 index += 1;
8221 }
8222 (tail_start, end)
8223}
8224
8225fn ampersand_suffix_selector(
8226 tokens: &[Token<'_>],
8227 start: usize,
8228 end: usize,
8229) -> Option<(String, TextRange)> {
8230 let ampersand_index = skip_trivia_tokens(tokens, start, end);
8231 if tokens.get(ampersand_index)?.kind != SyntaxKind::Ampersand {
8232 return None;
8233 }
8234 let suffix = next_non_trivia_token_until(tokens, ampersand_index + 1, end)?;
8235 if matches!(
8236 suffix.kind,
8237 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
8238 ) {
8239 return Some((suffix.text.to_string(), suffix.range));
8240 }
8241 None
8242}
8243
8244fn collect_class_selector_names_from_header(
8245 tokens: &[Token<'_>],
8246 start: usize,
8247 end: usize,
8248) -> Vec<(String, TextRange)> {
8249 let mut names = Vec::new();
8250 let mut index = start;
8251 let mut paren_depth = 0usize;
8252 let mut bracket_depth = 0usize;
8253 while index < end {
8254 match tokens[index].kind {
8255 SyntaxKind::LeftParen => paren_depth += 1,
8256 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8257 SyntaxKind::LeftBracket => bracket_depth += 1,
8258 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8259 _ => {}
8260 }
8261 if paren_depth == 0
8262 && bracket_depth == 0
8263 && tokens[index].kind == SyntaxKind::Dot
8264 && let Some(name) = next_non_trivia_token_until(tokens, index + 1, end)
8265 && matches!(
8266 name.kind,
8267 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
8268 )
8269 {
8270 names.push((name.text.to_string(), name.range));
8271 }
8272 index += 1;
8273 }
8274 names
8275}
8276
8277fn collect_local_function_selector_names(
8278 tokens: &[Token<'_>],
8279 start: usize,
8280 end: usize,
8281) -> Option<Vec<(String, TextRange)>> {
8282 let colon_index = skip_trivia_tokens(tokens, start, end);
8283 if tokens.get(colon_index)?.kind != SyntaxKind::Colon {
8284 return None;
8285 }
8286 let ident = next_non_trivia_token_until(tokens, colon_index + 1, end)?;
8287 if ident.kind != SyntaxKind::Ident || ident.text != "local" {
8288 return None;
8289 }
8290 let open_index = skip_trivia_tokens(tokens, colon_index + 2, end);
8291 if tokens.get(open_index)?.kind != SyntaxKind::LeftParen {
8292 return None;
8293 }
8294 Some(collect_class_selector_names_from_header(
8295 tokens,
8296 open_index + 1,
8297 end.saturating_sub(1),
8298 ))
8299}
8300
8301fn collect_local_function_id_selector_facts_from_header(
8302 tokens: &[Token<'_>],
8303 start: usize,
8304 end: usize,
8305) -> Vec<(String, TextRange)> {
8306 let mut ids = Vec::new();
8307 let mut index = start;
8308 while index < end {
8309 if tokens[index].kind == SyntaxKind::Colon
8310 && let Some(scope) = next_non_trivia_token_until(tokens, index + 1, end)
8311 && scope.kind == SyntaxKind::Ident
8312 && scope.text == "local"
8313 && let Some(open) = next_non_trivia_token_after_range(tokens, scope.range, end)
8314 && open.kind == SyntaxKind::LeftParen
8315 && let Some(close) = matching_right_paren_from_range(tokens, open.range, end)
8316 {
8317 ids.extend(collect_id_selector_facts_from_header(
8318 tokens,
8319 token_index_by_range(tokens, open.range).map_or(index + 1, |value| value + 1),
8320 close,
8321 ));
8322 index = close.saturating_add(1);
8323 continue;
8324 }
8325 index += 1;
8326 }
8327 ids
8328}
8329
8330fn css_module_block_scope_marker_in_header(
8331 tokens: &[Token<'_>],
8332 start: usize,
8333 end: usize,
8334) -> Option<&'static str> {
8335 if next_non_trivia_token_until(tokens, start, end)
8336 .is_some_and(|token| token.kind == SyntaxKind::AtKeyword)
8337 {
8338 return None;
8339 }
8340
8341 css_module_scope_marker_after_colon(tokens, start, end)
8342 .filter(|_| !css_module_scope_marker_is_function(tokens, start, end))
8343}
8344
8345fn css_module_header_is_global_only(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
8346 if next_non_trivia_token_until(tokens, start, end)
8347 .is_some_and(|token| token.kind == SyntaxKind::AtKeyword)
8348 {
8349 return false;
8350 }
8351 css_module_header_contains_scope(tokens, start, end, "global")
8352 && collect_class_selector_names_from_header(tokens, start, end).is_empty()
8353 && collect_local_function_selector_names(tokens, start, end)
8354 .map(|names| names.is_empty())
8355 .unwrap_or(true)
8356}
8357
8358fn css_module_header_contains_scope(
8359 tokens: &[Token<'_>],
8360 start: usize,
8361 end: usize,
8362 expected_scope: &str,
8363) -> bool {
8364 let mut index = start;
8365 while index < end {
8366 if tokens[index].kind == SyntaxKind::Colon
8367 && let Some(scope) = next_non_trivia_token_until(tokens, index + 1, end)
8368 && scope.kind == SyntaxKind::Ident
8369 && scope.text == expected_scope
8370 {
8371 return true;
8372 }
8373 index += 1;
8374 }
8375 false
8376}
8377
8378fn css_module_scope_marker_after_colon(
8379 tokens: &[Token<'_>],
8380 start: usize,
8381 end: usize,
8382) -> Option<&'static str> {
8383 let colon = skip_trivia_tokens(tokens, start, end);
8384 if tokens.get(colon)?.kind != SyntaxKind::Colon {
8385 return None;
8386 }
8387 let scope = next_non_trivia_token_until(tokens, colon + 1, end)?;
8388 if scope.kind != SyntaxKind::Ident {
8389 return None;
8390 }
8391 match scope.text {
8392 "global" => Some("global"),
8393 "local" => Some("local"),
8394 _ => None,
8395 }
8396}
8397
8398fn css_module_scope_marker_is_function(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
8399 let colon = skip_trivia_tokens(tokens, start, end);
8400 let mut index = colon + 1;
8401 let Some(scope) = next_non_trivia_token_until(tokens, index, end) else {
8402 return false;
8403 };
8404 while index < end {
8405 if tokens[index].range == scope.range {
8406 break;
8407 }
8408 index += 1;
8409 }
8410 let Some(next) = next_non_trivia_token_until(tokens, index + 1, end) else {
8411 return false;
8412 };
8413 scope.kind == SyntaxKind::Ident && next.kind == SyntaxKind::LeftParen
8414}
8415
8416fn collect_id_selector_facts_from_header(
8417 tokens: &[Token<'_>],
8418 start: usize,
8419 end: usize,
8420) -> Vec<(String, TextRange)> {
8421 let mut names = Vec::new();
8422 let mut index = start;
8423 let mut paren_depth = 0usize;
8424 let mut bracket_depth = 0usize;
8425 while index < end {
8426 match tokens[index].kind {
8427 SyntaxKind::LeftParen => paren_depth += 1,
8428 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8429 SyntaxKind::LeftBracket => bracket_depth += 1,
8430 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8431 _ => {}
8432 }
8433 let token = tokens[index];
8434 if paren_depth == 0 && bracket_depth == 0 && token.kind == SyntaxKind::Hash {
8435 names.push((token.text.trim_start_matches('#').to_string(), token.range));
8436 }
8437 index += 1;
8438 }
8439 names
8440}
8441
8442fn collect_placeholder_selector_facts_from_header(
8443 tokens: &[Token<'_>],
8444 start: usize,
8445 end: usize,
8446) -> Vec<(String, TextRange)> {
8447 let mut names = Vec::new();
8448 let mut index = start;
8449 let mut paren_depth = 0usize;
8450 let mut bracket_depth = 0usize;
8451 while index < end {
8452 match tokens[index].kind {
8453 SyntaxKind::LeftParen => paren_depth += 1,
8454 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8455 SyntaxKind::LeftBracket => bracket_depth += 1,
8456 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
8457 _ => {}
8458 }
8459 let token = tokens[index];
8460 if paren_depth == 0 && bracket_depth == 0 && token.kind == SyntaxKind::ScssPlaceholder {
8461 names.push((token.text.trim_start_matches('%').to_string(), token.range));
8462 }
8463 index += 1;
8464 }
8465 names
8466}
8467
8468fn collect_variable_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedVariableFact> {
8469 let mut variables = Vec::new();
8470 for (index, token) in tokens.iter().enumerate() {
8471 let kind = match token.kind {
8472 SyntaxKind::ScssVariable => {
8473 if scss_variable_token_is_declaration(tokens, index) {
8474 ParsedVariableFactKind::ScssDeclaration
8475 } else {
8476 ParsedVariableFactKind::ScssReference
8477 }
8478 }
8479 SyntaxKind::LessVariable => {
8480 if next_non_trivia_token(tokens, index + 1)
8481 .is_some_and(|candidate| candidate.kind == SyntaxKind::Colon)
8482 {
8483 ParsedVariableFactKind::LessDeclaration
8484 } else {
8485 ParsedVariableFactKind::LessReference
8486 }
8487 }
8488 SyntaxKind::CustomPropertyName => {
8489 if previous_non_trivia_token(tokens, 0, index).is_some_and(|candidate| {
8490 matches!(candidate.kind, SyntaxKind::Ampersand | SyntaxKind::Dot)
8491 }) {
8492 continue;
8493 }
8494 if let Some(at_rule_name) = containing_at_rule_header_name(tokens, index) {
8495 if at_rule_name == "@property" {
8496 ParsedVariableFactKind::CustomPropertyDeclaration
8497 } else {
8498 continue;
8499 }
8500 } else if next_non_trivia_token(tokens, index + 1)
8501 .is_some_and(|candidate| candidate.kind == SyntaxKind::Colon)
8502 {
8503 ParsedVariableFactKind::CustomPropertyDeclaration
8504 } else {
8505 ParsedVariableFactKind::CustomPropertyReference
8506 }
8507 }
8508 _ => continue,
8509 };
8510 let has_fallback = kind == ParsedVariableFactKind::CustomPropertyReference
8511 && custom_property_reference_has_var_fallback(tokens, index);
8512 variables.push(ParsedVariableFact {
8513 kind,
8514 name: token.text.to_string(),
8515 range: token.range,
8516 has_fallback,
8517 });
8518 }
8519 variables
8520}
8521
8522fn custom_property_reference_has_var_fallback(tokens: &[Token<'_>], index: usize) -> bool {
8529 let Some(open_index) = previous_non_trivia_token_index(tokens, index, 0) else {
8532 return false;
8533 };
8534 if tokens[open_index].kind != SyntaxKind::LeftParen {
8535 return false;
8536 }
8537 let Some(callee_index) = previous_non_trivia_token_index(tokens, open_index, 0) else {
8538 return false;
8539 };
8540 if tokens[callee_index].kind != SyntaxKind::Ident
8541 || !tokens[callee_index].text.eq_ignore_ascii_case("var")
8542 {
8543 return false;
8544 }
8545 let mut depth = 0usize;
8547 let mut cursor = open_index;
8548 while cursor < tokens.len() {
8549 match tokens[cursor].kind {
8550 SyntaxKind::LeftParen => depth += 1,
8551 SyntaxKind::RightParen => {
8552 depth = depth.saturating_sub(1);
8553 if depth == 0 {
8554 return false;
8555 }
8556 }
8557 SyntaxKind::Comma if depth == 1 => return true,
8558 _ => {}
8559 }
8560 cursor += 1;
8561 }
8562 false
8563}
8564
8565fn scss_variable_token_is_declaration(tokens: &[Token<'_>], index: usize) -> bool {
8566 if scss_loop_variable_token_is_binding(tokens, index) {
8567 return true;
8568 }
8569 next_non_trivia_token(tokens, index + 1).is_some_and(|candidate| {
8570 candidate.kind == SyntaxKind::Colon
8571 || (matches!(candidate.kind, SyntaxKind::Comma | SyntaxKind::RightParen)
8572 && containing_at_rule_header_name(tokens, index).is_some_and(|name| {
8573 name.eq_ignore_ascii_case("@mixin") || name.eq_ignore_ascii_case("@function")
8574 }))
8575 })
8576}
8577
8578fn scss_loop_variable_token_is_binding(tokens: &[Token<'_>], index: usize) -> bool {
8587 let Some(header_index) = containing_at_rule_header_index(tokens, index) else {
8588 return false;
8589 };
8590 let separator = match () {
8591 _ if tokens[header_index].text.eq_ignore_ascii_case("@each") => "in",
8592 _ if tokens[header_index].text.eq_ignore_ascii_case("@for") => "from",
8593 _ => return false,
8594 };
8595 let mut paren_depth = 0usize;
8599 for token in &tokens[header_index + 1..index] {
8600 match token.kind {
8601 SyntaxKind::LeftParen => paren_depth += 1,
8602 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8603 SyntaxKind::Ident if paren_depth == 0 && token.text.eq_ignore_ascii_case(separator) => {
8604 return false;
8605 }
8606 _ => {}
8607 }
8608 }
8609 true
8610}
8611
8612fn containing_at_rule_header_index(tokens: &[Token<'_>], index: usize) -> Option<usize> {
8615 let mut current = index;
8616 while current > 0 {
8617 current -= 1;
8618 let token = tokens.get(current)?;
8619 if token.kind.is_trivia() {
8620 continue;
8621 }
8622 if matches!(
8623 token.kind,
8624 SyntaxKind::Semicolon
8625 | SyntaxKind::SassOptionalSemicolon
8626 | SyntaxKind::LeftBrace
8627 | SyntaxKind::RightBrace
8628 | SyntaxKind::SassIndent
8629 | SyntaxKind::SassDedent
8630 ) {
8631 return None;
8632 }
8633 if token.kind == SyntaxKind::AtKeyword {
8634 return Some(current);
8635 }
8636 }
8637 None
8638}
8639
8640fn collect_sass_symbol_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedSassSymbolFact> {
8641 let declared_functions = collect_sass_callable_declaration_names(tokens, "@function");
8642 let mut symbols = Vec::new();
8643
8644 for (index, token) in tokens.iter().enumerate() {
8645 match token.kind {
8646 SyntaxKind::ScssVariable => {
8647 let kind = if scss_variable_token_is_declaration(tokens, index) {
8648 ParsedSassSymbolFactKind::VariableDeclaration
8649 } else {
8650 ParsedSassSymbolFactKind::VariableReference
8651 };
8652 let namespace = (!scss_variable_token_is_declaration(tokens, index))
8653 .then(|| sass_member_namespace_before(tokens, index))
8654 .flatten();
8655 symbols.push(ParsedSassSymbolFact {
8656 kind,
8657 symbol_kind: "variable",
8658 name: token.text.trim_start_matches('$').to_string(),
8659 role: match kind {
8660 ParsedSassSymbolFactKind::VariableDeclaration => "declaration",
8661 _ => "reference",
8662 },
8663 namespace,
8664 range: sass_symbol_variable_range(token, kind),
8665 });
8666 }
8667 SyntaxKind::AtKeyword if token.text.eq_ignore_ascii_case("@mixin") => {
8668 if let Some(name) = sass_callable_name_after_at_rule(tokens, index) {
8669 symbols.push(ParsedSassSymbolFact {
8670 kind: ParsedSassSymbolFactKind::MixinDeclaration,
8671 symbol_kind: "mixin",
8672 name: name.text.to_string(),
8673 role: "declaration",
8674 namespace: None,
8675 range: name.range,
8676 });
8677 }
8678 }
8679 SyntaxKind::AtKeyword if token.text.eq_ignore_ascii_case("@include") => {
8680 if let Some((name, namespace)) = sass_include_name_after_at_rule(tokens, index) {
8681 symbols.push(ParsedSassSymbolFact {
8682 kind: ParsedSassSymbolFactKind::MixinInclude,
8683 symbol_kind: "mixin",
8684 name: name.text.to_string(),
8685 role: "include",
8686 namespace,
8687 range: name.range,
8688 });
8689 }
8690 }
8691 SyntaxKind::AtKeyword if token.text.eq_ignore_ascii_case("@function") => {
8692 if let Some(name) = sass_callable_name_after_at_rule(tokens, index) {
8693 symbols.push(ParsedSassSymbolFact {
8694 kind: ParsedSassSymbolFactKind::FunctionDeclaration,
8695 symbol_kind: "function",
8696 name: name.text.to_string(),
8697 role: "declaration",
8698 namespace: None,
8699 range: name.range,
8700 });
8701 }
8702 }
8703 SyntaxKind::Ident
8704 if (declared_functions.contains(token.text)
8705 || sass_member_namespace_before(tokens, index).is_some())
8706 && next_non_trivia_token(tokens, index + 1)
8707 .is_some_and(|candidate| candidate.kind == SyntaxKind::LeftParen)
8708 && !containing_at_rule_header_name(tokens, index)
8709 .is_some_and(|name| name.eq_ignore_ascii_case("@include"))
8710 && previous_non_trivia_token(tokens, 0, index).is_none_or(|candidate| {
8711 !matches!(candidate.kind, SyntaxKind::AtKeyword)
8712 }) =>
8713 {
8714 symbols.push(ParsedSassSymbolFact {
8715 kind: ParsedSassSymbolFactKind::FunctionCall,
8716 symbol_kind: "function",
8717 name: token.text.to_string(),
8718 role: "call",
8719 namespace: sass_member_namespace_before(tokens, index),
8720 range: token.range,
8721 });
8722 }
8723 _ => {}
8724 }
8725 }
8726
8727 symbols
8728}
8729
8730fn sass_symbol_variable_range(token: &Token<'_>, kind: ParsedSassSymbolFactKind) -> TextRange {
8731 if kind == ParsedSassSymbolFactKind::VariableDeclaration && token.text.starts_with('$') {
8732 let start = u32::from(token.range.start());
8733 let end = u32::from(token.range.end());
8734 if start < end {
8735 return TextRange::new(TextSize::from(start + 1), TextSize::from(end));
8736 }
8737 }
8738 token.range
8739}
8740
8741fn collect_sass_callable_declaration_names(
8742 tokens: &[Token<'_>],
8743 at_keyword: &str,
8744) -> BTreeSet<String> {
8745 tokens
8746 .iter()
8747 .enumerate()
8748 .filter_map(|(index, token)| {
8749 (token.kind == SyntaxKind::AtKeyword && token.text.eq_ignore_ascii_case(at_keyword))
8750 .then(|| sass_callable_name_after_at_rule(tokens, index))
8751 .flatten()
8752 .map(|name| name.text.to_string())
8753 })
8754 .collect()
8755}
8756
8757fn sass_callable_name_after_at_rule<'text>(
8758 tokens: &[Token<'text>],
8759 at_rule_index: usize,
8760) -> Option<Token<'text>> {
8761 let statement_end = css_module_value_statement_end(tokens, at_rule_index + 1);
8762 let name_index = next_non_trivia_token_index_until(tokens, at_rule_index + 1, statement_end)?;
8763 let name = tokens[name_index];
8764 if name.kind != SyntaxKind::Ident {
8765 return None;
8766 }
8767 if next_non_trivia_token_index_until(tokens, name_index + 1, statement_end)
8768 .is_some_and(|next| tokens[next].kind == SyntaxKind::Dot)
8769 {
8770 return None;
8771 }
8772 Some(name)
8773}
8774
8775fn sass_include_name_after_at_rule<'text>(
8776 tokens: &[Token<'text>],
8777 at_rule_index: usize,
8778) -> Option<(Token<'text>, Option<String>)> {
8779 let statement_end = css_module_value_statement_end(tokens, at_rule_index + 1);
8780 let first_index = next_non_trivia_token_index_until(tokens, at_rule_index + 1, statement_end)?;
8781 let first = tokens[first_index];
8782 if first.kind != SyntaxKind::Ident {
8783 return None;
8784 }
8785 let Some(dot_index) = next_non_trivia_token_index_until(tokens, first_index + 1, statement_end)
8786 else {
8787 return Some((first, None));
8788 };
8789 if tokens[dot_index].kind != SyntaxKind::Dot {
8790 return Some((first, None));
8791 }
8792 let member_index = next_non_trivia_token_index_until(tokens, dot_index + 1, statement_end)?;
8793 let member = tokens[member_index];
8794 (member.kind == SyntaxKind::Ident).then(|| (member, Some(first.text.to_string())))
8795}
8796
8797fn sass_member_namespace_before(tokens: &[Token<'_>], member_index: usize) -> Option<String> {
8798 let dot_index = previous_non_trivia_token_index(tokens, member_index, 0)?;
8799 if tokens[dot_index].kind != SyntaxKind::Dot {
8800 return None;
8801 }
8802 let namespace = tokens[previous_non_trivia_token_index(tokens, dot_index, 0)?];
8803 (namespace.kind == SyntaxKind::Ident).then(|| namespace.text.to_string())
8804}
8805
8806fn collect_sass_include_facts_from_tokens(
8807 source: &str,
8808 tokens: &[Token<'_>],
8809) -> Vec<ParsedSassIncludeFact> {
8810 let mut includes = Vec::new();
8811 for (index, token) in tokens.iter().enumerate() {
8812 if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@include") {
8813 continue;
8814 }
8815 let statement_end = css_module_value_statement_end(tokens, index + 1);
8816 let Some((name, namespace)) = sass_include_name_after_at_rule(tokens, index) else {
8817 continue;
8818 };
8819 let header_end = previous_non_trivia_token_index(tokens, statement_end, index + 1)
8820 .map(|previous| tokens[previous].range.end())
8821 .unwrap_or(name.range.end());
8822 let params = source
8823 .get(u32::from(name.range.end()) as usize..u32::from(header_end) as usize)
8824 .unwrap_or_default()
8825 .trim()
8826 .to_string();
8827 includes.push(ParsedSassIncludeFact {
8828 name: name.text.to_string(),
8829 namespace,
8830 params,
8831 range: TextRange::new(token.range.start(), header_end),
8832 });
8833 }
8834 includes
8835}
8836
8837fn collect_sass_module_edge_facts_from_tokens(
8838 tokens: &[Token<'_>],
8839) -> Vec<ParsedSassModuleEdgeFact> {
8840 let mut edges = Vec::new();
8841 let mut seen = BTreeSet::new();
8842
8843 for (index, token) in tokens.iter().enumerate() {
8844 if token.kind != SyntaxKind::AtKeyword {
8845 continue;
8846 }
8847 let Some(kind) = sass_module_edge_kind(token.text) else {
8848 continue;
8849 };
8850 let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
8851 let end = css_module_value_statement_end(tokens, start);
8852 if kind == ParsedSassModuleEdgeFactKind::Import {
8853 collect_sass_import_module_edges(tokens, start, end, &mut edges, &mut seen);
8854 continue;
8855 }
8856 let Some(source_index) = next_non_trivia_token_index_until(tokens, start, end) else {
8857 continue;
8858 };
8859 let source = tokens[source_index];
8860 if !matches!(source.kind, SyntaxKind::String | SyntaxKind::Url) {
8861 continue;
8862 }
8863 let source_name = css_module_value_source_name(source);
8864 let (namespace_kind, namespace) = if kind == ParsedSassModuleEdgeFactKind::Use {
8865 sass_module_use_namespace(tokens, source_name.as_str(), source_index + 1, end)
8866 } else {
8867 (None, None)
8868 };
8869 let (visibility_filter_kind, visibility_filter_names) =
8870 if kind == ParsedSassModuleEdgeFactKind::Forward {
8871 sass_module_forward_visibility_filter(tokens, source_index + 1, end)
8872 } else {
8873 (None, Vec::new())
8874 };
8875 let forward_prefix = if kind == ParsedSassModuleEdgeFactKind::Forward {
8876 sass_module_forward_prefix(tokens, source_index + 1, end)
8877 } else {
8878 None
8879 };
8880 push_sass_module_edge_fact(
8881 &mut edges,
8882 &mut seen,
8883 ParsedSassModuleEdgeFact {
8884 kind,
8885 source: source_name,
8886 namespace_kind,
8887 namespace,
8888 forward_prefix,
8889 visibility_filter_kind,
8890 visibility_filter_names,
8891 media_qualified: false,
8892 range: source.range,
8893 },
8894 );
8895 }
8896
8897 edges
8898}
8899
8900fn sass_module_edge_kind(text: &str) -> Option<ParsedSassModuleEdgeFactKind> {
8901 match text {
8902 text if text.eq_ignore_ascii_case("@use") => Some(ParsedSassModuleEdgeFactKind::Use),
8903 text if text.eq_ignore_ascii_case("@forward") => {
8904 Some(ParsedSassModuleEdgeFactKind::Forward)
8905 }
8906 text if text.eq_ignore_ascii_case("@import") => Some(ParsedSassModuleEdgeFactKind::Import),
8907 _ => None,
8908 }
8909}
8910
8911fn collect_extend_target_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedExtendTargetFact> {
8920 let mut targets = Vec::new();
8921
8922 for (index, token) in tokens.iter().enumerate() {
8923 if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@extend") {
8924 continue;
8925 }
8926 let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
8927 let end = css_module_value_statement_end(tokens, start);
8928
8929 let optional = extend_statement_has_optional_flag(tokens, start, end);
8931
8932 let mut cursor = start;
8934 let mut captured: Option<ParsedExtendTargetFact> = None;
8935 while cursor < end {
8936 let current = tokens[cursor];
8937 if current.kind == SyntaxKind::ScssPlaceholder {
8938 captured = Some(ParsedExtendTargetFact {
8939 kind: ParsedExtendTargetFactKind::Placeholder,
8940 name: current.text.trim_start_matches('%').to_string(),
8941 optional,
8942 range: current.range,
8943 });
8944 break;
8945 }
8946 if current.kind == SyntaxKind::Dot
8947 && let Some(name_index) = next_non_trivia_token_index_until(tokens, cursor + 1, end)
8948 && tokens[name_index].kind == SyntaxKind::Ident
8949 {
8950 let name_token = tokens[name_index];
8951 let range = TextRange::new(current.range.start(), name_token.range.end());
8952 captured = Some(ParsedExtendTargetFact {
8953 kind: ParsedExtendTargetFactKind::Class,
8954 name: name_token.text.to_string(),
8955 optional,
8956 range,
8957 });
8958 break;
8959 }
8960 cursor += 1;
8961 }
8962
8963 if let Some(target) = captured {
8964 targets.push(target);
8965 }
8966 }
8967
8968 targets
8969}
8970
8971fn extend_statement_has_optional_flag(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
8975 let mut index = start;
8976 while index < end {
8977 if tokens[index].kind == SyntaxKind::Delim
8978 && tokens[index].text == "!"
8979 && let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
8980 && tokens[next_index].kind == SyntaxKind::Ident
8981 && tokens[next_index].text.eq_ignore_ascii_case("optional")
8982 {
8983 return true;
8984 }
8985 index += 1;
8986 }
8987 false
8988}
8989
8990fn collect_sass_import_module_edges(
8991 tokens: &[Token<'_>],
8992 start: usize,
8993 end: usize,
8994 edges: &mut Vec<ParsedSassModuleEdgeFact>,
8995 seen: &mut BTreeSet<(ParsedSassModuleEdgeFactKind, String, u32, u32)>,
8996) {
8997 for index in start..end {
8998 let token = tokens[index];
8999 if !matches!(token.kind, SyntaxKind::String | SyntaxKind::Url) {
9000 continue;
9001 }
9002 let media_qualified = next_non_trivia_token_index_until(tokens, index + 1, end)
9008 .is_some_and(|next| tokens[next].kind != SyntaxKind::Comma);
9009 push_sass_module_edge_fact(
9010 edges,
9011 seen,
9012 ParsedSassModuleEdgeFact {
9013 kind: ParsedSassModuleEdgeFactKind::Import,
9014 source: css_module_value_source_name(token),
9015 namespace_kind: None,
9016 namespace: None,
9017 forward_prefix: None,
9018 visibility_filter_kind: None,
9019 visibility_filter_names: Vec::new(),
9020 media_qualified,
9021 range: token.range,
9022 },
9023 );
9024 }
9025}
9026
9027fn sass_module_use_namespace(
9028 tokens: &[Token<'_>],
9029 source: &str,
9030 start: usize,
9031 end: usize,
9032) -> (Option<&'static str>, Option<String>) {
9033 let Some(as_index) = top_level_token_text_index(tokens, start, end, "as") else {
9034 return (
9035 Some("default"),
9036 sass_module_default_namespace(source).map(str::to_string),
9037 );
9038 };
9039 let Some(namespace_index) = next_non_trivia_token_index_until(tokens, as_index + 1, end) else {
9040 return (Some("invalid"), None);
9041 };
9042 let namespace = tokens[namespace_index];
9043 match namespace.kind {
9044 SyntaxKind::Star => (Some("wildcard"), None),
9045 SyntaxKind::Ident => (Some("alias"), Some(namespace.text.to_string())),
9046 _ => (Some("invalid"), None),
9047 }
9048}
9049
9050fn sass_module_forward_prefix(tokens: &[Token<'_>], start: usize, end: usize) -> Option<String> {
9051 let as_index = top_level_token_text_index(tokens, start, end, "as")?;
9052 let prefix_index = next_non_trivia_token_index_until(tokens, as_index + 1, end)?;
9053 let prefix = tokens[prefix_index].text.trim();
9054 if prefix.is_empty() {
9055 return None;
9056 }
9057 Some(prefix.to_string())
9058}
9059
9060fn sass_module_forward_visibility_filter(
9061 tokens: &[Token<'_>],
9062 start: usize,
9063 end: usize,
9064) -> (Option<&'static str>, Vec<String>) {
9065 let show_index = top_level_token_text_index(tokens, start, end, "show");
9066 let hide_index = top_level_token_text_index(tokens, start, end, "hide");
9067 let (filter_kind, filter_index) = match (show_index, hide_index) {
9068 (Some(show_index), Some(hide_index)) if show_index <= hide_index => ("show", show_index),
9069 (Some(_), Some(hide_index)) => ("hide", hide_index),
9070 (Some(show_index), None) => ("show", show_index),
9071 (None, Some(hide_index)) => ("hide", hide_index),
9072 (None, None) => return (None, Vec::new()),
9073 };
9074 let clause_end =
9075 top_level_token_text_index(tokens, filter_index + 1, end, "with").unwrap_or(end);
9076 (
9077 Some(filter_kind),
9078 sass_module_visibility_filter_names(tokens, filter_index + 1, clause_end),
9079 )
9080}
9081
9082fn sass_module_visibility_filter_names(
9083 tokens: &[Token<'_>],
9084 start: usize,
9085 end: usize,
9086) -> Vec<String> {
9087 let mut names = BTreeSet::new();
9088 for token in &tokens[start..end] {
9089 match token.kind {
9090 SyntaxKind::Ident | SyntaxKind::ScssVariable => {
9091 if matches_ignore_ascii_case(token.text, &["show", "hide", "with", "as"]) {
9092 continue;
9093 }
9094 let name = token.text.trim_start_matches('$');
9095 if !name.is_empty() {
9096 names.insert(name.to_string());
9097 }
9098 }
9099 _ => {}
9100 }
9101 }
9102 names.into_iter().collect()
9103}
9104
9105fn sass_module_default_namespace(source: &str) -> Option<&str> {
9106 let basename = source
9107 .rsplit(['/', '\\', ':'])
9108 .next()
9109 .unwrap_or(source)
9110 .trim_start_matches('_');
9111 let namespace = basename.split('.').next().unwrap_or(basename);
9112 (!namespace.is_empty()).then_some(namespace)
9113}
9114
9115fn push_sass_module_edge_fact(
9116 edges: &mut Vec<ParsedSassModuleEdgeFact>,
9117 seen: &mut BTreeSet<(ParsedSassModuleEdgeFactKind, String, u32, u32)>,
9118 edge: ParsedSassModuleEdgeFact,
9119) {
9120 let start: u32 = edge.range.start().into();
9121 let end: u32 = edge.range.end().into();
9122 if seen.insert((edge.kind, edge.source.clone(), start, end)) {
9123 edges.push(edge);
9124 }
9125}
9126
9127fn collect_css_module_value_facts_from_tokens(
9128 tokens: &[Token<'_>],
9129) -> Vec<ParsedCssModuleValueFact> {
9130 let mut values = Vec::new();
9131 let mut seen = BTreeSet::new();
9132 let value_path_aliases = collect_css_module_value_path_aliases_from_tokens(tokens);
9133 for (index, token) in tokens.iter().enumerate() {
9134 if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9135 continue;
9136 }
9137
9138 let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9139 let end = css_module_value_statement_end(tokens, start);
9140 let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9141 let from_index = top_level_token_text_index(tokens, start, end, "from");
9142
9143 if let Some(from_index) = from_index
9144 && match colon_index {
9145 Some(colon_index) => from_index < colon_index,
9146 None => true,
9147 }
9148 {
9149 collect_css_module_value_import_facts(
9150 tokens,
9151 start,
9152 from_index,
9153 end,
9154 &value_path_aliases,
9155 &mut values,
9156 &mut seen,
9157 );
9158 continue;
9159 }
9160
9161 if let Some(colon_index) = colon_index {
9162 if css_module_value_path_alias_from_tokens(tokens, start, colon_index, end).is_some() {
9163 continue;
9164 }
9165 collect_css_module_value_definition_facts(
9166 tokens,
9167 start,
9168 colon_index,
9169 &mut values,
9170 &mut seen,
9171 );
9172 collect_css_module_value_reference_facts(
9173 tokens,
9174 colon_index + 1,
9175 end,
9176 &mut values,
9177 &mut seen,
9178 );
9179 } else {
9180 collect_css_module_value_definition_facts(tokens, start, end, &mut values, &mut seen);
9181 }
9182 }
9183 let local_value_names = values
9184 .iter()
9185 .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
9186 .map(|value| value.name.clone())
9187 .collect::<BTreeSet<_>>();
9188 collect_css_module_value_declaration_reference_facts(
9189 tokens,
9190 0,
9191 tokens.len(),
9192 &local_value_names,
9193 &mut values,
9194 &mut seen,
9195 );
9196 values
9197}
9198
9199fn collect_css_module_value_path_aliases_from_tokens(
9200 tokens: &[Token<'_>],
9201) -> BTreeMap<String, String> {
9202 let mut aliases = BTreeMap::new();
9203 for (index, token) in tokens.iter().enumerate() {
9204 if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9205 continue;
9206 }
9207
9208 let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9209 let end = css_module_value_statement_end(tokens, start);
9210 let Some(colon_index) = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon)
9211 else {
9212 continue;
9213 };
9214 if top_level_token_text_index(tokens, start, end, "from").is_some() {
9215 continue;
9216 }
9217 if let Some((name, target)) =
9218 css_module_value_path_alias_from_tokens(tokens, start, colon_index, end)
9219 {
9220 aliases.insert(name, target);
9221 }
9222 }
9223 aliases
9224}
9225
9226fn css_module_value_path_alias_from_tokens(
9227 tokens: &[Token<'_>],
9228 start: usize,
9229 colon_index: usize,
9230 end: usize,
9231) -> Option<(String, String)> {
9232 let name_index = next_non_trivia_token_index_until(tokens, start, colon_index)?;
9233 let name_token = tokens[name_index];
9234 if !css_module_value_name_token_can_define(name_token) {
9235 return None;
9236 }
9237 let source_index = next_non_trivia_token_index_until(tokens, colon_index + 1, end)?;
9238 let source_token = tokens[source_index];
9239 if !matches!(source_token.kind, SyntaxKind::String | SyntaxKind::Url) {
9240 return None;
9241 }
9242 let source = css_module_value_source_name(source_token);
9243 css_module_value_source_looks_like_style_request(&source)
9244 .then(|| (name_token.text.to_string(), source))
9245}
9246
9247fn css_module_value_statement_end(tokens: &[Token<'_>], start: usize) -> usize {
9248 let mut index = start;
9249 let mut paren_depth = 0usize;
9250 let mut bracket_depth = 0usize;
9251 while index < tokens.len() {
9252 match tokens[index].kind {
9253 SyntaxKind::LeftParen => paren_depth += 1,
9254 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9255 SyntaxKind::LeftBracket => bracket_depth += 1,
9256 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9257 SyntaxKind::Semicolon
9258 | SyntaxKind::SassOptionalSemicolon
9259 | SyntaxKind::LeftBrace
9260 | SyntaxKind::RightBrace
9261 | SyntaxKind::SassIndent
9262 | SyntaxKind::SassDedent
9263 if paren_depth == 0 && bracket_depth == 0 =>
9264 {
9265 return index;
9266 }
9267 _ => {}
9268 }
9269 index += 1;
9270 }
9271 index
9272}
9273
9274fn collect_css_module_value_import_facts(
9275 tokens: &[Token<'_>],
9276 start: usize,
9277 from_index: usize,
9278 end: usize,
9279 value_path_aliases: &BTreeMap<String, String>,
9280 values: &mut Vec<ParsedCssModuleValueFact>,
9281 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9282) {
9283 collect_css_module_value_import_names(tokens, start, from_index, values, seen);
9284 if let Some((source_name, source_range)) =
9285 css_module_value_import_edge_source(tokens, from_index + 1, end, value_path_aliases)
9286 {
9287 push_css_module_value_fact(
9288 values,
9289 seen,
9290 ParsedCssModuleValueFactKind::ImportSource,
9291 source_name,
9292 source_range,
9293 );
9294 }
9295}
9296
9297fn collect_css_module_value_import_edge_facts_from_tokens(
9298 tokens: &[Token<'_>],
9299) -> Vec<ParsedCssModuleValueImportEdgeFact> {
9300 let mut edges = Vec::new();
9301 let value_path_aliases = collect_css_module_value_path_aliases_from_tokens(tokens);
9302 for (index, token) in tokens.iter().enumerate() {
9303 if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9304 continue;
9305 }
9306
9307 let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9308 let end = css_module_value_statement_end(tokens, start);
9309 let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9310 let from_index = top_level_token_text_index(tokens, start, end, "from");
9311 let Some(from_index) = from_index else {
9312 continue;
9313 };
9314 if colon_index.is_some_and(|colon_index| from_index > colon_index) {
9315 continue;
9316 }
9317 let Some((import_source, _source_range)) =
9318 css_module_value_import_edge_source(tokens, from_index + 1, end, &value_path_aliases)
9319 else {
9320 continue;
9321 };
9322
9323 collect_css_module_value_import_edges(tokens, start, from_index, import_source, &mut edges);
9324 }
9325 edges
9326}
9327
9328fn collect_css_module_value_definition_edge_facts_from_tokens(
9329 tokens: &[Token<'_>],
9330) -> Vec<ParsedCssModuleValueDefinitionEdgeFact> {
9331 let mut edges = Vec::new();
9332 for (index, token) in tokens.iter().enumerate() {
9333 if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9334 continue;
9335 }
9336
9337 let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9338 let end = css_module_value_statement_end(tokens, start);
9339 let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9340 let from_index = top_level_token_text_index(tokens, start, end, "from");
9341 let Some(colon_index) = colon_index else {
9342 continue;
9343 };
9344 if from_index.is_some_and(|from_index| from_index < colon_index) {
9345 continue;
9346 }
9347
9348 let definition_names = collect_css_module_value_definition_edge_names(
9349 tokens,
9350 start,
9351 colon_index,
9352 |tokens, index| css_module_value_name_token_can_define(tokens[index]),
9353 );
9354 let reference_names = collect_css_module_value_definition_edge_names(
9355 tokens,
9356 colon_index + 1,
9357 end,
9358 css_module_value_reference_token_can_be_name,
9359 );
9360 if reference_names.is_empty() {
9361 continue;
9362 }
9363 let range_end = end
9364 .checked_sub(1)
9365 .and_then(|end| tokens.get(end))
9366 .map(|token| token.range.end())
9367 .unwrap_or_else(|| tokens[index].range.end());
9368
9369 for definition_name in definition_names {
9370 edges.push(ParsedCssModuleValueDefinitionEdgeFact {
9371 definition_name,
9372 reference_names: reference_names.clone(),
9373 range: TextRange::new(tokens[index].range.start(), range_end),
9374 });
9375 }
9376 }
9377 edges
9378}
9379
9380fn collect_css_module_value_definition_edge_names(
9381 tokens: &[Token<'_>],
9382 start: usize,
9383 end: usize,
9384 predicate: impl Fn(&[Token<'_>], usize) -> bool,
9385) -> Vec<String> {
9386 let mut names = Vec::new();
9387 let mut index = start;
9388 while index < end {
9389 if predicate(tokens, index) && !names.iter().any(|name| name == tokens[index].text) {
9390 names.push(tokens[index].text.to_string());
9391 }
9392 index += 1;
9393 }
9394 names
9395}
9396
9397fn css_module_value_import_edge_source(
9398 tokens: &[Token<'_>],
9399 start: usize,
9400 end: usize,
9401 value_path_aliases: &BTreeMap<String, String>,
9402) -> Option<(String, TextRange)> {
9403 let source_index = next_non_trivia_token_index_until(tokens, start, end)?;
9404 let token = tokens[source_index];
9405 if matches!(token.kind, SyntaxKind::String | SyntaxKind::Url) {
9406 return Some((css_module_value_source_name(token), token.range));
9407 }
9408 if css_module_value_name_token_can_define(token) {
9409 return css_module_value_source_alias_target(token.text, token.range, value_path_aliases);
9410 }
9411 None
9412}
9413
9414fn css_module_value_source_alias_target(
9415 name: &str,
9416 range: TextRange,
9417 value_path_aliases: &BTreeMap<String, String>,
9418) -> Option<(String, TextRange)> {
9419 value_path_aliases
9420 .get(name)
9421 .map(|source| (source.clone(), range))
9422}
9423
9424fn collect_css_module_value_import_edges(
9425 tokens: &[Token<'_>],
9426 start: usize,
9427 end: usize,
9428 import_source: String,
9429 edges: &mut Vec<ParsedCssModuleValueImportEdgeFact>,
9430) {
9431 let mut index = start;
9432 while index < end {
9433 let token = tokens[index];
9434 if !css_module_value_name_token_can_define(token) {
9435 index += 1;
9436 continue;
9437 }
9438 if previous_non_trivia_token_index(tokens, index, start)
9439 .is_some_and(|previous| tokens[previous].text == "as")
9440 {
9441 index += 1;
9442 continue;
9443 }
9444 let remote_name = token.text.to_string();
9445 let mut local_name = remote_name.clone();
9446 let mut local_range = token.range;
9447 if let Some(as_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
9448 && tokens[as_index].text == "as"
9449 && let Some(local_index) = next_non_trivia_token_index_until(tokens, as_index + 1, end)
9450 && css_module_value_name_token_can_define(tokens[local_index])
9451 {
9452 local_name = tokens[local_index].text.to_string();
9453 local_range = tokens[local_index].range;
9454 index = local_index + 1;
9455 } else {
9456 index += 1;
9457 }
9458 edges.push(ParsedCssModuleValueImportEdgeFact {
9459 remote_name,
9460 local_name,
9461 import_source: import_source.clone(),
9462 local_range,
9463 remote_range: token.range,
9464 range: token.range,
9465 });
9466 }
9467}
9468
9469fn collect_css_module_value_import_names(
9470 tokens: &[Token<'_>],
9471 start: usize,
9472 end: usize,
9473 values: &mut Vec<ParsedCssModuleValueFact>,
9474 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9475) {
9476 let mut index = start;
9477 while index < end {
9478 let token = tokens[index];
9479 if css_module_value_name_token_can_define(token) {
9480 let previous = previous_non_trivia_token_index(tokens, index, start);
9481 let next = next_non_trivia_token_index_until(tokens, index + 1, end);
9482 let kind = if previous.is_some_and(|previous| tokens[previous].text == "as") {
9483 Some(ParsedCssModuleValueFactKind::Definition)
9484 } else if next.is_some_and(|next| tokens[next].text == "as") {
9485 Some(ParsedCssModuleValueFactKind::Reference)
9486 } else {
9487 Some(ParsedCssModuleValueFactKind::Definition)
9488 };
9489 if let Some(kind) = kind {
9490 push_css_module_value_fact(values, seen, kind, token.text.to_string(), token.range);
9491 }
9492 }
9493 index += 1;
9494 }
9495}
9496
9497fn collect_css_module_value_definition_facts(
9498 tokens: &[Token<'_>],
9499 start: usize,
9500 end: usize,
9501 values: &mut Vec<ParsedCssModuleValueFact>,
9502 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9503) {
9504 let mut index = start;
9505 while index < end {
9506 let token = tokens[index];
9507 if css_module_value_name_token_can_define(token) {
9508 push_css_module_value_fact(
9509 values,
9510 seen,
9511 ParsedCssModuleValueFactKind::Definition,
9512 token.text.to_string(),
9513 token.range,
9514 );
9515 }
9516 index += 1;
9517 }
9518}
9519
9520fn collect_css_module_value_reference_facts(
9521 tokens: &[Token<'_>],
9522 start: usize,
9523 end: usize,
9524 values: &mut Vec<ParsedCssModuleValueFact>,
9525 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9526) {
9527 let mut index = start;
9528 let mut paren_depth = 0usize;
9529 let mut bracket_depth = 0usize;
9530 while index < end {
9531 match tokens[index].kind {
9532 SyntaxKind::LeftParen => paren_depth += 1,
9533 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9534 SyntaxKind::LeftBracket => bracket_depth += 1,
9535 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9536 _ => {}
9537 }
9538 if paren_depth == 0
9539 && bracket_depth == 0
9540 && css_module_value_reference_token_can_be_name(tokens, index)
9541 {
9542 push_css_module_value_fact(
9543 values,
9544 seen,
9545 ParsedCssModuleValueFactKind::Reference,
9546 tokens[index].text.to_string(),
9547 tokens[index].range,
9548 );
9549 }
9550 index += 1;
9551 }
9552}
9553
9554fn collect_css_module_value_declaration_reference_facts(
9555 tokens: &[Token<'_>],
9556 start: usize,
9557 end: usize,
9558 local_value_names: &BTreeSet<String>,
9559 values: &mut Vec<ParsedCssModuleValueFact>,
9560 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9561) {
9562 if local_value_names.is_empty() {
9563 return;
9564 }
9565
9566 let mut index = start;
9567 while index < end {
9568 index = skip_trivia_tokens(tokens, index, end);
9569 if index >= end {
9570 break;
9571 }
9572
9573 if tokens[index].kind == SyntaxKind::AtKeyword {
9574 let block = find_block_after_header(tokens, index, end);
9575 if let Some((open, close)) = block {
9576 if style_wrapper_at_rule(tokens[index].text) {
9577 collect_css_module_value_declaration_reference_facts(
9578 tokens,
9579 open + 1,
9580 close,
9581 local_value_names,
9582 values,
9583 seen,
9584 );
9585 }
9586 index = close + 1;
9587 } else {
9588 index = skip_statement(tokens, index, end);
9589 }
9590 continue;
9591 }
9592
9593 let statement_end = css_module_value_statement_end(tokens, index);
9594 if statement_end < end && tokens[statement_end].kind == SyntaxKind::LeftBrace {
9595 if let Some(close) = matching_right_brace(tokens, statement_end, end) {
9596 collect_css_module_value_declaration_reference_facts(
9597 tokens,
9598 statement_end + 1,
9599 close,
9600 local_value_names,
9601 values,
9602 seen,
9603 );
9604 index = close + 1;
9605 } else {
9606 index = statement_end + 1;
9607 }
9608 continue;
9609 }
9610
9611 if let Some(colon_index) = declaration_colon_index(tokens, index, statement_end.min(end)) {
9612 collect_known_css_module_value_reference_facts(
9613 tokens,
9614 colon_index + 1,
9615 statement_end.min(end),
9616 local_value_names,
9617 values,
9618 seen,
9619 );
9620 }
9621
9622 if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
9623 break;
9624 }
9625 index = statement_end + 1;
9626 }
9627}
9628
9629fn declaration_colon_index(tokens: &[Token<'_>], start: usize, end: usize) -> Option<usize> {
9630 let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon)?;
9631 let property_index = previous_non_trivia_token_index(tokens, colon_index, start)?;
9632 if !matches!(
9633 tokens[property_index].kind,
9634 SyntaxKind::Ident
9635 | SyntaxKind::CustomPropertyName
9636 | SyntaxKind::ScssVariable
9637 | SyntaxKind::LessVariable
9638 | SyntaxKind::LessPropertyVariableToken
9639 ) {
9640 return None;
9641 }
9642 let value_index = next_non_trivia_token_index_until(tokens, colon_index + 1, end)?;
9643 if matches!(
9644 tokens[value_index].kind,
9645 SyntaxKind::LeftBrace | SyntaxKind::LeftParen | SyntaxKind::LeftBracket
9646 ) {
9647 return None;
9648 }
9649 Some(colon_index)
9650}
9651
9652fn collect_known_css_module_value_reference_facts(
9653 tokens: &[Token<'_>],
9654 start: usize,
9655 end: usize,
9656 local_value_names: &BTreeSet<String>,
9657 values: &mut Vec<ParsedCssModuleValueFact>,
9658 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9659) {
9660 let mut index = start;
9661 let mut paren_depth = 0usize;
9662 let mut bracket_depth = 0usize;
9663 while index < end {
9664 match tokens[index].kind {
9665 SyntaxKind::LeftParen => paren_depth += 1,
9666 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9667 SyntaxKind::LeftBracket => bracket_depth += 1,
9668 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9669 _ => {}
9670 }
9671 if paren_depth == 0
9672 && bracket_depth == 0
9673 && css_module_value_reference_token_can_be_name(tokens, index)
9674 && local_value_names.contains(tokens[index].text)
9675 {
9676 push_css_module_value_fact(
9677 values,
9678 seen,
9679 ParsedCssModuleValueFactKind::Reference,
9680 tokens[index].text.to_string(),
9681 tokens[index].range,
9682 );
9683 }
9684 index += 1;
9685 }
9686}
9687
9688fn push_css_module_value_fact(
9689 values: &mut Vec<ParsedCssModuleValueFact>,
9690 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9691 kind: ParsedCssModuleValueFactKind,
9692 name: String,
9693 range: TextRange,
9694) {
9695 if seen.insert((
9696 kind,
9697 name.clone(),
9698 u32::from(range.start()),
9699 u32::from(range.end()),
9700 )) {
9701 values.push(ParsedCssModuleValueFact { kind, name, range });
9702 }
9703}
9704
9705fn top_level_token_kind_index(
9706 tokens: &[Token<'_>],
9707 start: usize,
9708 end: usize,
9709 expected: SyntaxKind,
9710) -> Option<usize> {
9711 let mut index = start;
9712 let mut paren_depth = 0usize;
9713 let mut bracket_depth = 0usize;
9714 while index < end {
9715 match tokens[index].kind {
9716 SyntaxKind::LeftParen => paren_depth += 1,
9717 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9718 SyntaxKind::LeftBracket => bracket_depth += 1,
9719 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9720 kind if kind == expected && paren_depth == 0 && bracket_depth == 0 => {
9721 return Some(index);
9722 }
9723 _ => {}
9724 }
9725 index += 1;
9726 }
9727 None
9728}
9729
9730fn top_level_token_text_index(
9731 tokens: &[Token<'_>],
9732 start: usize,
9733 end: usize,
9734 expected: &str,
9735) -> Option<usize> {
9736 let mut index = start;
9737 let mut paren_depth = 0usize;
9738 let mut bracket_depth = 0usize;
9739 while index < end {
9740 match tokens[index].kind {
9741 SyntaxKind::LeftParen => paren_depth += 1,
9742 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9743 SyntaxKind::LeftBracket => bracket_depth += 1,
9744 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9745 SyntaxKind::Ident
9746 if paren_depth == 0
9747 && bracket_depth == 0
9748 && tokens[index].text.eq_ignore_ascii_case(expected) =>
9749 {
9750 return Some(index);
9751 }
9752 _ => {}
9753 }
9754 index += 1;
9755 }
9756 None
9757}
9758
9759fn previous_non_trivia_token_index(
9760 tokens: &[Token<'_>],
9761 mut index: usize,
9762 start: usize,
9763) -> Option<usize> {
9764 while index > start {
9765 index -= 1;
9766 if !tokens[index].kind.is_trivia() {
9767 return Some(index);
9768 }
9769 }
9770 None
9771}
9772
9773fn css_module_value_name_token_can_define(token: Token<'_>) -> bool {
9774 matches!(
9775 token.kind,
9776 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9777 ) && !matches!(token.text, "as" | "from")
9778}
9779
9780fn css_module_value_reference_token_can_be_name(tokens: &[Token<'_>], index: usize) -> bool {
9781 let token = tokens[index];
9782 if !matches!(
9783 token.kind,
9784 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9785 ) {
9786 return false;
9787 }
9788 if let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9789 && tokens[next_index].kind == SyntaxKind::LeftParen
9790 {
9791 return false;
9792 }
9793 !css_module_value_literal_ident_is_not_reference(token.text)
9794}
9795
9796fn css_module_value_literal_ident_is_not_reference(name: &str) -> bool {
9797 matches!(
9798 name.to_ascii_lowercase().as_str(),
9799 "initial"
9800 | "inherit"
9801 | "unset"
9802 | "revert"
9803 | "revert-layer"
9804 | "none"
9805 | "auto"
9806 | "normal"
9807 | "transparent"
9808 | "currentcolor"
9809 | "black"
9810 | "white"
9811 | "red"
9812 | "green"
9813 | "blue"
9814 | "yellow"
9815 | "magenta"
9816 | "cyan"
9817 | "solid"
9818 | "dashed"
9819 | "block"
9820 | "inline"
9821 | "flex"
9822 | "grid"
9823 )
9824}
9825
9826fn css_module_value_source_name(token: Token<'_>) -> String {
9827 token
9828 .text
9829 .trim_matches(|character| character == '"' || character == '\'')
9830 .to_string()
9831}
9832
9833fn css_module_value_source_looks_like_style_request(source: &str) -> bool {
9834 let lower = source.to_ascii_lowercase();
9835 (lower.starts_with('/') || lower.starts_with("./") || lower.starts_with("../"))
9836 && (lower.ends_with(".css")
9837 || lower.ends_with(".scss")
9838 || lower.ends_with(".sass")
9839 || lower.ends_with(".less"))
9840}
9841
9842fn collect_css_module_composes_facts_from_tokens(
9843 tokens: &[Token<'_>],
9844) -> Vec<ParsedCssModuleComposesFact> {
9845 let mut composes = Vec::new();
9846 let mut seen = BTreeSet::new();
9847 for (index, token) in tokens.iter().enumerate() {
9848 if token.kind != SyntaxKind::Ident || !token.text.eq_ignore_ascii_case("composes") {
9849 continue;
9850 }
9851 let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9852 else {
9853 continue;
9854 };
9855 if tokens[colon_index].kind != SyntaxKind::Colon {
9856 continue;
9857 }
9858
9859 let start = colon_index + 1;
9860 let end = css_module_value_statement_end(tokens, start);
9861 let from_index = top_level_token_text_index(tokens, start, end, "from");
9862 let target_end = from_index.unwrap_or(end);
9863 collect_css_module_composes_targets(tokens, start, target_end, &mut composes, &mut seen);
9864 if let Some(from_index) = from_index {
9865 collect_css_module_composes_import_source(
9866 tokens,
9867 from_index + 1,
9868 end,
9869 &mut composes,
9870 &mut seen,
9871 );
9872 }
9873 }
9874 composes
9875}
9876
9877fn collect_css_module_composes_edge_facts_from_tokens(
9878 tokens: &[Token<'_>],
9879) -> Vec<ParsedCssModuleComposesEdgeFact> {
9880 let mut edges = Vec::new();
9881 collect_css_module_composes_edge_facts_in_range(tokens, 0, tokens.len(), &[], None, &mut edges);
9882 edges
9883}
9884
9885fn collect_css_module_composes_edge_facts_in_range(
9886 tokens: &[Token<'_>],
9887 start: usize,
9888 end: usize,
9889 parent_branches: &[SelectorBranch],
9890 css_module_scope: Option<&'static str>,
9891 edges: &mut Vec<ParsedCssModuleComposesEdgeFact>,
9892) {
9893 let mut index = start;
9894 while index < end {
9895 index = skip_trivia_tokens(tokens, index, end);
9896 if index >= end {
9897 break;
9898 }
9899
9900 if tokens[index].kind == SyntaxKind::AtKeyword {
9901 let block = find_block_after_header(tokens, index, end);
9902 if let Some((open, close)) = block {
9903 if tokens[index].text == "@nest" {
9904 if css_module_scope == Some("global") {
9905 collect_css_module_composes_edge_facts_in_range(
9906 tokens,
9907 open + 1,
9908 close,
9909 &[],
9910 css_module_scope,
9911 edges,
9912 );
9913 } else {
9914 let branches =
9915 resolve_selector_header(tokens, index + 1, open, parent_branches);
9916 collect_immediate_css_module_composes_edge_facts(
9917 tokens,
9918 open + 1,
9919 close,
9920 &branches,
9921 edges,
9922 );
9923 collect_css_module_composes_edge_facts_in_range(
9924 tokens,
9925 open + 1,
9926 close,
9927 &branches,
9928 css_module_scope,
9929 edges,
9930 );
9931 }
9932 } else if style_wrapper_at_rule(tokens[index].text) {
9933 collect_css_module_composes_edge_facts_in_range(
9934 tokens,
9935 open + 1,
9936 close,
9937 parent_branches,
9938 css_module_scope,
9939 edges,
9940 );
9941 }
9942 index = close + 1;
9943 } else {
9944 index = skip_statement(tokens, index, end);
9945 }
9946 continue;
9947 }
9948
9949 let Some((open, close)) = find_block_after_header(tokens, index, end) else {
9950 index = skip_statement(tokens, index, end);
9951 continue;
9952 };
9953
9954 let effective_scope = css_module_scope
9955 .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
9956 if effective_scope == Some("global") {
9957 collect_css_module_composes_edge_facts_in_range(
9958 tokens,
9959 open + 1,
9960 close,
9961 &[],
9962 effective_scope,
9963 edges,
9964 );
9965 } else {
9966 let branches = resolve_selector_header(tokens, index, open, parent_branches);
9967 collect_immediate_css_module_composes_edge_facts(
9968 tokens,
9969 open + 1,
9970 close,
9971 &branches,
9972 edges,
9973 );
9974 collect_css_module_composes_edge_facts_in_range(
9975 tokens,
9976 open + 1,
9977 close,
9978 &branches,
9979 effective_scope,
9980 edges,
9981 );
9982 }
9983 index = close + 1;
9984 }
9985}
9986
9987fn collect_immediate_css_module_composes_edge_facts(
9988 tokens: &[Token<'_>],
9989 start: usize,
9990 end: usize,
9991 owner_branches: &[SelectorBranch],
9992 edges: &mut Vec<ParsedCssModuleComposesEdgeFact>,
9993) {
9994 let owner_selector_names = sorted_selector_branch_names(owner_branches);
9995 let mut index = start;
9996 let mut block_depth = 0usize;
9997 while index < end {
9998 match tokens[index].kind {
9999 SyntaxKind::LeftBrace | SyntaxKind::SassIndent => {
10000 block_depth += 1;
10001 index += 1;
10002 continue;
10003 }
10004 SyntaxKind::RightBrace | SyntaxKind::SassDedent => {
10005 block_depth = block_depth.saturating_sub(1);
10006 index += 1;
10007 continue;
10008 }
10009 _ => {}
10010 }
10011 if block_depth > 0
10012 || tokens[index].kind != SyntaxKind::Ident
10013 || !tokens[index].text.eq_ignore_ascii_case("composes")
10014 {
10015 index += 1;
10016 continue;
10017 }
10018 let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end) else {
10019 index += 1;
10020 continue;
10021 };
10022 if tokens[colon_index].kind != SyntaxKind::Colon {
10023 index += 1;
10024 continue;
10025 }
10026
10027 let value_start = colon_index + 1;
10028 let value_end = css_module_value_statement_end(tokens, value_start).min(end);
10029 let from_index = top_level_token_text_index(tokens, value_start, value_end, "from");
10030 let target_end = from_index.unwrap_or(value_end);
10031 let target_names =
10032 collect_css_module_composes_target_names(tokens, value_start, target_end);
10033 if target_names.is_empty() {
10034 index = value_end;
10035 continue;
10036 }
10037
10038 let (kind, import_source) = from_index
10039 .and_then(|from_index| {
10040 css_module_composes_import_edge_source(tokens, from_index + 1, value_end)
10041 })
10042 .map(|source| {
10043 if source == "global" {
10044 (ParsedCssModuleComposesEdgeKind::Global, Some(source))
10045 } else {
10046 (ParsedCssModuleComposesEdgeKind::External, Some(source))
10047 }
10048 })
10049 .unwrap_or((ParsedCssModuleComposesEdgeKind::Local, None));
10050 let range_end = value_end
10051 .checked_sub(1)
10052 .and_then(|end| tokens.get(end))
10053 .map(|token| token.range.end())
10054 .unwrap_or_else(|| tokens[index].range.end());
10055
10056 edges.push(ParsedCssModuleComposesEdgeFact {
10057 kind,
10058 owner_selector_names: owner_selector_names.clone(),
10059 target_names,
10060 import_source,
10061 range: TextRange::new(tokens[index].range.start(), range_end),
10062 });
10063 index = value_end;
10064 }
10065}
10066
10067fn sorted_selector_branch_names(branches: &[SelectorBranch]) -> Vec<String> {
10068 branches
10069 .iter()
10070 .map(|branch| branch.name.clone())
10071 .collect::<BTreeSet<_>>()
10072 .into_iter()
10073 .collect()
10074}
10075
10076fn collect_css_module_composes_target_names(
10077 tokens: &[Token<'_>],
10078 start: usize,
10079 end: usize,
10080) -> Vec<String> {
10081 let mut names = Vec::new();
10082 let mut index = start;
10083 while index < end {
10084 if matches!(
10085 tokens[index].kind,
10086 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10087 ) && !tokens[index].text.eq_ignore_ascii_case("from")
10088 && !names.iter().any(|name| name == tokens[index].text)
10089 {
10090 names.push(tokens[index].text.to_string());
10091 }
10092 index += 1;
10093 }
10094 names
10095}
10096
10097fn css_module_composes_import_edge_source(
10098 tokens: &[Token<'_>],
10099 start: usize,
10100 end: usize,
10101) -> Option<String> {
10102 let source_index = next_non_trivia_token_index_until(tokens, start, end)?;
10103 let token = tokens[source_index];
10104 matches!(
10105 token.kind,
10106 SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10107 )
10108 .then(|| css_module_value_source_name(token))
10109}
10110
10111fn collect_css_module_composes_targets(
10112 tokens: &[Token<'_>],
10113 start: usize,
10114 end: usize,
10115 composes: &mut Vec<ParsedCssModuleComposesFact>,
10116 seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
10117) {
10118 let mut index = start;
10119 while index < end {
10120 if matches!(
10121 tokens[index].kind,
10122 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10123 ) && !tokens[index].text.eq_ignore_ascii_case("from")
10124 {
10125 push_css_module_composes_fact(
10126 composes,
10127 seen,
10128 ParsedCssModuleComposesFactKind::Target,
10129 tokens[index].text.to_string(),
10130 tokens[index].range,
10131 );
10132 }
10133 index += 1;
10134 }
10135}
10136
10137fn collect_css_module_composes_import_source(
10138 tokens: &[Token<'_>],
10139 start: usize,
10140 end: usize,
10141 composes: &mut Vec<ParsedCssModuleComposesFact>,
10142 seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
10143) {
10144 if let Some(source_index) = next_non_trivia_token_index_until(tokens, start, end) {
10145 let token = tokens[source_index];
10146 if matches!(
10147 token.kind,
10148 SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10149 ) {
10150 push_css_module_composes_fact(
10151 composes,
10152 seen,
10153 ParsedCssModuleComposesFactKind::ImportSource,
10154 css_module_value_source_name(token),
10155 token.range,
10156 );
10157 }
10158 }
10159}
10160
10161fn push_css_module_composes_fact(
10162 composes: &mut Vec<ParsedCssModuleComposesFact>,
10163 seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
10164 kind: ParsedCssModuleComposesFactKind,
10165 name: String,
10166 range: TextRange,
10167) {
10168 if seen.insert((
10169 kind,
10170 name.clone(),
10171 u32::from(range.start()),
10172 u32::from(range.end()),
10173 )) {
10174 composes.push(ParsedCssModuleComposesFact { kind, name, range });
10175 }
10176}
10177
10178fn collect_icss_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedIcssFact> {
10179 let mut icss = Vec::new();
10180 let mut seen = BTreeSet::new();
10181 for (index, token) in tokens.iter().enumerate() {
10182 if token.kind != SyntaxKind::Colon {
10183 continue;
10184 }
10185 let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10186 else {
10187 continue;
10188 };
10189 let name = tokens[name_index].text;
10190 if !matches!(tokens[name_index].kind, SyntaxKind::Ident) {
10191 continue;
10192 }
10193 if name.eq_ignore_ascii_case("export") {
10194 if let Some((open, close)) =
10195 find_block_after_header(tokens, name_index + 1, tokens.len())
10196 {
10197 collect_icss_export_names(tokens, open + 1, close, &mut icss, &mut seen);
10198 }
10199 continue;
10200 }
10201 if name.eq_ignore_ascii_case("import") {
10202 collect_icss_import_source(tokens, name_index + 1, &mut icss, &mut seen);
10203 if let Some((open, close)) =
10204 find_block_after_header(tokens, name_index + 1, tokens.len())
10205 {
10206 collect_icss_import_names(tokens, open + 1, close, &mut icss, &mut seen);
10207 }
10208 }
10209 }
10210 icss
10211}
10212
10213fn collect_icss_import_edge_facts_from_tokens(
10214 tokens: &[Token<'_>],
10215) -> Vec<ParsedIcssImportEdgeFact> {
10216 let mut edges = Vec::new();
10217 for (index, token) in tokens.iter().enumerate() {
10218 if token.kind != SyntaxKind::Colon {
10219 continue;
10220 }
10221 let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10222 else {
10223 continue;
10224 };
10225 if tokens[name_index].kind != SyntaxKind::Ident
10226 || !tokens[name_index].text.eq_ignore_ascii_case("import")
10227 {
10228 continue;
10229 }
10230 let Some(import_source) = icss_import_edge_source(tokens, name_index + 1) else {
10231 continue;
10232 };
10233 if let Some((open, close)) = find_block_after_header(tokens, name_index + 1, tokens.len()) {
10234 collect_icss_import_edges(tokens, open + 1, close, import_source, &mut edges);
10235 }
10236 }
10237 edges
10238}
10239
10240fn collect_icss_export_edge_facts_from_tokens(
10241 tokens: &[Token<'_>],
10242) -> Vec<ParsedIcssExportEdgeFact> {
10243 let mut edges = Vec::new();
10244 for (index, token) in tokens.iter().enumerate() {
10245 if token.kind != SyntaxKind::Colon {
10246 continue;
10247 }
10248 let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10249 else {
10250 continue;
10251 };
10252 if tokens[name_index].kind != SyntaxKind::Ident
10253 || !tokens[name_index].text.eq_ignore_ascii_case("export")
10254 {
10255 continue;
10256 }
10257 if let Some((open, close)) = find_block_after_header(tokens, name_index + 1, tokens.len()) {
10258 collect_icss_export_edges(tokens, open + 1, close, &mut edges);
10259 }
10260 }
10261 edges
10262}
10263
10264fn collect_icss_export_edges(
10265 tokens: &[Token<'_>],
10266 start: usize,
10267 end: usize,
10268 edges: &mut Vec<ParsedIcssExportEdgeFact>,
10269) {
10270 let mut index = start;
10271 while index < end {
10272 let token = tokens[index];
10273 if matches!(
10274 token.kind,
10275 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10276 ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10277 && tokens[colon_index].kind == SyntaxKind::Colon
10278 {
10279 let value_end = css_module_value_statement_end(tokens, colon_index + 1).min(end);
10280 let reference_names = collect_css_module_value_definition_edge_names(
10281 tokens,
10282 colon_index + 1,
10283 value_end,
10284 css_module_value_reference_token_can_be_name,
10285 );
10286 if !reference_names.is_empty() {
10287 let range_end = value_end
10288 .checked_sub(1)
10289 .and_then(|end| tokens.get(end))
10290 .map(|token| token.range.end())
10291 .unwrap_or_else(|| token.range.end());
10292 edges.push(ParsedIcssExportEdgeFact {
10293 export_name: token.text.to_string(),
10294 reference_names,
10295 range: TextRange::new(token.range.start(), range_end),
10296 });
10297 }
10298 index = value_end;
10299 continue;
10300 }
10301 index += 1;
10302 }
10303}
10304
10305fn icss_import_edge_source(tokens: &[Token<'_>], start: usize) -> Option<String> {
10306 let open_index = next_non_trivia_token_index_until(tokens, start, tokens.len())?;
10307 if tokens[open_index].kind != SyntaxKind::LeftParen {
10308 return None;
10309 }
10310 let source_index = next_non_trivia_token_index_until(tokens, open_index + 1, tokens.len())?;
10311 let token = tokens[source_index];
10312 matches!(
10313 token.kind,
10314 SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10315 )
10316 .then(|| css_module_value_source_name(token))
10317}
10318
10319fn collect_icss_import_edges(
10320 tokens: &[Token<'_>],
10321 start: usize,
10322 end: usize,
10323 import_source: String,
10324 edges: &mut Vec<ParsedIcssImportEdgeFact>,
10325) {
10326 let mut index = start;
10327 while index < end {
10328 let token = tokens[index];
10329 if matches!(
10330 token.kind,
10331 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10332 ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10333 && tokens[colon_index].kind == SyntaxKind::Colon
10334 && let Some(remote_index) =
10335 next_non_trivia_token_index_until(tokens, colon_index + 1, end)
10336 && matches!(
10337 tokens[remote_index].kind,
10338 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10339 )
10340 {
10341 edges.push(ParsedIcssImportEdgeFact {
10342 local_name: token.text.to_string(),
10343 remote_name: tokens[remote_index].text.to_string(),
10344 import_source: import_source.clone(),
10345 range: token.range,
10346 });
10347 index = css_module_value_statement_end(tokens, colon_index + 1);
10348 continue;
10349 }
10350 index += 1;
10351 }
10352}
10353
10354fn collect_icss_export_names(
10355 tokens: &[Token<'_>],
10356 start: usize,
10357 end: usize,
10358 icss: &mut Vec<ParsedIcssFact>,
10359 seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10360) {
10361 let mut index = start;
10362 while index < end {
10363 let token = tokens[index];
10364 if matches!(
10365 token.kind,
10366 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10367 ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10368 && tokens[colon_index].kind == SyntaxKind::Colon
10369 {
10370 push_icss_fact(
10371 icss,
10372 seen,
10373 ParsedIcssFactKind::ExportName,
10374 token.text.to_string(),
10375 token.range,
10376 );
10377 index = css_module_value_statement_end(tokens, colon_index + 1);
10378 continue;
10379 }
10380 index += 1;
10381 }
10382}
10383
10384fn collect_icss_import_source(
10385 tokens: &[Token<'_>],
10386 start: usize,
10387 icss: &mut Vec<ParsedIcssFact>,
10388 seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10389) {
10390 let Some(open_index) = next_non_trivia_token_index_until(tokens, start, tokens.len()) else {
10391 return;
10392 };
10393 if tokens[open_index].kind != SyntaxKind::LeftParen {
10394 return;
10395 }
10396 let Some(source_index) =
10397 next_non_trivia_token_index_until(tokens, open_index + 1, tokens.len())
10398 else {
10399 return;
10400 };
10401 let token = tokens[source_index];
10402 if matches!(
10403 token.kind,
10404 SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10405 ) {
10406 push_icss_fact(
10407 icss,
10408 seen,
10409 ParsedIcssFactKind::ImportSource,
10410 css_module_value_source_name(token),
10411 token.range,
10412 );
10413 }
10414}
10415
10416fn collect_icss_import_names(
10417 tokens: &[Token<'_>],
10418 start: usize,
10419 end: usize,
10420 icss: &mut Vec<ParsedIcssFact>,
10421 seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10422) {
10423 let mut index = start;
10424 while index < end {
10425 let token = tokens[index];
10426 if matches!(
10427 token.kind,
10428 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10429 ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10430 && tokens[colon_index].kind == SyntaxKind::Colon
10431 {
10432 push_icss_fact(
10433 icss,
10434 seen,
10435 ParsedIcssFactKind::ImportLocalName,
10436 token.text.to_string(),
10437 token.range,
10438 );
10439 if let Some(remote_index) =
10440 next_non_trivia_token_index_until(tokens, colon_index + 1, end)
10441 && matches!(
10442 tokens[remote_index].kind,
10443 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10444 )
10445 {
10446 push_icss_fact(
10447 icss,
10448 seen,
10449 ParsedIcssFactKind::ImportRemoteName,
10450 tokens[remote_index].text.to_string(),
10451 tokens[remote_index].range,
10452 );
10453 }
10454 index = css_module_value_statement_end(tokens, colon_index + 1);
10455 continue;
10456 }
10457 index += 1;
10458 }
10459}
10460
10461fn push_icss_fact(
10462 icss: &mut Vec<ParsedIcssFact>,
10463 seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10464 kind: ParsedIcssFactKind,
10465 name: String,
10466 range: TextRange,
10467) {
10468 if seen.insert((
10469 kind,
10470 name.clone(),
10471 u32::from(range.start()),
10472 u32::from(range.end()),
10473 )) {
10474 icss.push(ParsedIcssFact { kind, name, range });
10475 }
10476}
10477
10478fn collect_animation_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedAnimationFact> {
10479 let mut animations = Vec::new();
10480 let mut seen = BTreeSet::new();
10481 for (index, token) in tokens.iter().enumerate() {
10482 if token.kind == SyntaxKind::AtKeyword && at_keyword_is_keyframes_rule(token.text) {
10483 if let Some(name_index) =
10484 next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10485 && let Some(name) = animation_name_from_token(tokens[name_index])
10486 {
10487 push_animation_fact(
10488 &mut animations,
10489 &mut seen,
10490 ParsedAnimationFactKind::KeyframesDeclaration,
10491 name,
10492 tokens[name_index].range,
10493 );
10494 }
10495 continue;
10496 }
10497
10498 if token.kind == SyntaxKind::Ident
10499 && token.text.eq_ignore_ascii_case("animation-name")
10500 && let Some(colon_index) =
10501 next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10502 && tokens[colon_index].kind == SyntaxKind::Colon
10503 {
10504 collect_animation_name_references_until(
10505 tokens,
10506 colon_index + 1,
10507 &mut animations,
10508 &mut seen,
10509 );
10510 }
10511
10512 if token.kind == SyntaxKind::Ident
10513 && token.text.eq_ignore_ascii_case("animation")
10514 && let Some(colon_index) =
10515 next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10516 && tokens[colon_index].kind == SyntaxKind::Colon
10517 {
10518 collect_animation_shorthand_references_until(
10519 tokens,
10520 colon_index + 1,
10521 &mut animations,
10522 &mut seen,
10523 );
10524 }
10525 }
10526 animations
10527}
10528
10529fn collect_animation_name_references_until(
10530 tokens: &[Token<'_>],
10531 start: usize,
10532 animations: &mut Vec<ParsedAnimationFact>,
10533 seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10534) {
10535 let mut index = start;
10536 let mut paren_depth = 0usize;
10537 let mut bracket_depth = 0usize;
10538 while index < tokens.len() {
10539 match tokens[index].kind {
10540 SyntaxKind::LeftParen => paren_depth += 1,
10541 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
10542 SyntaxKind::LeftBracket => bracket_depth += 1,
10543 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
10544 SyntaxKind::Semicolon
10545 | SyntaxKind::SassOptionalSemicolon
10546 | SyntaxKind::RightBrace
10547 | SyntaxKind::SassDedent
10548 if paren_depth == 0 && bracket_depth == 0 =>
10549 {
10550 break;
10551 }
10552 _ => {}
10553 }
10554
10555 if paren_depth == 0
10556 && bracket_depth == 0
10557 && !animation_name_token_is_interpolation_adjacent(tokens, index)
10558 && let Some(name) = animation_name_from_token(tokens[index])
10559 {
10560 push_animation_fact(
10561 animations,
10562 seen,
10563 ParsedAnimationFactKind::AnimationNameReference,
10564 name,
10565 tokens[index].range,
10566 );
10567 }
10568 index += 1;
10569 }
10570}
10571
10572fn collect_animation_shorthand_references_until(
10573 tokens: &[Token<'_>],
10574 start: usize,
10575 animations: &mut Vec<ParsedAnimationFact>,
10576 seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10577) {
10578 let mut index = start;
10579 let mut paren_depth = 0usize;
10580 let mut bracket_depth = 0usize;
10581 while index < tokens.len() {
10582 match tokens[index].kind {
10583 SyntaxKind::LeftParen => paren_depth += 1,
10584 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
10585 SyntaxKind::LeftBracket => bracket_depth += 1,
10586 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
10587 SyntaxKind::Semicolon
10588 | SyntaxKind::SassOptionalSemicolon
10589 | SyntaxKind::RightBrace
10590 | SyntaxKind::SassDedent
10591 if paren_depth == 0 && bracket_depth == 0 =>
10592 {
10593 break;
10594 }
10595 _ => {}
10596 }
10597
10598 if paren_depth == 0
10599 && bracket_depth == 0
10600 && animation_shorthand_token_can_be_name(tokens, index)
10601 && let Some(name) = animation_name_from_token(tokens[index])
10602 {
10603 push_animation_fact(
10604 animations,
10605 seen,
10606 ParsedAnimationFactKind::AnimationNameReference,
10607 name,
10608 tokens[index].range,
10609 );
10610 }
10611 index += 1;
10612 }
10613}
10614
10615fn animation_shorthand_token_can_be_name(tokens: &[Token<'_>], index: usize) -> bool {
10616 let token = tokens[index];
10617 if token.kind == SyntaxKind::String {
10618 return true;
10619 }
10620 if token.kind != SyntaxKind::Ident {
10621 return false;
10622 }
10623 if animation_name_token_is_interpolation_adjacent(tokens, index) {
10628 return false;
10629 }
10630 if animation_shorthand_ident_is_time_unit(token.text) {
10632 return false;
10633 }
10634 if let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10635 && tokens[next_index].kind == SyntaxKind::LeftParen
10636 {
10637 return false;
10638 }
10639 !animation_shorthand_ident_is_non_name(token.text)
10640}
10641
10642fn animation_shorthand_ident_is_time_unit(name: &str) -> bool {
10643 name.eq_ignore_ascii_case("s") || name.eq_ignore_ascii_case("ms")
10644}
10645
10646fn animation_name_token_is_interpolation_adjacent(tokens: &[Token<'_>], index: usize) -> bool {
10656 if index > 0
10657 && matches!(
10658 tokens[index - 1].kind,
10659 SyntaxKind::ScssInterpolationEnd | SyntaxKind::LessInterpolationEnd
10660 )
10661 {
10662 return true;
10663 }
10664 if let Some(next) = tokens.get(index + 1)
10665 && matches!(
10666 next.kind,
10667 SyntaxKind::ScssInterpolationStart | SyntaxKind::LessInterpolationStart
10668 )
10669 {
10670 return true;
10671 }
10672 false
10673}
10674
10675fn animation_shorthand_ident_is_non_name(name: &str) -> bool {
10676 matches!(
10677 name.to_ascii_lowercase().as_str(),
10678 "ease"
10679 | "ease-in"
10680 | "ease-out"
10681 | "ease-in-out"
10682 | "linear"
10683 | "step-start"
10684 | "step-end"
10685 | "infinite"
10686 | "normal"
10687 | "reverse"
10688 | "alternate"
10689 | "alternate-reverse"
10690 | "running"
10691 | "paused"
10692 | "forwards"
10693 | "backwards"
10694 | "both"
10695 | "replace"
10696 | "add"
10697 | "accumulate"
10698 | "auto"
10699 )
10700}
10701
10702fn push_animation_fact(
10703 animations: &mut Vec<ParsedAnimationFact>,
10704 seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10705 kind: ParsedAnimationFactKind,
10706 name: String,
10707 range: TextRange,
10708) {
10709 if seen.insert((
10710 kind,
10711 name.clone(),
10712 u32::from(range.start()),
10713 u32::from(range.end()),
10714 )) {
10715 animations.push(ParsedAnimationFact { kind, name, range });
10716 }
10717}
10718
10719fn animation_name_from_token(token: Token<'_>) -> Option<String> {
10720 if !matches!(token.kind, SyntaxKind::Ident | SyntaxKind::String) {
10721 return None;
10722 }
10723 let name = token
10724 .text
10725 .trim_matches(|character| character == '"' || character == '\'')
10726 .to_string();
10727 if name.is_empty() || animation_name_is_reserved(&name) {
10728 return None;
10729 }
10730 Some(name)
10731}
10732
10733fn animation_name_is_reserved(name: &str) -> bool {
10734 matches!(
10735 name.to_ascii_lowercase().as_str(),
10736 "none" | "initial" | "inherit" | "unset" | "revert" | "revert-layer"
10737 )
10738}
10739
10740fn at_keyword_is_keyframes_rule(text: &str) -> bool {
10748 let Some(rule) = text.strip_prefix('@') else {
10749 return false;
10750 };
10751 if rule.eq_ignore_ascii_case("keyframes") {
10752 return true;
10753 }
10754 if let Some(rest) = rule.strip_prefix('-')
10756 && let Some((vendor, remainder)) = rest.split_once('-')
10757 && !vendor.is_empty()
10758 && remainder.eq_ignore_ascii_case("keyframes")
10759 {
10760 return true;
10761 }
10762 false
10763}
10764
10765fn containing_at_rule_header_name<'text>(
10766 tokens: &'text [Token<'text>],
10767 index: usize,
10768) -> Option<&'text str> {
10769 let mut current = index;
10770 while current > 0 {
10771 current -= 1;
10772 let token = tokens.get(current)?;
10773 if token.kind.is_trivia() {
10774 continue;
10775 }
10776 if matches!(
10777 token.kind,
10778 SyntaxKind::Semicolon
10779 | SyntaxKind::SassOptionalSemicolon
10780 | SyntaxKind::LeftBrace
10781 | SyntaxKind::RightBrace
10782 | SyntaxKind::SassIndent
10783 | SyntaxKind::SassDedent
10784 ) {
10785 return None;
10786 }
10787 if token.kind == SyntaxKind::AtKeyword {
10788 return Some(token.text);
10789 }
10790 }
10791 None
10792}
10793
10794fn skip_trivia_tokens(tokens: &[Token<'_>], mut index: usize, end: usize) -> usize {
10795 while index < end && tokens[index].kind.is_trivia() {
10796 index += 1;
10797 }
10798 index
10799}
10800
10801fn skip_statement(tokens: &[Token<'_>], mut index: usize, end: usize) -> usize {
10802 while index < end {
10803 match tokens[index].kind {
10804 SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon => return index + 1,
10805 SyntaxKind::RightBrace | SyntaxKind::SassDedent => return index,
10806 _ => index += 1,
10807 }
10808 }
10809 index
10810}
10811
10812fn find_block_after_header(
10813 tokens: &[Token<'_>],
10814 start: usize,
10815 end: usize,
10816) -> Option<(usize, usize)> {
10817 let mut index = start;
10818 while index < end {
10819 match tokens[index].kind {
10820 SyntaxKind::Semicolon
10821 | SyntaxKind::SassOptionalSemicolon
10822 | SyntaxKind::RightBrace
10823 | SyntaxKind::SassDedent => return None,
10824 SyntaxKind::LeftBrace => {
10825 let close = matching_right_brace(tokens, index, end)?;
10826 return Some((index, close));
10827 }
10828 SyntaxKind::SassIndent => {
10829 let close = matching_sass_dedent(tokens, index, end)?;
10830 return Some((index, close));
10831 }
10832 _ => index += 1,
10833 }
10834 }
10835 None
10836}
10837
10838fn matching_right_brace(tokens: &[Token<'_>], open: usize, end: usize) -> Option<usize> {
10839 let mut depth = 0usize;
10840 let mut index = open;
10841 while index < end {
10842 match tokens[index].kind {
10843 SyntaxKind::LeftBrace => depth += 1,
10844 SyntaxKind::RightBrace => {
10845 depth = depth.saturating_sub(1);
10846 if depth == 0 {
10847 return Some(index);
10848 }
10849 }
10850 _ => {}
10851 }
10852 index += 1;
10853 }
10854 None
10855}
10856
10857fn matching_sass_dedent(tokens: &[Token<'_>], open: usize, end: usize) -> Option<usize> {
10858 let mut depth = 0usize;
10859 let mut index = open;
10860 while index < end {
10861 match tokens[index].kind {
10862 SyntaxKind::SassIndent => depth += 1,
10863 SyntaxKind::SassDedent => {
10864 depth = depth.saturating_sub(1);
10865 if depth == 0 {
10866 return Some(index);
10867 }
10868 }
10869 _ => {}
10870 }
10871 index += 1;
10872 }
10873 None
10874}
10875
10876fn style_wrapper_at_rule(name: &str) -> bool {
10877 matches_ignore_ascii_case(
10878 name,
10879 &[
10880 "@media",
10881 "@supports",
10882 "@when",
10883 "@else",
10884 "@layer",
10885 "@scope",
10886 "@container",
10887 "@starting-style",
10888 "@if",
10889 "@else",
10890 "@for",
10891 "@each",
10892 "@while",
10893 "@at-root",
10894 "@include",
10895 ],
10896 )
10897}
10898
10899fn is_selector_combinator_kind(kind: SyntaxKind) -> bool {
10900 matches!(
10901 kind,
10902 SyntaxKind::GreaterThan
10903 | SyntaxKind::Plus
10904 | SyntaxKind::Tilde
10905 | SyntaxKind::ColumnCombinator
10906 | SyntaxKind::DoublePipe
10907 )
10908}
10909
10910fn selector_component_can_start(kind: SyntaxKind) -> bool {
10911 matches!(
10912 kind,
10913 SyntaxKind::Dot
10914 | SyntaxKind::Hash
10915 | SyntaxKind::Ident
10916 | SyntaxKind::Star
10917 | SyntaxKind::Ampersand
10918 | SyntaxKind::ScssPlaceholder
10919 | SyntaxKind::LeftBracket
10920 | SyntaxKind::Colon
10921 | SyntaxKind::DoubleColon
10922 )
10923}
10924
10925fn namespace_selector_target_can_start(kind: SyntaxKind) -> bool {
10926 matches!(
10927 kind,
10928 SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star
10929 )
10930}
10931
10932fn keyframe_selector_token_is_valid(token: Token<'_>) -> bool {
10933 token.kind == SyntaxKind::Percentage
10934 || (token.kind == SyntaxKind::Ident
10935 && (token.text.eq_ignore_ascii_case("from") || token.text.eq_ignore_ascii_case("to")))
10936}
10937
10938fn selector_component_can_end(kind: SyntaxKind) -> bool {
10939 matches!(
10940 kind,
10941 SyntaxKind::Ident
10942 | SyntaxKind::CustomPropertyName
10943 | SyntaxKind::Hash
10944 | SyntaxKind::RightBracket
10945 | SyntaxKind::RightParen
10946 | SyntaxKind::Star
10947 )
10948}
10949
10950fn collect_at_rule_facts_from_tokens(
10951 tokens: &[Token<'_>],
10952 dialect: StyleDialect,
10953) -> Vec<ParsedAtRuleFact> {
10954 tokens
10955 .iter()
10956 .filter(|token| token.kind == SyntaxKind::AtKeyword)
10957 .map(|token| {
10958 let css_spec = at_rule_spec(token.text);
10959 let node_kind = css_spec
10960 .or_else(|| match dialect {
10961 StyleDialect::Scss | StyleDialect::Sass => scss_at_rule_spec(token.text),
10962 StyleDialect::Css | StyleDialect::Less => None,
10963 })
10964 .map(|spec| spec.node_kind);
10965 let name = if css_spec.is_some() {
10966 token.text.to_ascii_lowercase()
10967 } else {
10968 token.text.to_string()
10969 };
10970 ParsedAtRuleFact {
10971 name,
10972 node_kind,
10973 range: token.range,
10974 }
10975 })
10976 .collect()
10977}
10978
10979fn next_non_trivia_token<'text>(
10980 tokens: &'text [Token<'text>],
10981 mut index: usize,
10982) -> Option<Token<'text>> {
10983 while let Some(token) = tokens.get(index).copied() {
10984 if !token.kind.is_trivia() {
10985 return Some(token);
10986 }
10987 index += 1;
10988 }
10989 None
10990}
10991
10992fn next_non_trivia_token_until<'text>(
10993 tokens: &'text [Token<'text>],
10994 mut index: usize,
10995 end: usize,
10996) -> Option<Token<'text>> {
10997 while index < end {
10998 let token = tokens.get(index).copied()?;
10999 if !token.kind.is_trivia() {
11000 return Some(token);
11001 }
11002 index += 1;
11003 }
11004 None
11005}
11006
11007fn next_non_trivia_token_index_until(
11008 tokens: &[Token<'_>],
11009 mut index: usize,
11010 end: usize,
11011) -> Option<usize> {
11012 while index < end {
11013 let token = tokens.get(index)?;
11014 if !token.kind.is_trivia() {
11015 return Some(index);
11016 }
11017 index += 1;
11018 }
11019 None
11020}
11021
11022fn next_non_trivia_token_after_range<'text>(
11023 tokens: &'text [Token<'text>],
11024 range: TextRange,
11025 end: usize,
11026) -> Option<Token<'text>> {
11027 let index = token_index_by_range(tokens, range)?;
11028 next_non_trivia_token_until(tokens, index + 1, end)
11029}
11030
11031fn token_index_by_range(tokens: &[Token<'_>], range: TextRange) -> Option<usize> {
11032 tokens.iter().position(|token| token.range == range)
11033}
11034
11035fn matching_right_paren_from_range(
11036 tokens: &[Token<'_>],
11037 open_range: TextRange,
11038 end: usize,
11039) -> Option<usize> {
11040 let mut depth = 0usize;
11041 let mut index = token_index_by_range(tokens, open_range)?;
11042 while index < end {
11043 match tokens[index].kind {
11044 SyntaxKind::LeftParen => depth += 1,
11045 SyntaxKind::RightParen => {
11046 depth = depth.saturating_sub(1);
11047 if depth == 0 {
11048 return Some(index);
11049 }
11050 }
11051 _ => {}
11052 }
11053 index += 1;
11054 }
11055 None
11056}
11057
11058fn previous_non_trivia_token<'text>(
11059 tokens: &'text [Token<'text>],
11060 start: usize,
11061 index: usize,
11062) -> Option<Token<'text>> {
11063 let mut current = index;
11064 while current > start {
11065 current -= 1;
11066 let token = tokens.get(current).copied()?;
11067 if !token.kind.is_trivia() {
11068 return Some(token);
11069 }
11070 }
11071 None
11072}
11073
11074fn at_rule_spec(text: &str) -> Option<AtRuleSpec> {
11075 let lowered = text.to_ascii_lowercase();
11076 let (node_kind, block_kind) = match lowered.as_str() {
11077 "@media" => (SyntaxKind::MediaRule, AtRuleBlockKind::GroupRuleList),
11078 "@supports" => (SyntaxKind::SupportsRule, AtRuleBlockKind::GroupRuleList),
11079 "@when" => (SyntaxKind::WhenRule, AtRuleBlockKind::GroupRuleList),
11080 "@else" => (SyntaxKind::ElseRule, AtRuleBlockKind::GroupRuleList),
11081 "@container" => (SyntaxKind::ContainerRule, AtRuleBlockKind::GroupRuleList),
11082 "@layer" => (SyntaxKind::LayerRule, AtRuleBlockKind::GroupRuleList),
11083 "@scope" => (SyntaxKind::ScopeRule, AtRuleBlockKind::GroupRuleList),
11084 "@starting-style" => (
11085 SyntaxKind::StartingStyleRule,
11086 AtRuleBlockKind::GroupRuleList,
11087 ),
11088 "@nest" => (SyntaxKind::NestRule, AtRuleBlockKind::DeclarationList),
11089 "@keyframes" => (SyntaxKind::KeyframesRule, AtRuleBlockKind::Keyframes),
11090 "@font-face" => (SyntaxKind::FontFaceRule, AtRuleBlockKind::DeclarationList),
11091 "@page" => (SyntaxKind::PageRule, AtRuleBlockKind::DeclarationList),
11092 "@property" => (SyntaxKind::PropertyRule, AtRuleBlockKind::DeclarationList),
11093 "@counter-style" => (
11094 SyntaxKind::CounterStyleRule,
11095 AtRuleBlockKind::DeclarationList,
11096 ),
11097 "@font-palette-values" => (
11098 SyntaxKind::FontPaletteValuesRule,
11099 AtRuleBlockKind::DeclarationList,
11100 ),
11101 "@color-profile" => (
11102 SyntaxKind::ColorProfileRule,
11103 AtRuleBlockKind::DeclarationList,
11104 ),
11105 "@position-try" => (
11106 SyntaxKind::PositionTryRule,
11107 AtRuleBlockKind::DeclarationList,
11108 ),
11109 "@font-feature-values" => (
11110 SyntaxKind::FontFeatureValuesRule,
11111 AtRuleBlockKind::GroupRuleList,
11112 ),
11113 "@stylistic" => (
11114 SyntaxKind::FontFeatureValuesStylisticRule,
11115 AtRuleBlockKind::DeclarationList,
11116 ),
11117 "@styleset" => (
11118 SyntaxKind::FontFeatureValuesStylesetRule,
11119 AtRuleBlockKind::DeclarationList,
11120 ),
11121 "@character-variant" => (
11122 SyntaxKind::FontFeatureValuesCharacterVariantRule,
11123 AtRuleBlockKind::DeclarationList,
11124 ),
11125 "@swash" => (
11126 SyntaxKind::FontFeatureValuesSwashRule,
11127 AtRuleBlockKind::DeclarationList,
11128 ),
11129 "@ornaments" => (
11130 SyntaxKind::FontFeatureValuesOrnamentsRule,
11131 AtRuleBlockKind::DeclarationList,
11132 ),
11133 "@annotation" => (
11134 SyntaxKind::FontFeatureValuesAnnotationRule,
11135 AtRuleBlockKind::DeclarationList,
11136 ),
11137 "@historical-forms" => (
11138 SyntaxKind::FontFeatureValuesHistoricalFormsRule,
11139 AtRuleBlockKind::DeclarationList,
11140 ),
11141 "@view-transition" => (
11142 SyntaxKind::ViewTransitionRule,
11143 AtRuleBlockKind::DeclarationList,
11144 ),
11145 "@charset" => (SyntaxKind::CharsetRule, AtRuleBlockKind::Raw),
11146 "@import" => (SyntaxKind::ImportRule, AtRuleBlockKind::Raw),
11147 "@namespace" => (SyntaxKind::NamespaceRule, AtRuleBlockKind::Raw),
11148 "@custom-media" => (SyntaxKind::CustomMediaRule, AtRuleBlockKind::Raw),
11149 text if is_page_margin_at_rule(text) => {
11150 (SyntaxKind::PageMarginRule, AtRuleBlockKind::DeclarationList)
11151 }
11152 _ => return None,
11153 };
11154 Some(AtRuleSpec {
11155 node_kind,
11156 block_kind,
11157 })
11158}
11159
11160fn is_page_margin_at_rule(text: &str) -> bool {
11161 matches!(
11162 text,
11163 "@top-left-corner"
11164 | "@top-left"
11165 | "@top-center"
11166 | "@top-right"
11167 | "@top-right-corner"
11168 | "@bottom-left-corner"
11169 | "@bottom-left"
11170 | "@bottom-center"
11171 | "@bottom-right"
11172 | "@bottom-right-corner"
11173 | "@left-top"
11174 | "@left-middle"
11175 | "@left-bottom"
11176 | "@right-top"
11177 | "@right-middle"
11178 | "@right-bottom"
11179 )
11180}
11181
11182fn scss_at_rule_spec(text: &str) -> Option<AtRuleSpec> {
11183 let (node_kind, block_kind) = match text {
11184 "@use" => (SyntaxKind::ScssUseRule, AtRuleBlockKind::Raw),
11185 "@forward" => (SyntaxKind::ScssForwardRule, AtRuleBlockKind::Raw),
11186 "@mixin" => (
11187 SyntaxKind::ScssMixinDeclaration,
11188 AtRuleBlockKind::DeclarationList,
11189 ),
11190 "@include" => (
11191 SyntaxKind::ScssIncludeRule,
11192 AtRuleBlockKind::DeclarationList,
11193 ),
11194 "@function" => (
11195 SyntaxKind::ScssFunctionDeclaration,
11196 AtRuleBlockKind::DeclarationList,
11197 ),
11198 "@return" => (SyntaxKind::ScssReturnRule, AtRuleBlockKind::Raw),
11199 "@extend" => (SyntaxKind::ScssExtendRule, AtRuleBlockKind::Raw),
11200 "@if" => (SyntaxKind::ScssControlIf, AtRuleBlockKind::DeclarationList),
11201 "@else" => (
11202 SyntaxKind::ScssControlElse,
11203 AtRuleBlockKind::DeclarationList,
11204 ),
11205 "@each" => (
11206 SyntaxKind::ScssControlEach,
11207 AtRuleBlockKind::DeclarationList,
11208 ),
11209 "@for" => (SyntaxKind::ScssControlFor, AtRuleBlockKind::DeclarationList),
11210 "@while" => (
11211 SyntaxKind::ScssControlWhile,
11212 AtRuleBlockKind::DeclarationList,
11213 ),
11214 "@at-root" => (SyntaxKind::ScssAtRootRule, AtRuleBlockKind::DeclarationList),
11215 "@error" => (SyntaxKind::ScssErrorRule, AtRuleBlockKind::Raw),
11216 "@warn" => (SyntaxKind::ScssWarnRule, AtRuleBlockKind::Raw),
11217 "@debug" => (SyntaxKind::ScssDebugRule, AtRuleBlockKind::Raw),
11218 "@content" => (SyntaxKind::ScssContentRule, AtRuleBlockKind::Raw),
11219 _ => return None,
11220 };
11221 Some(AtRuleSpec {
11222 node_kind,
11223 block_kind,
11224 })
11225}
11226
11227fn is_selector_boundary(kind: SyntaxKind) -> bool {
11228 matches!(
11229 kind,
11230 SyntaxKind::Comma
11231 | SyntaxKind::LeftBrace
11232 | SyntaxKind::SassIndent
11233 | SyntaxKind::RightBrace
11234 | SyntaxKind::SassDedent
11235 | SyntaxKind::Semicolon
11236 | SyntaxKind::SassOptionalSemicolon
11237 )
11238}
11239
11240fn is_selector_boundary_until(kind: SyntaxKind, recovery: &[SyntaxKind]) -> bool {
11241 is_selector_boundary(kind) || recovery.contains(&kind)
11242}
11243
11244fn is_selector_list_pseudo_class(text: &str) -> bool {
11245 matches!(text, "is" | "where" | "local" | "global")
11246}
11247
11248fn is_nth_pseudo_class(text: &str) -> bool {
11249 matches!(
11250 text,
11251 "nth-child" | "nth-last-child" | "nth-of-type" | "nth-last-of-type"
11252 )
11253}
11254
11255fn language_tag_token_can_start(kind: SyntaxKind) -> bool {
11256 matches!(kind, SyntaxKind::Ident | SyntaxKind::String)
11257}
11258
11259fn selector_item_token_is_recoverable(kind: SyntaxKind) -> bool {
11260 matches!(
11261 kind,
11262 SyntaxKind::Whitespace
11263 | SyntaxKind::SassIndentedNewline
11264 | SyntaxKind::Dot
11265 | SyntaxKind::Comma
11266 | SyntaxKind::Hash
11267 | SyntaxKind::Ident
11268 | SyntaxKind::CustomPropertyName
11269 | SyntaxKind::String
11270 | SyntaxKind::Number
11271 | SyntaxKind::Percentage
11272 | SyntaxKind::Dimension
11273 | SyntaxKind::Star
11274 | SyntaxKind::Ampersand
11275 | SyntaxKind::ScssPlaceholder
11276 | SyntaxKind::LeftBracket
11277 | SyntaxKind::RightBracket
11278 | SyntaxKind::Colon
11279 | SyntaxKind::DoubleColon
11280 | SyntaxKind::LeftParen
11281 | SyntaxKind::RightParen
11282 | SyntaxKind::Equals
11283 | SyntaxKind::IncludesMatch
11284 | SyntaxKind::DashMatch
11285 | SyntaxKind::PrefixMatch
11286 | SyntaxKind::SuffixMatch
11287 | SyntaxKind::SubstringMatch
11288 | SyntaxKind::Pipe
11289 | SyntaxKind::ColumnCombinator
11290 | SyntaxKind::GreaterThan
11291 | SyntaxKind::Plus
11292 | SyntaxKind::Minus
11293 | SyntaxKind::Tilde
11294 | SyntaxKind::KeywordAnd
11295 | SyntaxKind::KeywordOr
11296 | SyntaxKind::KeywordNot
11297 )
11298}
11299
11300fn is_at_rule_prelude_boundary(kind: SyntaxKind) -> bool {
11301 matches!(
11302 kind,
11303 SyntaxKind::LeftBrace
11304 | SyntaxKind::SassIndent
11305 | SyntaxKind::Semicolon
11306 | SyntaxKind::SassOptionalSemicolon
11307 )
11308}
11309
11310fn is_statement_end(kind: SyntaxKind) -> bool {
11311 matches!(
11312 kind,
11313 SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon
11314 )
11315}
11316
11317fn sass_token_can_end_statement(kind: SyntaxKind) -> bool {
11318 !matches!(
11319 kind,
11320 SyntaxKind::Whitespace
11321 | SyntaxKind::LineComment
11322 | SyntaxKind::BlockComment
11323 | SyntaxKind::SassIndentedNewline
11324 | SyntaxKind::SassIndent
11325 | SyntaxKind::SassDedent
11326 | SyntaxKind::SassOptionalSemicolon
11327 | SyntaxKind::Comma
11328 | SyntaxKind::Colon
11329 | SyntaxKind::DoubleColon
11330 | SyntaxKind::LeftBrace
11331 | SyntaxKind::LeftParen
11332 | SyntaxKind::LeftBracket
11333 | SyntaxKind::Plus
11334 | SyntaxKind::Minus
11335 | SyntaxKind::Star
11336 | SyntaxKind::Slash
11337 | SyntaxKind::GreaterThan
11338 | SyntaxKind::LessThan
11339 | SyntaxKind::Equals
11340 | SyntaxKind::Arrow
11341 | SyntaxKind::Pipe
11342 | SyntaxKind::Tilde
11343 | SyntaxKind::Caret
11344 | SyntaxKind::Ampersand
11345 | SyntaxKind::DoubleAmpersand
11346 | SyntaxKind::ColumnCombinator
11347 | SyntaxKind::IncludesMatch
11348 | SyntaxKind::DashMatch
11349 | SyntaxKind::PrefixMatch
11350 | SyntaxKind::SuffixMatch
11351 | SyntaxKind::SubstringMatch
11352 | SyntaxKind::PlusEquals
11353 | SyntaxKind::MinusEquals
11354 | SyntaxKind::SlashEquals
11355 )
11356}
11357
11358fn function_argument_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11359 let mut kinds = vec![SyntaxKind::RightParen];
11360 for kind in recovery {
11361 if !kinds.contains(kind) {
11362 kinds.push(*kind);
11363 }
11364 }
11365 kinds
11366}
11367
11368fn bracketed_value_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11369 let mut kinds = vec![SyntaxKind::RightBracket];
11370 for kind in recovery {
11371 if !kinds.contains(kind) {
11372 kinds.push(*kind);
11373 }
11374 }
11375 kinds
11376}
11377
11378fn simple_block_recovery(close_kind: SyntaxKind, recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11379 let mut kinds = vec![close_kind];
11380 for kind in recovery {
11381 if !kinds.contains(kind) {
11382 kinds.push(*kind);
11383 }
11384 }
11385 kinds
11386}
11387
11388fn matching_simple_block_close(open_kind: SyntaxKind) -> Option<SyntaxKind> {
11389 match open_kind {
11390 SyntaxKind::LeftBrace => Some(SyntaxKind::RightBrace),
11391 SyntaxKind::LeftBracket => Some(SyntaxKind::RightBracket),
11392 SyntaxKind::LeftParen => Some(SyntaxKind::RightParen),
11393 _ => None,
11394 }
11395}
11396
11397fn value_list_item_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11398 let mut kinds = vec![SyntaxKind::Comma];
11399 for kind in recovery {
11400 if !kinds.contains(kind) {
11401 kinds.push(*kind);
11402 }
11403 }
11404 kinds
11405}
11406
11407fn comma_separated_component_value_list_item_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11408 let mut kinds = vec![SyntaxKind::Comma];
11409 for kind in recovery {
11410 if !kinds.contains(kind) {
11411 kinds.push(*kind);
11412 }
11413 }
11414 kinds
11415}
11416
11417fn variable_declaration_node_kind(kind: SyntaxKind, has_colon: bool) -> SyntaxKind {
11418 if has_colon {
11419 return kind;
11420 }
11421 match kind {
11422 SyntaxKind::ScssVariableDeclaration => SyntaxKind::BogusScssVariable,
11423 SyntaxKind::LessVariableDeclaration => SyntaxKind::BogusLessVariable,
11424 _ => kind,
11425 }
11426}
11427
11428fn is_attribute_matcher(kind: SyntaxKind) -> bool {
11429 matches!(
11430 kind,
11431 SyntaxKind::Equals
11432 | SyntaxKind::IncludesMatch
11433 | SyntaxKind::DashMatch
11434 | SyntaxKind::PrefixMatch
11435 | SyntaxKind::SuffixMatch
11436 | SyntaxKind::SubstringMatch
11437 )
11438}
11439
11440fn attribute_name_token_can_start(kind: SyntaxKind) -> bool {
11441 matches!(
11442 kind,
11443 SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star
11444 )
11445}
11446
11447fn attribute_name_token_can_continue(kind: SyntaxKind) -> bool {
11448 matches!(
11449 kind,
11450 SyntaxKind::Ident
11451 | SyntaxKind::CustomPropertyName
11452 | SyntaxKind::Star
11453 | SyntaxKind::Pipe
11454 | SyntaxKind::ColumnCombinator
11455 )
11456}
11457
11458fn attribute_value_token_can_start(kind: SyntaxKind) -> bool {
11459 matches!(
11460 kind,
11461 SyntaxKind::Ident
11462 | SyntaxKind::CustomPropertyName
11463 | SyntaxKind::String
11464 | SyntaxKind::Hash
11465 | SyntaxKind::Number
11466 | SyntaxKind::Dimension
11467 )
11468}
11469
11470fn is_combinator(kind: SyntaxKind) -> bool {
11471 matches!(
11472 kind,
11473 SyntaxKind::GreaterThan
11474 | SyntaxKind::Plus
11475 | SyntaxKind::Tilde
11476 | SyntaxKind::ColumnCombinator
11477 )
11478}
11479
11480fn infix_binding_power(kind: SyntaxKind) -> Option<(u8, u8)> {
11481 match kind {
11482 SyntaxKind::Plus | SyntaxKind::Minus => Some((1, 2)),
11483 SyntaxKind::Star | SyntaxKind::Slash | SyntaxKind::Percent => Some((3, 4)),
11484 _ => None,
11485 }
11486}
11487
11488fn specialized_function_kind(text: &str) -> Option<SyntaxKind> {
11489 if text.eq_ignore_ascii_case("var") {
11490 return Some(SyntaxKind::VarFunction);
11491 }
11492 if text.eq_ignore_ascii_case("calc") {
11493 return Some(SyntaxKind::CalcFunction);
11494 }
11495 if text.eq_ignore_ascii_case("env") {
11496 return Some(SyntaxKind::EnvFunction);
11497 }
11498 if text.eq_ignore_ascii_case("attr") {
11499 return Some(SyntaxKind::AttrFunction);
11500 }
11501 if matches_ignore_ascii_case(text, VALUES_L4_MATH_FUNCTION_NAMES) {
11502 return Some(SyntaxKind::MathFunction);
11503 }
11504 if matches_ignore_ascii_case(text, CSS_COLOR_FUNCTION_NAMES) {
11505 return Some(SyntaxKind::ColorValue);
11506 }
11507 if matches_ignore_ascii_case(text, CSS_GRADIENT_FUNCTION_NAMES) {
11508 return Some(SyntaxKind::GradientFunction);
11509 }
11510 if matches_ignore_ascii_case(text, CSS_TRANSFORM_FUNCTION_NAMES) {
11511 return Some(SyntaxKind::TransformFunction);
11512 }
11513 if matches_ignore_ascii_case(text, CSS_FILTER_FUNCTION_NAMES) {
11514 return Some(SyntaxKind::FilterFunction);
11515 }
11516 if matches_ignore_ascii_case(text, CSS_IMAGE_FUNCTION_NAMES) {
11517 return Some(SyntaxKind::ImageFunction);
11518 }
11519 if matches_ignore_ascii_case(text, CSS_SHAPE_FUNCTION_NAMES) {
11520 return Some(SyntaxKind::ShapeFunction);
11521 }
11522 None
11523}
11524
11525fn function_argument_count_is_valid(function_name: &str, argument_count: usize) -> bool {
11526 if function_name.eq_ignore_ascii_case("calc") {
11527 return argument_count == 1;
11528 }
11529 if matches_ignore_ascii_case(function_name, &["min", "max", "hypot"]) {
11530 return argument_count >= 1;
11531 }
11532 if function_name.eq_ignore_ascii_case("clamp") {
11533 return argument_count == 3;
11534 }
11535 if function_name.eq_ignore_ascii_case("round") {
11536 return (2..=3).contains(&argument_count);
11537 }
11538 if function_name.eq_ignore_ascii_case("log") {
11539 return (1..=2).contains(&argument_count);
11540 }
11541 if matches_ignore_ascii_case(function_name, &["mod", "rem", "pow", "atan2"]) {
11542 return argument_count == 2;
11543 }
11544 if matches_ignore_ascii_case(
11545 function_name,
11546 &[
11547 "sin", "cos", "tan", "asin", "acos", "atan", "sqrt", "exp", "abs", "sign",
11548 ],
11549 ) {
11550 return argument_count == 1;
11551 }
11552 if function_name.eq_ignore_ascii_case("color-mix") {
11553 return argument_count == 3;
11554 }
11555 if function_name.eq_ignore_ascii_case("light-dark") {
11556 return argument_count == 2;
11557 }
11558 if function_name.eq_ignore_ascii_case("contrast-color") {
11559 return argument_count == 1;
11560 }
11561 true
11562}
11563
11564fn function_requires_filled_top_level_arguments(function_name: &str) -> bool {
11565 function_name.eq_ignore_ascii_case("calc")
11566 || matches_ignore_ascii_case(function_name, VALUES_L4_MATH_FUNCTION_NAMES)
11567 || matches_ignore_ascii_case(
11568 function_name,
11569 &["color-mix", "light-dark", "contrast-color"],
11570 )
11571}
11572
11573fn at_rule_prelude_head_is_custom_property_name(kind: SyntaxKind) -> bool {
11574 kind == SyntaxKind::CustomPropertyName || is_interpolation_start(kind)
11575}
11576
11577fn at_rule_prelude_head_is_custom_ident(kind: SyntaxKind) -> bool {
11578 kind == SyntaxKind::Ident || is_interpolation_start(kind)
11579}
11580
11581fn is_dynamic_function_argument_head(kind: SyntaxKind) -> bool {
11582 matches!(
11583 kind,
11584 SyntaxKind::ScssVariable
11585 | SyntaxKind::LessVariable
11586 | SyntaxKind::ScssInterpolationStart
11587 | SyntaxKind::LessInterpolationStart
11588 )
11589}
11590
11591fn is_scss_module_source_token(kind: SyntaxKind) -> bool {
11592 matches!(
11593 kind,
11594 SyntaxKind::String | SyntaxKind::Url | SyntaxKind::ScssInterpolationStart
11595 )
11596}
11597
11598fn is_scss_module_namespace_token(kind: SyntaxKind) -> bool {
11599 matches!(
11600 kind,
11601 SyntaxKind::Ident | SyntaxKind::Star | SyntaxKind::ScssInterpolationStart
11602 )
11603}
11604
11605fn is_scss_module_visibility_name_token(kind: SyntaxKind) -> bool {
11606 matches!(
11607 kind,
11608 SyntaxKind::Ident
11609 | SyntaxKind::ScssVariable
11610 | SyntaxKind::ScssPlaceholder
11611 | SyntaxKind::ScssInterpolationStart
11612 )
11613}
11614
11615fn is_css_module_from_source_token(kind: SyntaxKind, text: &str) -> bool {
11616 matches!(
11617 kind,
11618 SyntaxKind::String
11619 | SyntaxKind::Url
11620 | SyntaxKind::ScssInterpolationStart
11621 | SyntaxKind::LessInterpolationStart
11622 ) || (kind == SyntaxKind::Ident && text == "global")
11623}
11624
11625fn is_scss_control_rule_kind(kind: SyntaxKind) -> bool {
11626 matches!(
11627 kind,
11628 SyntaxKind::ScssControlIf
11629 | SyntaxKind::ScssControlElse
11630 | SyntaxKind::ScssControlEach
11631 | SyntaxKind::ScssControlFor
11632 | SyntaxKind::ScssControlWhile
11633 )
11634}
11635
11636fn matches_ignore_ascii_case(value: &str, candidates: &[&str]) -> bool {
11637 candidates
11638 .iter()
11639 .any(|candidate| value.eq_ignore_ascii_case(candidate))
11640}
11641
11642fn css_module_scope_function_kind(text: &str) -> Option<SyntaxKind> {
11643 match text {
11644 "local" => Some(SyntaxKind::CssModuleLocalBlock),
11645 "global" => Some(SyntaxKind::CssModuleGlobalBlock),
11646 _ => None,
11647 }
11648}
11649
11650fn text_range(start: usize, end: usize) -> TextRange {
11651 TextRange::new(TextSize::from(start as u32), TextSize::from(end as u32))
11652}
11653
11654#[cfg(test)]
11655mod tests;