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