1use cstree::{
8 Syntax,
9 build::GreenNodeBuilder,
10 green::GreenNode,
11 interning::TokenInterner,
12 syntax::SyntaxNode,
13 text::{TextRange, TextSize},
14};
15use omena_interner::{
16 NameKind, intern_class_name, intern_css_ident, intern_custom_property_name, intern_file_path,
17 intern_keyframes_name, intern_mixin_name, intern_property_name, intern_selector_key,
18};
19pub use omena_syntax::StyleDialect;
20use omena_syntax::SyntaxKind;
21use serde::Serialize;
22use std::{
23 collections::{BTreeMap, BTreeSet},
24 sync::Arc,
25};
26
27mod public_product;
28pub use public_product::{
29 ParserCanonicalCandidateBundleV0, ParserCanonicalProducerSignalV0, ParserEvaluatorCandidatesV0,
30 ParserIndexSummaryV0, dialect_for_path, summarize_css_modules_intermediate,
31 summarize_parser_canonical_candidate, summarize_parser_canonical_producer_signal,
32 summarize_parser_evaluator_candidates,
33};
34
35const VALUES_L4_MATH_FUNCTION_NAMES: &[&str] = &[
36 "min", "max", "clamp", "round", "mod", "rem", "sin", "cos", "tan", "asin", "acos", "atan",
37 "atan2", "pow", "sqrt", "hypot", "log", "exp", "abs", "sign",
38];
39
40const CSS_COLOR_FUNCTION_NAMES: &[&str] = &[
41 "rgb",
42 "rgba",
43 "hsl",
44 "hsla",
45 "hwb",
46 "lab",
47 "lch",
48 "oklab",
49 "oklch",
50 "color",
51 "color-mix",
52 "device-cmyk",
53 "light-dark",
54 "contrast-color",
55];
56
57const CSS_GRADIENT_FUNCTION_NAMES: &[&str] = &[
58 "linear-gradient",
59 "radial-gradient",
60 "conic-gradient",
61 "repeating-linear-gradient",
62 "repeating-radial-gradient",
63 "repeating-conic-gradient",
64];
65
66const CSS_TRANSFORM_FUNCTION_NAMES: &[&str] = &[
67 "matrix",
68 "matrix3d",
69 "translate",
70 "translate3d",
71 "translateX",
72 "translateY",
73 "translateZ",
74 "scale",
75 "scale3d",
76 "scaleX",
77 "scaleY",
78 "scaleZ",
79 "rotate",
80 "rotate3d",
81 "rotateX",
82 "rotateY",
83 "rotateZ",
84 "skew",
85 "skewX",
86 "skewY",
87 "perspective",
88];
89
90const CSS_FILTER_FUNCTION_NAMES: &[&str] = &[
91 "blur",
92 "brightness",
93 "contrast",
94 "drop-shadow",
95 "grayscale",
96 "hue-rotate",
97 "invert",
98 "opacity",
99 "saturate",
100 "sepia",
101];
102
103const CSS_IMAGE_FUNCTION_NAMES: &[&str] = &["image", "image-set", "cross-fade", "element", "paint"];
104
105const CSS_SHAPE_FUNCTION_NAMES: &[&str] = &[
106 "path", "shape", "ray", "inset", "circle", "ellipse", "polygon",
107];
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
110#[serde(rename_all = "camelCase")]
111pub struct ParserByteSpanV0 {
112 pub start: usize,
113 pub end: usize,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
117#[serde(rename_all = "camelCase")]
118pub struct ParserPositionV0 {
119 pub line: usize,
120 pub character: usize,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Default)]
124#[serde(rename_all = "camelCase")]
125pub struct ParserRangeV0 {
126 pub start: ParserPositionV0,
127 pub end: ParserPositionV0,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum StyleLanguage {
132 Css,
133 Scss,
134 Less,
135}
136
137impl StyleLanguage {
138 pub fn from_module_path(path: &str) -> Option<Self> {
139 if path.ends_with(".module.css") || path.ends_with(".css") {
140 Some(Self::Css)
141 } else if path.ends_with(".module.scss") || path.ends_with(".scss") {
142 Some(Self::Scss)
143 } else if path.ends_with(".module.less") || path.ends_with(".less") {
144 Some(Self::Less)
145 } else {
146 None
147 }
148 }
149}
150
151#[derive(Debug, Clone)]
152pub struct ParseResult {
153 green: GreenNode,
154 interner: Option<Arc<TokenInterner>>,
155 errors: Vec<ParseError>,
156 token_count: usize,
157 dialect: StyleDialect,
158}
159
160impl PartialEq for ParseResult {
161 fn eq(&self, other: &Self) -> bool {
162 self.green == other.green
163 && self.errors == other.errors
164 && self.token_count == other.token_count
165 && self.dialect == other.dialect
166 }
167}
168
169impl Eq for ParseResult {}
170
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct LexResult {
173 tokens: Vec<LexedToken>,
174 errors: Vec<ParseError>,
175 dialect: StyleDialect,
176}
177
178impl LexResult {
179 pub fn tokens(&self) -> &[LexedToken] {
180 &self.tokens
181 }
182
183 pub fn errors(&self) -> &[ParseError] {
184 &self.errors
185 }
186
187 pub fn dialect(&self) -> StyleDialect {
188 self.dialect
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct LexedToken {
194 pub kind: SyntaxKind,
195 pub range: TextRange,
196 pub text: String,
197}
198
199impl ParseResult {
200 pub fn green(&self) -> &GreenNode {
201 &self.green
202 }
203
204 pub fn syntax(&self) -> SyntaxNode<SyntaxKind> {
205 if let Some(interner) = &self.interner {
206 return SyntaxNode::new_root_with_resolver(self.green.clone(), Arc::clone(interner))
207 .syntax()
208 .clone();
209 }
210 SyntaxNode::new_root(self.green.clone())
211 }
212
213 pub fn source_text(&self) -> Option<String> {
214 let syntax = self.syntax();
215 syntax
216 .try_resolved()
217 .map(|resolved| resolved.text().to_string())
218 }
219
220 pub fn errors(&self) -> &[ParseError] {
221 &self.errors
222 }
223
224 pub fn token_count(&self) -> usize {
225 self.token_count
226 }
227
228 pub fn dialect(&self) -> StyleDialect {
229 self.dialect
230 }
231
232 pub fn cst(&self) -> ParsedCst {
233 ParsedCst::new(self.syntax())
234 }
235}
236
237#[derive(Debug, Clone, PartialEq, Eq)]
238pub struct ParseError {
239 pub code: ParseErrorCode,
240 pub range: TextRange,
241 pub message: &'static str,
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
245pub enum ParseErrorCode {
246 UnterminatedBlockComment,
247 UnterminatedString,
248 UnexpectedCharacter,
249 ExpectedSelectorName,
250 UnterminatedAttributeSelector,
251 ExpectedValue,
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
255pub enum ParseEntryPoint {
256 Stylesheet,
257 RuleList,
258 Rule,
259 DeclarationList,
260 Declaration,
261 Value,
262 ComponentValue,
263 ComponentValueList,
264 CommaSeparatedComponentValueList,
265 SimpleBlock,
266}
267
268#[derive(Debug, Clone, PartialEq, Eq)]
269pub struct ParserBoundarySummary {
270 pub product: &'static str,
271 pub tree_model: &'static str,
272 pub parser_track: &'static str,
273 pub dialect_count: usize,
274 pub shared_name_kind_count: usize,
275 pub ready_surfaces: Vec<&'static str>,
276 pub not_ready_surfaces: Vec<&'static str>,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
280pub struct ParserSemanticNameConsumptionSummaryV0 {
281 pub product: &'static str,
282 pub dialect: StyleDialect,
283 pub semantic_name_count: usize,
284 pub interned_name_count: usize,
285 pub invalid_name_count: usize,
286 pub class_name_count: usize,
287 pub css_ident_count: usize,
288 pub property_name_count: usize,
289 pub selector_key_count: usize,
290 pub custom_property_name_count: usize,
291 pub keyframes_name_count: usize,
292 pub mixin_name_count: usize,
293 pub file_path_count: usize,
294 pub ready_surfaces: Vec<&'static str>,
295}
296
297#[derive(Debug, Clone, PartialEq, Eq)]
298pub struct ParserCstEquivalenceSummaryV0 {
299 pub product: &'static str,
300 pub dialect: StyleDialect,
301 pub root_kind: SyntaxKind,
302 pub parser_node_count: usize,
303 pub parser_token_count: usize,
304 pub typed_wrapper_count: usize,
305 pub source_text_round_trip_ready: bool,
306 pub syntax_kind_round_trip_ready: bool,
307 pub zero_unknown_kind_ready: bool,
308 pub typed_cst_wrapper_ready: bool,
309 pub ready_surfaces: Vec<&'static str>,
310}
311
312#[derive(Debug, Clone, PartialEq, Eq)]
313pub struct ParserPrattValueCoverageSummaryV0 {
314 pub product: &'static str,
315 pub infix_operator_kinds: Vec<SyntaxKind>,
316 pub prefix_operator_kinds: Vec<SyntaxKind>,
317 pub value_expression_node_kinds: Vec<SyntaxKind>,
318 pub specialized_function_family_count: usize,
319 pub css_values_l4_math_function_count: usize,
320 pub css_color_function_count: usize,
321 pub ready_surfaces: Vec<&'static str>,
322 pub next_surfaces: Vec<&'static str>,
323}
324
325#[derive(Debug, Clone, PartialEq, Eq)]
326pub struct ParserRecursiveDescentCoverageSummaryV0 {
327 pub product: &'static str,
328 pub dialect_count: usize,
329 pub entry_point_count: usize,
330 pub selector_surface_count: usize,
331 pub at_rule_surface_count: usize,
332 pub dialect_extension_surface_count: usize,
333 pub recovery_surface_count: usize,
334 pub ready_surfaces: Vec<&'static str>,
335 pub next_surfaces: Vec<&'static str>,
336}
337
338#[derive(Debug, Clone, PartialEq, Eq)]
339struct ParserSemanticNameCandidateV0 {
340 kind: NameKind,
341 text: String,
342}
343
344#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct ParsedStyleFacts {
346 pub product: &'static str,
347 pub dialect: StyleDialect,
348 pub selector_count: usize,
349 pub selectors: Vec<ParsedSelectorFact>,
350 pub variable_count: usize,
351 pub variables: Vec<ParsedVariableFact>,
352 pub sass_symbol_count: usize,
353 pub sass_symbols: Vec<ParsedSassSymbolFact>,
354 pub sass_include_count: usize,
355 pub sass_includes: Vec<ParsedSassIncludeFact>,
356 pub sass_module_edge_count: usize,
357 pub sass_module_edges: Vec<ParsedSassModuleEdgeFact>,
358 pub animation_count: usize,
359 pub animations: Vec<ParsedAnimationFact>,
360 pub css_module_value_count: usize,
361 pub css_module_values: Vec<ParsedCssModuleValueFact>,
362 pub css_module_value_import_edge_count: usize,
363 pub css_module_value_import_edges: Vec<ParsedCssModuleValueImportEdgeFact>,
364 pub css_module_value_definition_edge_count: usize,
365 pub css_module_value_definition_edges: Vec<ParsedCssModuleValueDefinitionEdgeFact>,
366 pub css_module_composes_count: usize,
367 pub css_module_composes: Vec<ParsedCssModuleComposesFact>,
368 pub css_module_composes_edge_count: usize,
369 pub css_module_composes_edges: Vec<ParsedCssModuleComposesEdgeFact>,
370 pub icss_count: usize,
371 pub icss: Vec<ParsedIcssFact>,
372 pub icss_import_edge_count: usize,
373 pub icss_import_edges: Vec<ParsedIcssImportEdgeFact>,
374 pub icss_export_edge_count: usize,
375 pub icss_export_edges: Vec<ParsedIcssExportEdgeFact>,
376 pub at_rule_count: usize,
377 pub at_rules: Vec<ParsedAtRuleFact>,
378 pub error_count: usize,
379}
380
381#[derive(Debug, Clone, PartialEq, Eq)]
382pub struct ParsedSelectorFact {
383 pub kind: ParsedSelectorFactKind,
384 pub name: String,
385 pub range: TextRange,
386}
387
388#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
389pub enum ParsedSelectorFactKind {
390 Class,
391 Id,
392 Placeholder,
393}
394
395#[derive(Debug, Clone, PartialEq, Eq)]
396pub struct ParsedVariableFact {
397 pub kind: ParsedVariableFactKind,
398 pub name: String,
399 pub range: TextRange,
400}
401
402#[derive(Debug, Clone, Copy, PartialEq, Eq)]
403pub enum ParsedVariableFactKind {
404 ScssDeclaration,
405 ScssReference,
406 LessDeclaration,
407 LessReference,
408 CustomPropertyDeclaration,
409 CustomPropertyReference,
410}
411
412#[derive(Debug, Clone, PartialEq, Eq)]
413pub struct ParsedSassSymbolFact {
414 pub kind: ParsedSassSymbolFactKind,
415 pub symbol_kind: &'static str,
416 pub name: String,
417 pub role: &'static str,
418 pub namespace: Option<String>,
419 pub range: TextRange,
420}
421
422#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
423pub enum ParsedSassSymbolFactKind {
424 VariableDeclaration,
425 VariableReference,
426 MixinDeclaration,
427 MixinInclude,
428 FunctionDeclaration,
429 FunctionCall,
430}
431
432#[derive(Debug, Clone, PartialEq, Eq)]
433pub struct ParsedSassIncludeFact {
434 pub name: String,
435 pub namespace: Option<String>,
436 pub params: String,
437 pub range: TextRange,
438}
439
440#[derive(Debug, Clone, PartialEq, Eq)]
441pub struct ParsedSassModuleEdgeFact {
442 pub kind: ParsedSassModuleEdgeFactKind,
443 pub source: String,
444 pub namespace_kind: Option<&'static str>,
445 pub namespace: 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 ],
2544 not_ready_surfaces: vec![
2545 "completeExternalSpecMirror",
2546 "fullPropertyValueGrammarRegistry",
2547 "productCutover",
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 push_sass_module_edge_fact(
8725 &mut edges,
8726 &mut seen,
8727 ParsedSassModuleEdgeFact {
8728 kind,
8729 source: source_name,
8730 namespace_kind,
8731 namespace,
8732 visibility_filter_kind,
8733 visibility_filter_names,
8734 range: source.range,
8735 },
8736 );
8737 }
8738
8739 edges
8740}
8741
8742fn sass_module_edge_kind(text: &str) -> Option<ParsedSassModuleEdgeFactKind> {
8743 match text {
8744 text if text.eq_ignore_ascii_case("@use") => Some(ParsedSassModuleEdgeFactKind::Use),
8745 text if text.eq_ignore_ascii_case("@forward") => {
8746 Some(ParsedSassModuleEdgeFactKind::Forward)
8747 }
8748 text if text.eq_ignore_ascii_case("@import") => Some(ParsedSassModuleEdgeFactKind::Import),
8749 _ => None,
8750 }
8751}
8752
8753fn collect_sass_import_module_edges(
8754 tokens: &[Token<'_>],
8755 start: usize,
8756 end: usize,
8757 edges: &mut Vec<ParsedSassModuleEdgeFact>,
8758 seen: &mut BTreeSet<(ParsedSassModuleEdgeFactKind, String, u32, u32)>,
8759) {
8760 for token in &tokens[start..end] {
8761 if !matches!(token.kind, SyntaxKind::String | SyntaxKind::Url) {
8762 continue;
8763 }
8764 push_sass_module_edge_fact(
8765 edges,
8766 seen,
8767 ParsedSassModuleEdgeFact {
8768 kind: ParsedSassModuleEdgeFactKind::Import,
8769 source: css_module_value_source_name(*token),
8770 namespace_kind: None,
8771 namespace: None,
8772 visibility_filter_kind: None,
8773 visibility_filter_names: Vec::new(),
8774 range: token.range,
8775 },
8776 );
8777 }
8778}
8779
8780fn sass_module_use_namespace(
8781 tokens: &[Token<'_>],
8782 source: &str,
8783 start: usize,
8784 end: usize,
8785) -> (Option<&'static str>, Option<String>) {
8786 let Some(as_index) = top_level_token_text_index(tokens, start, end, "as") else {
8787 return (
8788 Some("default"),
8789 sass_module_default_namespace(source).map(str::to_string),
8790 );
8791 };
8792 let Some(namespace_index) = next_non_trivia_token_index_until(tokens, as_index + 1, end) else {
8793 return (Some("invalid"), None);
8794 };
8795 let namespace = tokens[namespace_index];
8796 match namespace.kind {
8797 SyntaxKind::Star => (Some("wildcard"), None),
8798 SyntaxKind::Ident => (Some("alias"), Some(namespace.text.to_string())),
8799 _ => (Some("invalid"), None),
8800 }
8801}
8802
8803fn sass_module_forward_visibility_filter(
8804 tokens: &[Token<'_>],
8805 start: usize,
8806 end: usize,
8807) -> (Option<&'static str>, Vec<String>) {
8808 let show_index = top_level_token_text_index(tokens, start, end, "show");
8809 let hide_index = top_level_token_text_index(tokens, start, end, "hide");
8810 let (filter_kind, filter_index) = match (show_index, hide_index) {
8811 (Some(show_index), Some(hide_index)) if show_index <= hide_index => ("show", show_index),
8812 (Some(_), Some(hide_index)) => ("hide", hide_index),
8813 (Some(show_index), None) => ("show", show_index),
8814 (None, Some(hide_index)) => ("hide", hide_index),
8815 (None, None) => return (None, Vec::new()),
8816 };
8817 let clause_end =
8818 top_level_token_text_index(tokens, filter_index + 1, end, "with").unwrap_or(end);
8819 (
8820 Some(filter_kind),
8821 sass_module_visibility_filter_names(tokens, filter_index + 1, clause_end),
8822 )
8823}
8824
8825fn sass_module_visibility_filter_names(
8826 tokens: &[Token<'_>],
8827 start: usize,
8828 end: usize,
8829) -> Vec<String> {
8830 let mut names = BTreeSet::new();
8831 for token in &tokens[start..end] {
8832 match token.kind {
8833 SyntaxKind::Ident | SyntaxKind::ScssVariable => {
8834 if matches_ignore_ascii_case(token.text, &["show", "hide", "with", "as"]) {
8835 continue;
8836 }
8837 let name = token.text.trim_start_matches('$');
8838 if !name.is_empty() {
8839 names.insert(name.to_string());
8840 }
8841 }
8842 _ => {}
8843 }
8844 }
8845 names.into_iter().collect()
8846}
8847
8848fn sass_module_default_namespace(source: &str) -> Option<&str> {
8849 let basename = source
8850 .rsplit(['/', '\\', ':'])
8851 .next()
8852 .unwrap_or(source)
8853 .trim_start_matches('_');
8854 let namespace = basename.split('.').next().unwrap_or(basename);
8855 (!namespace.is_empty()).then_some(namespace)
8856}
8857
8858fn push_sass_module_edge_fact(
8859 edges: &mut Vec<ParsedSassModuleEdgeFact>,
8860 seen: &mut BTreeSet<(ParsedSassModuleEdgeFactKind, String, u32, u32)>,
8861 edge: ParsedSassModuleEdgeFact,
8862) {
8863 let start: u32 = edge.range.start().into();
8864 let end: u32 = edge.range.end().into();
8865 if seen.insert((edge.kind, edge.source.clone(), start, end)) {
8866 edges.push(edge);
8867 }
8868}
8869
8870fn collect_css_module_value_facts_from_tokens(
8871 tokens: &[Token<'_>],
8872) -> Vec<ParsedCssModuleValueFact> {
8873 let mut values = Vec::new();
8874 let mut seen = BTreeSet::new();
8875 let value_path_aliases = collect_css_module_value_path_aliases_from_tokens(tokens);
8876 for (index, token) in tokens.iter().enumerate() {
8877 if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
8878 continue;
8879 }
8880
8881 let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
8882 let end = css_module_value_statement_end(tokens, start);
8883 let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
8884 let from_index = top_level_token_text_index(tokens, start, end, "from");
8885
8886 if let Some(from_index) = from_index
8887 && match colon_index {
8888 Some(colon_index) => from_index < colon_index,
8889 None => true,
8890 }
8891 {
8892 collect_css_module_value_import_facts(
8893 tokens,
8894 start,
8895 from_index,
8896 end,
8897 &value_path_aliases,
8898 &mut values,
8899 &mut seen,
8900 );
8901 continue;
8902 }
8903
8904 if let Some(colon_index) = colon_index {
8905 if css_module_value_path_alias_from_tokens(tokens, start, colon_index, end).is_some() {
8906 continue;
8907 }
8908 collect_css_module_value_definition_facts(
8909 tokens,
8910 start,
8911 colon_index,
8912 &mut values,
8913 &mut seen,
8914 );
8915 collect_css_module_value_reference_facts(
8916 tokens,
8917 colon_index + 1,
8918 end,
8919 &mut values,
8920 &mut seen,
8921 );
8922 } else {
8923 collect_css_module_value_definition_facts(tokens, start, end, &mut values, &mut seen);
8924 }
8925 }
8926 let local_value_names = values
8927 .iter()
8928 .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
8929 .map(|value| value.name.clone())
8930 .collect::<BTreeSet<_>>();
8931 collect_css_module_value_declaration_reference_facts(
8932 tokens,
8933 0,
8934 tokens.len(),
8935 &local_value_names,
8936 &mut values,
8937 &mut seen,
8938 );
8939 values
8940}
8941
8942fn collect_css_module_value_path_aliases_from_tokens(
8943 tokens: &[Token<'_>],
8944) -> BTreeMap<String, String> {
8945 let mut aliases = BTreeMap::new();
8946 for (index, token) in tokens.iter().enumerate() {
8947 if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
8948 continue;
8949 }
8950
8951 let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
8952 let end = css_module_value_statement_end(tokens, start);
8953 let Some(colon_index) = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon)
8954 else {
8955 continue;
8956 };
8957 if top_level_token_text_index(tokens, start, end, "from").is_some() {
8958 continue;
8959 }
8960 if let Some((name, target)) =
8961 css_module_value_path_alias_from_tokens(tokens, start, colon_index, end)
8962 {
8963 aliases.insert(name, target);
8964 }
8965 }
8966 aliases
8967}
8968
8969fn css_module_value_path_alias_from_tokens(
8970 tokens: &[Token<'_>],
8971 start: usize,
8972 colon_index: usize,
8973 end: usize,
8974) -> Option<(String, String)> {
8975 let name_index = next_non_trivia_token_index_until(tokens, start, colon_index)?;
8976 let name_token = tokens[name_index];
8977 if !css_module_value_name_token_can_define(name_token) {
8978 return None;
8979 }
8980 let source_index = next_non_trivia_token_index_until(tokens, colon_index + 1, end)?;
8981 let source_token = tokens[source_index];
8982 if !matches!(source_token.kind, SyntaxKind::String | SyntaxKind::Url) {
8983 return None;
8984 }
8985 let source = css_module_value_source_name(source_token);
8986 css_module_value_source_looks_like_style_request(&source)
8987 .then(|| (name_token.text.to_string(), source))
8988}
8989
8990fn css_module_value_statement_end(tokens: &[Token<'_>], start: usize) -> usize {
8991 let mut index = start;
8992 let mut paren_depth = 0usize;
8993 let mut bracket_depth = 0usize;
8994 while index < tokens.len() {
8995 match tokens[index].kind {
8996 SyntaxKind::LeftParen => paren_depth += 1,
8997 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
8998 SyntaxKind::LeftBracket => bracket_depth += 1,
8999 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9000 SyntaxKind::Semicolon
9001 | SyntaxKind::SassOptionalSemicolon
9002 | SyntaxKind::LeftBrace
9003 | SyntaxKind::RightBrace
9004 | SyntaxKind::SassIndent
9005 | SyntaxKind::SassDedent
9006 if paren_depth == 0 && bracket_depth == 0 =>
9007 {
9008 return index;
9009 }
9010 _ => {}
9011 }
9012 index += 1;
9013 }
9014 index
9015}
9016
9017fn collect_css_module_value_import_facts(
9018 tokens: &[Token<'_>],
9019 start: usize,
9020 from_index: usize,
9021 end: usize,
9022 value_path_aliases: &BTreeMap<String, String>,
9023 values: &mut Vec<ParsedCssModuleValueFact>,
9024 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9025) {
9026 collect_css_module_value_import_names(tokens, start, from_index, values, seen);
9027 if let Some((source_name, source_range)) =
9028 css_module_value_import_edge_source(tokens, from_index + 1, end, value_path_aliases)
9029 {
9030 push_css_module_value_fact(
9031 values,
9032 seen,
9033 ParsedCssModuleValueFactKind::ImportSource,
9034 source_name,
9035 source_range,
9036 );
9037 }
9038}
9039
9040fn collect_css_module_value_import_edge_facts_from_tokens(
9041 tokens: &[Token<'_>],
9042) -> Vec<ParsedCssModuleValueImportEdgeFact> {
9043 let mut edges = Vec::new();
9044 let value_path_aliases = collect_css_module_value_path_aliases_from_tokens(tokens);
9045 for (index, token) in tokens.iter().enumerate() {
9046 if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9047 continue;
9048 }
9049
9050 let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9051 let end = css_module_value_statement_end(tokens, start);
9052 let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9053 let from_index = top_level_token_text_index(tokens, start, end, "from");
9054 let Some(from_index) = from_index else {
9055 continue;
9056 };
9057 if colon_index.is_some_and(|colon_index| from_index > colon_index) {
9058 continue;
9059 }
9060 let Some((import_source, _source_range)) =
9061 css_module_value_import_edge_source(tokens, from_index + 1, end, &value_path_aliases)
9062 else {
9063 continue;
9064 };
9065
9066 collect_css_module_value_import_edges(tokens, start, from_index, import_source, &mut edges);
9067 }
9068 edges
9069}
9070
9071fn collect_css_module_value_definition_edge_facts_from_tokens(
9072 tokens: &[Token<'_>],
9073) -> Vec<ParsedCssModuleValueDefinitionEdgeFact> {
9074 let mut edges = Vec::new();
9075 for (index, token) in tokens.iter().enumerate() {
9076 if token.kind != SyntaxKind::AtKeyword || !token.text.eq_ignore_ascii_case("@value") {
9077 continue;
9078 }
9079
9080 let start = skip_trivia_tokens(tokens, index + 1, tokens.len());
9081 let end = css_module_value_statement_end(tokens, start);
9082 let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon);
9083 let from_index = top_level_token_text_index(tokens, start, end, "from");
9084 let Some(colon_index) = colon_index else {
9085 continue;
9086 };
9087 if from_index.is_some_and(|from_index| from_index < colon_index) {
9088 continue;
9089 }
9090
9091 let definition_names = collect_css_module_value_definition_edge_names(
9092 tokens,
9093 start,
9094 colon_index,
9095 |tokens, index| css_module_value_name_token_can_define(tokens[index]),
9096 );
9097 let reference_names = collect_css_module_value_definition_edge_names(
9098 tokens,
9099 colon_index + 1,
9100 end,
9101 css_module_value_reference_token_can_be_name,
9102 );
9103 if reference_names.is_empty() {
9104 continue;
9105 }
9106 let range_end = end
9107 .checked_sub(1)
9108 .and_then(|end| tokens.get(end))
9109 .map(|token| token.range.end())
9110 .unwrap_or_else(|| tokens[index].range.end());
9111
9112 for definition_name in definition_names {
9113 edges.push(ParsedCssModuleValueDefinitionEdgeFact {
9114 definition_name,
9115 reference_names: reference_names.clone(),
9116 range: TextRange::new(tokens[index].range.start(), range_end),
9117 });
9118 }
9119 }
9120 edges
9121}
9122
9123fn collect_css_module_value_definition_edge_names(
9124 tokens: &[Token<'_>],
9125 start: usize,
9126 end: usize,
9127 predicate: impl Fn(&[Token<'_>], usize) -> bool,
9128) -> Vec<String> {
9129 let mut names = Vec::new();
9130 let mut index = start;
9131 while index < end {
9132 if predicate(tokens, index) && !names.iter().any(|name| name == tokens[index].text) {
9133 names.push(tokens[index].text.to_string());
9134 }
9135 index += 1;
9136 }
9137 names
9138}
9139
9140fn css_module_value_import_edge_source(
9141 tokens: &[Token<'_>],
9142 start: usize,
9143 end: usize,
9144 value_path_aliases: &BTreeMap<String, String>,
9145) -> Option<(String, TextRange)> {
9146 let source_index = next_non_trivia_token_index_until(tokens, start, end)?;
9147 let token = tokens[source_index];
9148 if matches!(token.kind, SyntaxKind::String | SyntaxKind::Url) {
9149 return Some((css_module_value_source_name(token), token.range));
9150 }
9151 if css_module_value_name_token_can_define(token) {
9152 return css_module_value_source_alias_target(token.text, token.range, value_path_aliases);
9153 }
9154 None
9155}
9156
9157fn css_module_value_source_alias_target(
9158 name: &str,
9159 range: TextRange,
9160 value_path_aliases: &BTreeMap<String, String>,
9161) -> Option<(String, TextRange)> {
9162 value_path_aliases
9163 .get(name)
9164 .map(|source| (source.clone(), range))
9165}
9166
9167fn collect_css_module_value_import_edges(
9168 tokens: &[Token<'_>],
9169 start: usize,
9170 end: usize,
9171 import_source: String,
9172 edges: &mut Vec<ParsedCssModuleValueImportEdgeFact>,
9173) {
9174 let mut index = start;
9175 while index < end {
9176 let token = tokens[index];
9177 if !css_module_value_name_token_can_define(token) {
9178 index += 1;
9179 continue;
9180 }
9181 if previous_non_trivia_token_index(tokens, index, start)
9182 .is_some_and(|previous| tokens[previous].text == "as")
9183 {
9184 index += 1;
9185 continue;
9186 }
9187 let remote_name = token.text.to_string();
9188 let mut local_name = remote_name.clone();
9189 let mut local_range = token.range;
9190 if let Some(as_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
9191 && tokens[as_index].text == "as"
9192 && let Some(local_index) = next_non_trivia_token_index_until(tokens, as_index + 1, end)
9193 && css_module_value_name_token_can_define(tokens[local_index])
9194 {
9195 local_name = tokens[local_index].text.to_string();
9196 local_range = tokens[local_index].range;
9197 index = local_index + 1;
9198 } else {
9199 index += 1;
9200 }
9201 edges.push(ParsedCssModuleValueImportEdgeFact {
9202 remote_name,
9203 local_name,
9204 import_source: import_source.clone(),
9205 local_range,
9206 remote_range: token.range,
9207 range: token.range,
9208 });
9209 }
9210}
9211
9212fn collect_css_module_value_import_names(
9213 tokens: &[Token<'_>],
9214 start: usize,
9215 end: usize,
9216 values: &mut Vec<ParsedCssModuleValueFact>,
9217 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9218) {
9219 let mut index = start;
9220 while index < end {
9221 let token = tokens[index];
9222 if css_module_value_name_token_can_define(token) {
9223 let previous = previous_non_trivia_token_index(tokens, index, start);
9224 let next = next_non_trivia_token_index_until(tokens, index + 1, end);
9225 let kind = if previous.is_some_and(|previous| tokens[previous].text == "as") {
9226 Some(ParsedCssModuleValueFactKind::Definition)
9227 } else if next.is_some_and(|next| tokens[next].text == "as") {
9228 Some(ParsedCssModuleValueFactKind::Reference)
9229 } else {
9230 Some(ParsedCssModuleValueFactKind::Definition)
9231 };
9232 if let Some(kind) = kind {
9233 push_css_module_value_fact(values, seen, kind, token.text.to_string(), token.range);
9234 }
9235 }
9236 index += 1;
9237 }
9238}
9239
9240fn collect_css_module_value_definition_facts(
9241 tokens: &[Token<'_>],
9242 start: usize,
9243 end: usize,
9244 values: &mut Vec<ParsedCssModuleValueFact>,
9245 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9246) {
9247 let mut index = start;
9248 while index < end {
9249 let token = tokens[index];
9250 if css_module_value_name_token_can_define(token) {
9251 push_css_module_value_fact(
9252 values,
9253 seen,
9254 ParsedCssModuleValueFactKind::Definition,
9255 token.text.to_string(),
9256 token.range,
9257 );
9258 }
9259 index += 1;
9260 }
9261}
9262
9263fn collect_css_module_value_reference_facts(
9264 tokens: &[Token<'_>],
9265 start: usize,
9266 end: usize,
9267 values: &mut Vec<ParsedCssModuleValueFact>,
9268 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9269) {
9270 let mut index = start;
9271 let mut paren_depth = 0usize;
9272 let mut bracket_depth = 0usize;
9273 while index < end {
9274 match tokens[index].kind {
9275 SyntaxKind::LeftParen => paren_depth += 1,
9276 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9277 SyntaxKind::LeftBracket => bracket_depth += 1,
9278 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9279 _ => {}
9280 }
9281 if paren_depth == 0
9282 && bracket_depth == 0
9283 && css_module_value_reference_token_can_be_name(tokens, index)
9284 {
9285 push_css_module_value_fact(
9286 values,
9287 seen,
9288 ParsedCssModuleValueFactKind::Reference,
9289 tokens[index].text.to_string(),
9290 tokens[index].range,
9291 );
9292 }
9293 index += 1;
9294 }
9295}
9296
9297fn collect_css_module_value_declaration_reference_facts(
9298 tokens: &[Token<'_>],
9299 start: usize,
9300 end: usize,
9301 local_value_names: &BTreeSet<String>,
9302 values: &mut Vec<ParsedCssModuleValueFact>,
9303 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9304) {
9305 if local_value_names.is_empty() {
9306 return;
9307 }
9308
9309 let mut index = start;
9310 while index < end {
9311 index = skip_trivia_tokens(tokens, index, end);
9312 if index >= end {
9313 break;
9314 }
9315
9316 if tokens[index].kind == SyntaxKind::AtKeyword {
9317 let block = find_block_after_header(tokens, index, end);
9318 if let Some((open, close)) = block {
9319 if style_wrapper_at_rule(tokens[index].text) {
9320 collect_css_module_value_declaration_reference_facts(
9321 tokens,
9322 open + 1,
9323 close,
9324 local_value_names,
9325 values,
9326 seen,
9327 );
9328 }
9329 index = close + 1;
9330 } else {
9331 index = skip_statement(tokens, index, end);
9332 }
9333 continue;
9334 }
9335
9336 let statement_end = css_module_value_statement_end(tokens, index);
9337 if statement_end < end && tokens[statement_end].kind == SyntaxKind::LeftBrace {
9338 if let Some(close) = matching_right_brace(tokens, statement_end, end) {
9339 collect_css_module_value_declaration_reference_facts(
9340 tokens,
9341 statement_end + 1,
9342 close,
9343 local_value_names,
9344 values,
9345 seen,
9346 );
9347 index = close + 1;
9348 } else {
9349 index = statement_end + 1;
9350 }
9351 continue;
9352 }
9353
9354 if let Some(colon_index) = declaration_colon_index(tokens, index, statement_end.min(end)) {
9355 collect_known_css_module_value_reference_facts(
9356 tokens,
9357 colon_index + 1,
9358 statement_end.min(end),
9359 local_value_names,
9360 values,
9361 seen,
9362 );
9363 }
9364
9365 if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
9366 break;
9367 }
9368 index = statement_end + 1;
9369 }
9370}
9371
9372fn declaration_colon_index(tokens: &[Token<'_>], start: usize, end: usize) -> Option<usize> {
9373 let colon_index = top_level_token_kind_index(tokens, start, end, SyntaxKind::Colon)?;
9374 let property_index = previous_non_trivia_token_index(tokens, colon_index, start)?;
9375 if !matches!(
9376 tokens[property_index].kind,
9377 SyntaxKind::Ident
9378 | SyntaxKind::CustomPropertyName
9379 | SyntaxKind::ScssVariable
9380 | SyntaxKind::LessVariable
9381 | SyntaxKind::LessPropertyVariableToken
9382 ) {
9383 return None;
9384 }
9385 let value_index = next_non_trivia_token_index_until(tokens, colon_index + 1, end)?;
9386 if matches!(
9387 tokens[value_index].kind,
9388 SyntaxKind::LeftBrace | SyntaxKind::LeftParen | SyntaxKind::LeftBracket
9389 ) {
9390 return None;
9391 }
9392 Some(colon_index)
9393}
9394
9395fn collect_known_css_module_value_reference_facts(
9396 tokens: &[Token<'_>],
9397 start: usize,
9398 end: usize,
9399 local_value_names: &BTreeSet<String>,
9400 values: &mut Vec<ParsedCssModuleValueFact>,
9401 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9402) {
9403 let mut index = start;
9404 let mut paren_depth = 0usize;
9405 let mut bracket_depth = 0usize;
9406 while index < end {
9407 match tokens[index].kind {
9408 SyntaxKind::LeftParen => paren_depth += 1,
9409 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9410 SyntaxKind::LeftBracket => bracket_depth += 1,
9411 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9412 _ => {}
9413 }
9414 if paren_depth == 0
9415 && bracket_depth == 0
9416 && css_module_value_reference_token_can_be_name(tokens, index)
9417 && local_value_names.contains(tokens[index].text)
9418 {
9419 push_css_module_value_fact(
9420 values,
9421 seen,
9422 ParsedCssModuleValueFactKind::Reference,
9423 tokens[index].text.to_string(),
9424 tokens[index].range,
9425 );
9426 }
9427 index += 1;
9428 }
9429}
9430
9431fn push_css_module_value_fact(
9432 values: &mut Vec<ParsedCssModuleValueFact>,
9433 seen: &mut BTreeSet<(ParsedCssModuleValueFactKind, String, u32, u32)>,
9434 kind: ParsedCssModuleValueFactKind,
9435 name: String,
9436 range: TextRange,
9437) {
9438 if seen.insert((
9439 kind,
9440 name.clone(),
9441 u32::from(range.start()),
9442 u32::from(range.end()),
9443 )) {
9444 values.push(ParsedCssModuleValueFact { kind, name, range });
9445 }
9446}
9447
9448fn top_level_token_kind_index(
9449 tokens: &[Token<'_>],
9450 start: usize,
9451 end: usize,
9452 expected: SyntaxKind,
9453) -> Option<usize> {
9454 let mut index = start;
9455 let mut paren_depth = 0usize;
9456 let mut bracket_depth = 0usize;
9457 while index < end {
9458 match tokens[index].kind {
9459 SyntaxKind::LeftParen => paren_depth += 1,
9460 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9461 SyntaxKind::LeftBracket => bracket_depth += 1,
9462 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9463 kind if kind == expected && paren_depth == 0 && bracket_depth == 0 => {
9464 return Some(index);
9465 }
9466 _ => {}
9467 }
9468 index += 1;
9469 }
9470 None
9471}
9472
9473fn top_level_token_text_index(
9474 tokens: &[Token<'_>],
9475 start: usize,
9476 end: usize,
9477 expected: &str,
9478) -> Option<usize> {
9479 let mut index = start;
9480 let mut paren_depth = 0usize;
9481 let mut bracket_depth = 0usize;
9482 while index < end {
9483 match tokens[index].kind {
9484 SyntaxKind::LeftParen => paren_depth += 1,
9485 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
9486 SyntaxKind::LeftBracket => bracket_depth += 1,
9487 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
9488 SyntaxKind::Ident
9489 if paren_depth == 0
9490 && bracket_depth == 0
9491 && tokens[index].text.eq_ignore_ascii_case(expected) =>
9492 {
9493 return Some(index);
9494 }
9495 _ => {}
9496 }
9497 index += 1;
9498 }
9499 None
9500}
9501
9502fn previous_non_trivia_token_index(
9503 tokens: &[Token<'_>],
9504 mut index: usize,
9505 start: usize,
9506) -> Option<usize> {
9507 while index > start {
9508 index -= 1;
9509 if !tokens[index].kind.is_trivia() {
9510 return Some(index);
9511 }
9512 }
9513 None
9514}
9515
9516fn css_module_value_name_token_can_define(token: Token<'_>) -> bool {
9517 matches!(
9518 token.kind,
9519 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9520 ) && !matches!(token.text, "as" | "from")
9521}
9522
9523fn css_module_value_reference_token_can_be_name(tokens: &[Token<'_>], index: usize) -> bool {
9524 let token = tokens[index];
9525 if !matches!(
9526 token.kind,
9527 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9528 ) {
9529 return false;
9530 }
9531 if let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9532 && tokens[next_index].kind == SyntaxKind::LeftParen
9533 {
9534 return false;
9535 }
9536 !css_module_value_literal_ident_is_not_reference(token.text)
9537}
9538
9539fn css_module_value_literal_ident_is_not_reference(name: &str) -> bool {
9540 matches!(
9541 name.to_ascii_lowercase().as_str(),
9542 "initial"
9543 | "inherit"
9544 | "unset"
9545 | "revert"
9546 | "revert-layer"
9547 | "none"
9548 | "auto"
9549 | "normal"
9550 | "transparent"
9551 | "currentcolor"
9552 | "black"
9553 | "white"
9554 | "red"
9555 | "green"
9556 | "blue"
9557 | "yellow"
9558 | "magenta"
9559 | "cyan"
9560 | "solid"
9561 | "dashed"
9562 | "block"
9563 | "inline"
9564 | "flex"
9565 | "grid"
9566 )
9567}
9568
9569fn css_module_value_source_name(token: Token<'_>) -> String {
9570 token
9571 .text
9572 .trim_matches(|character| character == '"' || character == '\'')
9573 .to_string()
9574}
9575
9576fn css_module_value_source_looks_like_style_request(source: &str) -> bool {
9577 let lower = source.to_ascii_lowercase();
9578 (lower.starts_with('/') || lower.starts_with("./") || lower.starts_with("../"))
9579 && (lower.ends_with(".css")
9580 || lower.ends_with(".scss")
9581 || lower.ends_with(".sass")
9582 || lower.ends_with(".less"))
9583}
9584
9585fn collect_css_module_composes_facts_from_tokens(
9586 tokens: &[Token<'_>],
9587) -> Vec<ParsedCssModuleComposesFact> {
9588 let mut composes = Vec::new();
9589 let mut seen = BTreeSet::new();
9590 for (index, token) in tokens.iter().enumerate() {
9591 if token.kind != SyntaxKind::Ident || !token.text.eq_ignore_ascii_case("composes") {
9592 continue;
9593 }
9594 let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9595 else {
9596 continue;
9597 };
9598 if tokens[colon_index].kind != SyntaxKind::Colon {
9599 continue;
9600 }
9601
9602 let start = colon_index + 1;
9603 let end = css_module_value_statement_end(tokens, start);
9604 let from_index = top_level_token_text_index(tokens, start, end, "from");
9605 let target_end = from_index.unwrap_or(end);
9606 collect_css_module_composes_targets(tokens, start, target_end, &mut composes, &mut seen);
9607 if let Some(from_index) = from_index {
9608 collect_css_module_composes_import_source(
9609 tokens,
9610 from_index + 1,
9611 end,
9612 &mut composes,
9613 &mut seen,
9614 );
9615 }
9616 }
9617 composes
9618}
9619
9620fn collect_css_module_composes_edge_facts_from_tokens(
9621 tokens: &[Token<'_>],
9622) -> Vec<ParsedCssModuleComposesEdgeFact> {
9623 let mut edges = Vec::new();
9624 collect_css_module_composes_edge_facts_in_range(tokens, 0, tokens.len(), &[], None, &mut edges);
9625 edges
9626}
9627
9628fn collect_css_module_composes_edge_facts_in_range(
9629 tokens: &[Token<'_>],
9630 start: usize,
9631 end: usize,
9632 parent_branches: &[SelectorBranch],
9633 css_module_scope: Option<&'static str>,
9634 edges: &mut Vec<ParsedCssModuleComposesEdgeFact>,
9635) {
9636 let mut index = start;
9637 while index < end {
9638 index = skip_trivia_tokens(tokens, index, end);
9639 if index >= end {
9640 break;
9641 }
9642
9643 if tokens[index].kind == SyntaxKind::AtKeyword {
9644 let block = find_block_after_header(tokens, index, end);
9645 if let Some((open, close)) = block {
9646 if tokens[index].text == "@nest" {
9647 if css_module_scope == Some("global") {
9648 collect_css_module_composes_edge_facts_in_range(
9649 tokens,
9650 open + 1,
9651 close,
9652 &[],
9653 css_module_scope,
9654 edges,
9655 );
9656 } else {
9657 let branches =
9658 resolve_selector_header(tokens, index + 1, open, parent_branches);
9659 collect_immediate_css_module_composes_edge_facts(
9660 tokens,
9661 open + 1,
9662 close,
9663 &branches,
9664 edges,
9665 );
9666 collect_css_module_composes_edge_facts_in_range(
9667 tokens,
9668 open + 1,
9669 close,
9670 &branches,
9671 css_module_scope,
9672 edges,
9673 );
9674 }
9675 } else if style_wrapper_at_rule(tokens[index].text) {
9676 collect_css_module_composes_edge_facts_in_range(
9677 tokens,
9678 open + 1,
9679 close,
9680 parent_branches,
9681 css_module_scope,
9682 edges,
9683 );
9684 }
9685 index = close + 1;
9686 } else {
9687 index = skip_statement(tokens, index, end);
9688 }
9689 continue;
9690 }
9691
9692 let Some((open, close)) = find_block_after_header(tokens, index, end) else {
9693 index = skip_statement(tokens, index, end);
9694 continue;
9695 };
9696
9697 let effective_scope = css_module_scope
9698 .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
9699 if effective_scope == Some("global") {
9700 collect_css_module_composes_edge_facts_in_range(
9701 tokens,
9702 open + 1,
9703 close,
9704 &[],
9705 effective_scope,
9706 edges,
9707 );
9708 } else {
9709 let branches = resolve_selector_header(tokens, index, open, parent_branches);
9710 collect_immediate_css_module_composes_edge_facts(
9711 tokens,
9712 open + 1,
9713 close,
9714 &branches,
9715 edges,
9716 );
9717 collect_css_module_composes_edge_facts_in_range(
9718 tokens,
9719 open + 1,
9720 close,
9721 &branches,
9722 effective_scope,
9723 edges,
9724 );
9725 }
9726 index = close + 1;
9727 }
9728}
9729
9730fn collect_immediate_css_module_composes_edge_facts(
9731 tokens: &[Token<'_>],
9732 start: usize,
9733 end: usize,
9734 owner_branches: &[SelectorBranch],
9735 edges: &mut Vec<ParsedCssModuleComposesEdgeFact>,
9736) {
9737 let owner_selector_names = sorted_selector_branch_names(owner_branches);
9738 let mut index = start;
9739 let mut block_depth = 0usize;
9740 while index < end {
9741 match tokens[index].kind {
9742 SyntaxKind::LeftBrace | SyntaxKind::SassIndent => {
9743 block_depth += 1;
9744 index += 1;
9745 continue;
9746 }
9747 SyntaxKind::RightBrace | SyntaxKind::SassDedent => {
9748 block_depth = block_depth.saturating_sub(1);
9749 index += 1;
9750 continue;
9751 }
9752 _ => {}
9753 }
9754 if block_depth > 0
9755 || tokens[index].kind != SyntaxKind::Ident
9756 || !tokens[index].text.eq_ignore_ascii_case("composes")
9757 {
9758 index += 1;
9759 continue;
9760 }
9761 let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end) else {
9762 index += 1;
9763 continue;
9764 };
9765 if tokens[colon_index].kind != SyntaxKind::Colon {
9766 index += 1;
9767 continue;
9768 }
9769
9770 let value_start = colon_index + 1;
9771 let value_end = css_module_value_statement_end(tokens, value_start).min(end);
9772 let from_index = top_level_token_text_index(tokens, value_start, value_end, "from");
9773 let target_end = from_index.unwrap_or(value_end);
9774 let target_names =
9775 collect_css_module_composes_target_names(tokens, value_start, target_end);
9776 if target_names.is_empty() {
9777 index = value_end;
9778 continue;
9779 }
9780
9781 let (kind, import_source) = from_index
9782 .and_then(|from_index| {
9783 css_module_composes_import_edge_source(tokens, from_index + 1, value_end)
9784 })
9785 .map(|source| {
9786 if source == "global" {
9787 (ParsedCssModuleComposesEdgeKind::Global, Some(source))
9788 } else {
9789 (ParsedCssModuleComposesEdgeKind::External, Some(source))
9790 }
9791 })
9792 .unwrap_or((ParsedCssModuleComposesEdgeKind::Local, None));
9793 let range_end = value_end
9794 .checked_sub(1)
9795 .and_then(|end| tokens.get(end))
9796 .map(|token| token.range.end())
9797 .unwrap_or_else(|| tokens[index].range.end());
9798
9799 edges.push(ParsedCssModuleComposesEdgeFact {
9800 kind,
9801 owner_selector_names: owner_selector_names.clone(),
9802 target_names,
9803 import_source,
9804 range: TextRange::new(tokens[index].range.start(), range_end),
9805 });
9806 index = value_end;
9807 }
9808}
9809
9810fn sorted_selector_branch_names(branches: &[SelectorBranch]) -> Vec<String> {
9811 branches
9812 .iter()
9813 .map(|branch| branch.name.clone())
9814 .collect::<BTreeSet<_>>()
9815 .into_iter()
9816 .collect()
9817}
9818
9819fn collect_css_module_composes_target_names(
9820 tokens: &[Token<'_>],
9821 start: usize,
9822 end: usize,
9823) -> Vec<String> {
9824 let mut names = Vec::new();
9825 let mut index = start;
9826 while index < end {
9827 if matches!(
9828 tokens[index].kind,
9829 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9830 ) && !tokens[index].text.eq_ignore_ascii_case("from")
9831 && !names.iter().any(|name| name == tokens[index].text)
9832 {
9833 names.push(tokens[index].text.to_string());
9834 }
9835 index += 1;
9836 }
9837 names
9838}
9839
9840fn css_module_composes_import_edge_source(
9841 tokens: &[Token<'_>],
9842 start: usize,
9843 end: usize,
9844) -> Option<String> {
9845 let source_index = next_non_trivia_token_index_until(tokens, start, end)?;
9846 let token = tokens[source_index];
9847 matches!(
9848 token.kind,
9849 SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
9850 )
9851 .then(|| css_module_value_source_name(token))
9852}
9853
9854fn collect_css_module_composes_targets(
9855 tokens: &[Token<'_>],
9856 start: usize,
9857 end: usize,
9858 composes: &mut Vec<ParsedCssModuleComposesFact>,
9859 seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
9860) {
9861 let mut index = start;
9862 while index < end {
9863 if matches!(
9864 tokens[index].kind,
9865 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
9866 ) && !tokens[index].text.eq_ignore_ascii_case("from")
9867 {
9868 push_css_module_composes_fact(
9869 composes,
9870 seen,
9871 ParsedCssModuleComposesFactKind::Target,
9872 tokens[index].text.to_string(),
9873 tokens[index].range,
9874 );
9875 }
9876 index += 1;
9877 }
9878}
9879
9880fn collect_css_module_composes_import_source(
9881 tokens: &[Token<'_>],
9882 start: usize,
9883 end: usize,
9884 composes: &mut Vec<ParsedCssModuleComposesFact>,
9885 seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
9886) {
9887 if let Some(source_index) = next_non_trivia_token_index_until(tokens, start, end) {
9888 let token = tokens[source_index];
9889 if matches!(
9890 token.kind,
9891 SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
9892 ) {
9893 push_css_module_composes_fact(
9894 composes,
9895 seen,
9896 ParsedCssModuleComposesFactKind::ImportSource,
9897 css_module_value_source_name(token),
9898 token.range,
9899 );
9900 }
9901 }
9902}
9903
9904fn push_css_module_composes_fact(
9905 composes: &mut Vec<ParsedCssModuleComposesFact>,
9906 seen: &mut BTreeSet<(ParsedCssModuleComposesFactKind, String, u32, u32)>,
9907 kind: ParsedCssModuleComposesFactKind,
9908 name: String,
9909 range: TextRange,
9910) {
9911 if seen.insert((
9912 kind,
9913 name.clone(),
9914 u32::from(range.start()),
9915 u32::from(range.end()),
9916 )) {
9917 composes.push(ParsedCssModuleComposesFact { kind, name, range });
9918 }
9919}
9920
9921fn collect_icss_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedIcssFact> {
9922 let mut icss = Vec::new();
9923 let mut seen = BTreeSet::new();
9924 for (index, token) in tokens.iter().enumerate() {
9925 if token.kind != SyntaxKind::Colon {
9926 continue;
9927 }
9928 let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9929 else {
9930 continue;
9931 };
9932 let name = tokens[name_index].text;
9933 if !matches!(tokens[name_index].kind, SyntaxKind::Ident) {
9934 continue;
9935 }
9936 if name.eq_ignore_ascii_case("export") {
9937 if let Some((open, close)) =
9938 find_block_after_header(tokens, name_index + 1, tokens.len())
9939 {
9940 collect_icss_export_names(tokens, open + 1, close, &mut icss, &mut seen);
9941 }
9942 continue;
9943 }
9944 if name.eq_ignore_ascii_case("import") {
9945 collect_icss_import_source(tokens, name_index + 1, &mut icss, &mut seen);
9946 if let Some((open, close)) =
9947 find_block_after_header(tokens, name_index + 1, tokens.len())
9948 {
9949 collect_icss_import_names(tokens, open + 1, close, &mut icss, &mut seen);
9950 }
9951 }
9952 }
9953 icss
9954}
9955
9956fn collect_icss_import_edge_facts_from_tokens(
9957 tokens: &[Token<'_>],
9958) -> Vec<ParsedIcssImportEdgeFact> {
9959 let mut edges = Vec::new();
9960 for (index, token) in tokens.iter().enumerate() {
9961 if token.kind != SyntaxKind::Colon {
9962 continue;
9963 }
9964 let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9965 else {
9966 continue;
9967 };
9968 if tokens[name_index].kind != SyntaxKind::Ident
9969 || !tokens[name_index].text.eq_ignore_ascii_case("import")
9970 {
9971 continue;
9972 }
9973 let Some(import_source) = icss_import_edge_source(tokens, name_index + 1) else {
9974 continue;
9975 };
9976 if let Some((open, close)) = find_block_after_header(tokens, name_index + 1, tokens.len()) {
9977 collect_icss_import_edges(tokens, open + 1, close, import_source, &mut edges);
9978 }
9979 }
9980 edges
9981}
9982
9983fn collect_icss_export_edge_facts_from_tokens(
9984 tokens: &[Token<'_>],
9985) -> Vec<ParsedIcssExportEdgeFact> {
9986 let mut edges = Vec::new();
9987 for (index, token) in tokens.iter().enumerate() {
9988 if token.kind != SyntaxKind::Colon {
9989 continue;
9990 }
9991 let Some(name_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
9992 else {
9993 continue;
9994 };
9995 if tokens[name_index].kind != SyntaxKind::Ident
9996 || !tokens[name_index].text.eq_ignore_ascii_case("export")
9997 {
9998 continue;
9999 }
10000 if let Some((open, close)) = find_block_after_header(tokens, name_index + 1, tokens.len()) {
10001 collect_icss_export_edges(tokens, open + 1, close, &mut edges);
10002 }
10003 }
10004 edges
10005}
10006
10007fn collect_icss_export_edges(
10008 tokens: &[Token<'_>],
10009 start: usize,
10010 end: usize,
10011 edges: &mut Vec<ParsedIcssExportEdgeFact>,
10012) {
10013 let mut index = start;
10014 while index < end {
10015 let token = tokens[index];
10016 if matches!(
10017 token.kind,
10018 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10019 ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10020 && tokens[colon_index].kind == SyntaxKind::Colon
10021 {
10022 let value_end = css_module_value_statement_end(tokens, colon_index + 1).min(end);
10023 let reference_names = collect_css_module_value_definition_edge_names(
10024 tokens,
10025 colon_index + 1,
10026 value_end,
10027 css_module_value_reference_token_can_be_name,
10028 );
10029 if !reference_names.is_empty() {
10030 let range_end = value_end
10031 .checked_sub(1)
10032 .and_then(|end| tokens.get(end))
10033 .map(|token| token.range.end())
10034 .unwrap_or_else(|| token.range.end());
10035 edges.push(ParsedIcssExportEdgeFact {
10036 export_name: token.text.to_string(),
10037 reference_names,
10038 range: TextRange::new(token.range.start(), range_end),
10039 });
10040 }
10041 index = value_end;
10042 continue;
10043 }
10044 index += 1;
10045 }
10046}
10047
10048fn icss_import_edge_source(tokens: &[Token<'_>], start: usize) -> Option<String> {
10049 let open_index = next_non_trivia_token_index_until(tokens, start, tokens.len())?;
10050 if tokens[open_index].kind != SyntaxKind::LeftParen {
10051 return None;
10052 }
10053 let source_index = next_non_trivia_token_index_until(tokens, open_index + 1, tokens.len())?;
10054 let token = tokens[source_index];
10055 matches!(
10056 token.kind,
10057 SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10058 )
10059 .then(|| css_module_value_source_name(token))
10060}
10061
10062fn collect_icss_import_edges(
10063 tokens: &[Token<'_>],
10064 start: usize,
10065 end: usize,
10066 import_source: String,
10067 edges: &mut Vec<ParsedIcssImportEdgeFact>,
10068) {
10069 let mut index = start;
10070 while index < end {
10071 let token = tokens[index];
10072 if matches!(
10073 token.kind,
10074 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10075 ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10076 && tokens[colon_index].kind == SyntaxKind::Colon
10077 && let Some(remote_index) =
10078 next_non_trivia_token_index_until(tokens, colon_index + 1, end)
10079 && matches!(
10080 tokens[remote_index].kind,
10081 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10082 )
10083 {
10084 edges.push(ParsedIcssImportEdgeFact {
10085 local_name: token.text.to_string(),
10086 remote_name: tokens[remote_index].text.to_string(),
10087 import_source: import_source.clone(),
10088 range: token.range,
10089 });
10090 index = css_module_value_statement_end(tokens, colon_index + 1);
10091 continue;
10092 }
10093 index += 1;
10094 }
10095}
10096
10097fn collect_icss_export_names(
10098 tokens: &[Token<'_>],
10099 start: usize,
10100 end: usize,
10101 icss: &mut Vec<ParsedIcssFact>,
10102 seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10103) {
10104 let mut index = start;
10105 while index < end {
10106 let token = tokens[index];
10107 if matches!(
10108 token.kind,
10109 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10110 ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10111 && tokens[colon_index].kind == SyntaxKind::Colon
10112 {
10113 push_icss_fact(
10114 icss,
10115 seen,
10116 ParsedIcssFactKind::ExportName,
10117 token.text.to_string(),
10118 token.range,
10119 );
10120 index = css_module_value_statement_end(tokens, colon_index + 1);
10121 continue;
10122 }
10123 index += 1;
10124 }
10125}
10126
10127fn collect_icss_import_source(
10128 tokens: &[Token<'_>],
10129 start: usize,
10130 icss: &mut Vec<ParsedIcssFact>,
10131 seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10132) {
10133 let Some(open_index) = next_non_trivia_token_index_until(tokens, start, tokens.len()) else {
10134 return;
10135 };
10136 if tokens[open_index].kind != SyntaxKind::LeftParen {
10137 return;
10138 }
10139 let Some(source_index) =
10140 next_non_trivia_token_index_until(tokens, open_index + 1, tokens.len())
10141 else {
10142 return;
10143 };
10144 let token = tokens[source_index];
10145 if matches!(
10146 token.kind,
10147 SyntaxKind::String | SyntaxKind::Url | SyntaxKind::Ident
10148 ) {
10149 push_icss_fact(
10150 icss,
10151 seen,
10152 ParsedIcssFactKind::ImportSource,
10153 css_module_value_source_name(token),
10154 token.range,
10155 );
10156 }
10157}
10158
10159fn collect_icss_import_names(
10160 tokens: &[Token<'_>],
10161 start: usize,
10162 end: usize,
10163 icss: &mut Vec<ParsedIcssFact>,
10164 seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10165) {
10166 let mut index = start;
10167 while index < end {
10168 let token = tokens[index];
10169 if matches!(
10170 token.kind,
10171 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10172 ) && let Some(colon_index) = next_non_trivia_token_index_until(tokens, index + 1, end)
10173 && tokens[colon_index].kind == SyntaxKind::Colon
10174 {
10175 push_icss_fact(
10176 icss,
10177 seen,
10178 ParsedIcssFactKind::ImportLocalName,
10179 token.text.to_string(),
10180 token.range,
10181 );
10182 if let Some(remote_index) =
10183 next_non_trivia_token_index_until(tokens, colon_index + 1, end)
10184 && matches!(
10185 tokens[remote_index].kind,
10186 SyntaxKind::Ident | SyntaxKind::CustomPropertyName
10187 )
10188 {
10189 push_icss_fact(
10190 icss,
10191 seen,
10192 ParsedIcssFactKind::ImportRemoteName,
10193 tokens[remote_index].text.to_string(),
10194 tokens[remote_index].range,
10195 );
10196 }
10197 index = css_module_value_statement_end(tokens, colon_index + 1);
10198 continue;
10199 }
10200 index += 1;
10201 }
10202}
10203
10204fn push_icss_fact(
10205 icss: &mut Vec<ParsedIcssFact>,
10206 seen: &mut BTreeSet<(ParsedIcssFactKind, String, u32, u32)>,
10207 kind: ParsedIcssFactKind,
10208 name: String,
10209 range: TextRange,
10210) {
10211 if seen.insert((
10212 kind,
10213 name.clone(),
10214 u32::from(range.start()),
10215 u32::from(range.end()),
10216 )) {
10217 icss.push(ParsedIcssFact { kind, name, range });
10218 }
10219}
10220
10221fn collect_animation_facts_from_tokens(tokens: &[Token<'_>]) -> Vec<ParsedAnimationFact> {
10222 let mut animations = Vec::new();
10223 let mut seen = BTreeSet::new();
10224 for (index, token) in tokens.iter().enumerate() {
10225 if token.kind == SyntaxKind::AtKeyword && token.text.eq_ignore_ascii_case("@keyframes") {
10226 if let Some(name_index) =
10227 next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10228 && let Some(name) = animation_name_from_token(tokens[name_index])
10229 {
10230 push_animation_fact(
10231 &mut animations,
10232 &mut seen,
10233 ParsedAnimationFactKind::KeyframesDeclaration,
10234 name,
10235 tokens[name_index].range,
10236 );
10237 }
10238 continue;
10239 }
10240
10241 if token.kind == SyntaxKind::Ident
10242 && token.text.eq_ignore_ascii_case("animation-name")
10243 && let Some(colon_index) =
10244 next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10245 && tokens[colon_index].kind == SyntaxKind::Colon
10246 {
10247 collect_animation_name_references_until(
10248 tokens,
10249 colon_index + 1,
10250 &mut animations,
10251 &mut seen,
10252 );
10253 }
10254
10255 if token.kind == SyntaxKind::Ident
10256 && token.text.eq_ignore_ascii_case("animation")
10257 && let Some(colon_index) =
10258 next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10259 && tokens[colon_index].kind == SyntaxKind::Colon
10260 {
10261 collect_animation_shorthand_references_until(
10262 tokens,
10263 colon_index + 1,
10264 &mut animations,
10265 &mut seen,
10266 );
10267 }
10268 }
10269 animations
10270}
10271
10272fn collect_animation_name_references_until(
10273 tokens: &[Token<'_>],
10274 start: usize,
10275 animations: &mut Vec<ParsedAnimationFact>,
10276 seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10277) {
10278 let mut index = start;
10279 let mut paren_depth = 0usize;
10280 let mut bracket_depth = 0usize;
10281 while index < tokens.len() {
10282 match tokens[index].kind {
10283 SyntaxKind::LeftParen => paren_depth += 1,
10284 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
10285 SyntaxKind::LeftBracket => bracket_depth += 1,
10286 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
10287 SyntaxKind::Semicolon
10288 | SyntaxKind::SassOptionalSemicolon
10289 | SyntaxKind::RightBrace
10290 | SyntaxKind::SassDedent
10291 if paren_depth == 0 && bracket_depth == 0 =>
10292 {
10293 break;
10294 }
10295 _ => {}
10296 }
10297
10298 if paren_depth == 0
10299 && bracket_depth == 0
10300 && let Some(name) = animation_name_from_token(tokens[index])
10301 {
10302 push_animation_fact(
10303 animations,
10304 seen,
10305 ParsedAnimationFactKind::AnimationNameReference,
10306 name,
10307 tokens[index].range,
10308 );
10309 }
10310 index += 1;
10311 }
10312}
10313
10314fn collect_animation_shorthand_references_until(
10315 tokens: &[Token<'_>],
10316 start: usize,
10317 animations: &mut Vec<ParsedAnimationFact>,
10318 seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10319) {
10320 let mut index = start;
10321 let mut paren_depth = 0usize;
10322 let mut bracket_depth = 0usize;
10323 while index < tokens.len() {
10324 match tokens[index].kind {
10325 SyntaxKind::LeftParen => paren_depth += 1,
10326 SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
10327 SyntaxKind::LeftBracket => bracket_depth += 1,
10328 SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
10329 SyntaxKind::Semicolon
10330 | SyntaxKind::SassOptionalSemicolon
10331 | SyntaxKind::RightBrace
10332 | SyntaxKind::SassDedent
10333 if paren_depth == 0 && bracket_depth == 0 =>
10334 {
10335 break;
10336 }
10337 _ => {}
10338 }
10339
10340 if paren_depth == 0
10341 && bracket_depth == 0
10342 && animation_shorthand_token_can_be_name(tokens, index)
10343 && let Some(name) = animation_name_from_token(tokens[index])
10344 {
10345 push_animation_fact(
10346 animations,
10347 seen,
10348 ParsedAnimationFactKind::AnimationNameReference,
10349 name,
10350 tokens[index].range,
10351 );
10352 }
10353 index += 1;
10354 }
10355}
10356
10357fn animation_shorthand_token_can_be_name(tokens: &[Token<'_>], index: usize) -> bool {
10358 let token = tokens[index];
10359 if token.kind == SyntaxKind::String {
10360 return true;
10361 }
10362 if token.kind != SyntaxKind::Ident {
10363 return false;
10364 }
10365 if let Some(next_index) = next_non_trivia_token_index_until(tokens, index + 1, tokens.len())
10366 && tokens[next_index].kind == SyntaxKind::LeftParen
10367 {
10368 return false;
10369 }
10370 !animation_shorthand_ident_is_non_name(token.text)
10371}
10372
10373fn animation_shorthand_ident_is_non_name(name: &str) -> bool {
10374 matches!(
10375 name.to_ascii_lowercase().as_str(),
10376 "ease"
10377 | "ease-in"
10378 | "ease-out"
10379 | "ease-in-out"
10380 | "linear"
10381 | "step-start"
10382 | "step-end"
10383 | "infinite"
10384 | "normal"
10385 | "reverse"
10386 | "alternate"
10387 | "alternate-reverse"
10388 | "running"
10389 | "paused"
10390 | "forwards"
10391 | "backwards"
10392 | "both"
10393 | "replace"
10394 | "add"
10395 | "accumulate"
10396 | "auto"
10397 )
10398}
10399
10400fn push_animation_fact(
10401 animations: &mut Vec<ParsedAnimationFact>,
10402 seen: &mut BTreeSet<(ParsedAnimationFactKind, String, u32, u32)>,
10403 kind: ParsedAnimationFactKind,
10404 name: String,
10405 range: TextRange,
10406) {
10407 if seen.insert((
10408 kind,
10409 name.clone(),
10410 u32::from(range.start()),
10411 u32::from(range.end()),
10412 )) {
10413 animations.push(ParsedAnimationFact { kind, name, range });
10414 }
10415}
10416
10417fn animation_name_from_token(token: Token<'_>) -> Option<String> {
10418 if !matches!(token.kind, SyntaxKind::Ident | SyntaxKind::String) {
10419 return None;
10420 }
10421 let name = token
10422 .text
10423 .trim_matches(|character| character == '"' || character == '\'')
10424 .to_string();
10425 if name.is_empty() || animation_name_is_reserved(&name) {
10426 return None;
10427 }
10428 Some(name)
10429}
10430
10431fn animation_name_is_reserved(name: &str) -> bool {
10432 matches!(
10433 name.to_ascii_lowercase().as_str(),
10434 "none" | "initial" | "inherit" | "unset" | "revert" | "revert-layer"
10435 )
10436}
10437
10438fn containing_at_rule_header_name<'text>(
10439 tokens: &'text [Token<'text>],
10440 index: usize,
10441) -> Option<&'text str> {
10442 let mut current = index;
10443 while current > 0 {
10444 current -= 1;
10445 let token = tokens.get(current)?;
10446 if token.kind.is_trivia() {
10447 continue;
10448 }
10449 if matches!(
10450 token.kind,
10451 SyntaxKind::Semicolon
10452 | SyntaxKind::SassOptionalSemicolon
10453 | SyntaxKind::LeftBrace
10454 | SyntaxKind::RightBrace
10455 | SyntaxKind::SassIndent
10456 | SyntaxKind::SassDedent
10457 ) {
10458 return None;
10459 }
10460 if token.kind == SyntaxKind::AtKeyword {
10461 return Some(token.text);
10462 }
10463 }
10464 None
10465}
10466
10467fn skip_trivia_tokens(tokens: &[Token<'_>], mut index: usize, end: usize) -> usize {
10468 while index < end && tokens[index].kind.is_trivia() {
10469 index += 1;
10470 }
10471 index
10472}
10473
10474fn skip_statement(tokens: &[Token<'_>], mut index: usize, end: usize) -> usize {
10475 while index < end {
10476 match tokens[index].kind {
10477 SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon => return index + 1,
10478 SyntaxKind::RightBrace | SyntaxKind::SassDedent => return index,
10479 _ => index += 1,
10480 }
10481 }
10482 index
10483}
10484
10485fn find_block_after_header(
10486 tokens: &[Token<'_>],
10487 start: usize,
10488 end: usize,
10489) -> Option<(usize, usize)> {
10490 let mut index = start;
10491 while index < end {
10492 match tokens[index].kind {
10493 SyntaxKind::Semicolon
10494 | SyntaxKind::SassOptionalSemicolon
10495 | SyntaxKind::RightBrace
10496 | SyntaxKind::SassDedent => return None,
10497 SyntaxKind::LeftBrace => {
10498 let close = matching_right_brace(tokens, index, end)?;
10499 return Some((index, close));
10500 }
10501 SyntaxKind::SassIndent => {
10502 let close = matching_sass_dedent(tokens, index, end)?;
10503 return Some((index, close));
10504 }
10505 _ => index += 1,
10506 }
10507 }
10508 None
10509}
10510
10511fn matching_right_brace(tokens: &[Token<'_>], open: usize, end: usize) -> Option<usize> {
10512 let mut depth = 0usize;
10513 let mut index = open;
10514 while index < end {
10515 match tokens[index].kind {
10516 SyntaxKind::LeftBrace => depth += 1,
10517 SyntaxKind::RightBrace => {
10518 depth = depth.saturating_sub(1);
10519 if depth == 0 {
10520 return Some(index);
10521 }
10522 }
10523 _ => {}
10524 }
10525 index += 1;
10526 }
10527 None
10528}
10529
10530fn matching_sass_dedent(tokens: &[Token<'_>], open: usize, end: usize) -> Option<usize> {
10531 let mut depth = 0usize;
10532 let mut index = open;
10533 while index < end {
10534 match tokens[index].kind {
10535 SyntaxKind::SassIndent => depth += 1,
10536 SyntaxKind::SassDedent => {
10537 depth = depth.saturating_sub(1);
10538 if depth == 0 {
10539 return Some(index);
10540 }
10541 }
10542 _ => {}
10543 }
10544 index += 1;
10545 }
10546 None
10547}
10548
10549fn style_wrapper_at_rule(name: &str) -> bool {
10550 matches_ignore_ascii_case(
10551 name,
10552 &[
10553 "@media",
10554 "@supports",
10555 "@when",
10556 "@else",
10557 "@layer",
10558 "@scope",
10559 "@container",
10560 "@starting-style",
10561 "@if",
10562 "@else",
10563 "@for",
10564 "@each",
10565 "@while",
10566 "@at-root",
10567 "@include",
10568 ],
10569 )
10570}
10571
10572fn is_selector_combinator_kind(kind: SyntaxKind) -> bool {
10573 matches!(
10574 kind,
10575 SyntaxKind::GreaterThan
10576 | SyntaxKind::Plus
10577 | SyntaxKind::Tilde
10578 | SyntaxKind::ColumnCombinator
10579 | SyntaxKind::DoublePipe
10580 )
10581}
10582
10583fn selector_component_can_start(kind: SyntaxKind) -> bool {
10584 matches!(
10585 kind,
10586 SyntaxKind::Dot
10587 | SyntaxKind::Hash
10588 | SyntaxKind::Ident
10589 | SyntaxKind::Star
10590 | SyntaxKind::Ampersand
10591 | SyntaxKind::ScssPlaceholder
10592 | SyntaxKind::LeftBracket
10593 | SyntaxKind::Colon
10594 | SyntaxKind::DoubleColon
10595 )
10596}
10597
10598fn namespace_selector_target_can_start(kind: SyntaxKind) -> bool {
10599 matches!(
10600 kind,
10601 SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star
10602 )
10603}
10604
10605fn keyframe_selector_token_is_valid(token: Token<'_>) -> bool {
10606 token.kind == SyntaxKind::Percentage
10607 || (token.kind == SyntaxKind::Ident
10608 && (token.text.eq_ignore_ascii_case("from") || token.text.eq_ignore_ascii_case("to")))
10609}
10610
10611fn selector_component_can_end(kind: SyntaxKind) -> bool {
10612 matches!(
10613 kind,
10614 SyntaxKind::Ident
10615 | SyntaxKind::CustomPropertyName
10616 | SyntaxKind::Hash
10617 | SyntaxKind::RightBracket
10618 | SyntaxKind::RightParen
10619 | SyntaxKind::Star
10620 )
10621}
10622
10623fn collect_at_rule_facts_from_tokens(
10624 tokens: &[Token<'_>],
10625 dialect: StyleDialect,
10626) -> Vec<ParsedAtRuleFact> {
10627 tokens
10628 .iter()
10629 .filter(|token| token.kind == SyntaxKind::AtKeyword)
10630 .map(|token| {
10631 let css_spec = at_rule_spec(token.text);
10632 let node_kind = css_spec
10633 .or_else(|| match dialect {
10634 StyleDialect::Scss | StyleDialect::Sass => scss_at_rule_spec(token.text),
10635 StyleDialect::Css | StyleDialect::Less => None,
10636 })
10637 .map(|spec| spec.node_kind);
10638 let name = if css_spec.is_some() {
10639 token.text.to_ascii_lowercase()
10640 } else {
10641 token.text.to_string()
10642 };
10643 ParsedAtRuleFact {
10644 name,
10645 node_kind,
10646 range: token.range,
10647 }
10648 })
10649 .collect()
10650}
10651
10652fn next_non_trivia_token<'text>(
10653 tokens: &'text [Token<'text>],
10654 mut index: usize,
10655) -> Option<Token<'text>> {
10656 while let Some(token) = tokens.get(index).copied() {
10657 if !token.kind.is_trivia() {
10658 return Some(token);
10659 }
10660 index += 1;
10661 }
10662 None
10663}
10664
10665fn next_non_trivia_token_until<'text>(
10666 tokens: &'text [Token<'text>],
10667 mut index: usize,
10668 end: usize,
10669) -> Option<Token<'text>> {
10670 while index < end {
10671 let token = tokens.get(index).copied()?;
10672 if !token.kind.is_trivia() {
10673 return Some(token);
10674 }
10675 index += 1;
10676 }
10677 None
10678}
10679
10680fn next_non_trivia_token_index_until(
10681 tokens: &[Token<'_>],
10682 mut index: usize,
10683 end: usize,
10684) -> Option<usize> {
10685 while index < end {
10686 let token = tokens.get(index)?;
10687 if !token.kind.is_trivia() {
10688 return Some(index);
10689 }
10690 index += 1;
10691 }
10692 None
10693}
10694
10695fn next_non_trivia_token_after_range<'text>(
10696 tokens: &'text [Token<'text>],
10697 range: TextRange,
10698 end: usize,
10699) -> Option<Token<'text>> {
10700 let index = token_index_by_range(tokens, range)?;
10701 next_non_trivia_token_until(tokens, index + 1, end)
10702}
10703
10704fn token_index_by_range(tokens: &[Token<'_>], range: TextRange) -> Option<usize> {
10705 tokens.iter().position(|token| token.range == range)
10706}
10707
10708fn matching_right_paren_from_range(
10709 tokens: &[Token<'_>],
10710 open_range: TextRange,
10711 end: usize,
10712) -> Option<usize> {
10713 let mut depth = 0usize;
10714 let mut index = token_index_by_range(tokens, open_range)?;
10715 while index < end {
10716 match tokens[index].kind {
10717 SyntaxKind::LeftParen => depth += 1,
10718 SyntaxKind::RightParen => {
10719 depth = depth.saturating_sub(1);
10720 if depth == 0 {
10721 return Some(index);
10722 }
10723 }
10724 _ => {}
10725 }
10726 index += 1;
10727 }
10728 None
10729}
10730
10731fn previous_non_trivia_token<'text>(
10732 tokens: &'text [Token<'text>],
10733 start: usize,
10734 index: usize,
10735) -> Option<Token<'text>> {
10736 let mut current = index;
10737 while current > start {
10738 current -= 1;
10739 let token = tokens.get(current).copied()?;
10740 if !token.kind.is_trivia() {
10741 return Some(token);
10742 }
10743 }
10744 None
10745}
10746
10747fn at_rule_spec(text: &str) -> Option<AtRuleSpec> {
10748 let lowered = text.to_ascii_lowercase();
10749 let (node_kind, block_kind) = match lowered.as_str() {
10750 "@media" => (SyntaxKind::MediaRule, AtRuleBlockKind::GroupRuleList),
10751 "@supports" => (SyntaxKind::SupportsRule, AtRuleBlockKind::GroupRuleList),
10752 "@when" => (SyntaxKind::WhenRule, AtRuleBlockKind::GroupRuleList),
10753 "@else" => (SyntaxKind::ElseRule, AtRuleBlockKind::GroupRuleList),
10754 "@container" => (SyntaxKind::ContainerRule, AtRuleBlockKind::GroupRuleList),
10755 "@layer" => (SyntaxKind::LayerRule, AtRuleBlockKind::GroupRuleList),
10756 "@scope" => (SyntaxKind::ScopeRule, AtRuleBlockKind::GroupRuleList),
10757 "@starting-style" => (
10758 SyntaxKind::StartingStyleRule,
10759 AtRuleBlockKind::GroupRuleList,
10760 ),
10761 "@nest" => (SyntaxKind::NestRule, AtRuleBlockKind::DeclarationList),
10762 "@keyframes" => (SyntaxKind::KeyframesRule, AtRuleBlockKind::Keyframes),
10763 "@font-face" => (SyntaxKind::FontFaceRule, AtRuleBlockKind::DeclarationList),
10764 "@page" => (SyntaxKind::PageRule, AtRuleBlockKind::DeclarationList),
10765 "@property" => (SyntaxKind::PropertyRule, AtRuleBlockKind::DeclarationList),
10766 "@counter-style" => (
10767 SyntaxKind::CounterStyleRule,
10768 AtRuleBlockKind::DeclarationList,
10769 ),
10770 "@font-palette-values" => (
10771 SyntaxKind::FontPaletteValuesRule,
10772 AtRuleBlockKind::DeclarationList,
10773 ),
10774 "@color-profile" => (
10775 SyntaxKind::ColorProfileRule,
10776 AtRuleBlockKind::DeclarationList,
10777 ),
10778 "@position-try" => (
10779 SyntaxKind::PositionTryRule,
10780 AtRuleBlockKind::DeclarationList,
10781 ),
10782 "@font-feature-values" => (
10783 SyntaxKind::FontFeatureValuesRule,
10784 AtRuleBlockKind::GroupRuleList,
10785 ),
10786 "@stylistic" => (
10787 SyntaxKind::FontFeatureValuesStylisticRule,
10788 AtRuleBlockKind::DeclarationList,
10789 ),
10790 "@styleset" => (
10791 SyntaxKind::FontFeatureValuesStylesetRule,
10792 AtRuleBlockKind::DeclarationList,
10793 ),
10794 "@character-variant" => (
10795 SyntaxKind::FontFeatureValuesCharacterVariantRule,
10796 AtRuleBlockKind::DeclarationList,
10797 ),
10798 "@swash" => (
10799 SyntaxKind::FontFeatureValuesSwashRule,
10800 AtRuleBlockKind::DeclarationList,
10801 ),
10802 "@ornaments" => (
10803 SyntaxKind::FontFeatureValuesOrnamentsRule,
10804 AtRuleBlockKind::DeclarationList,
10805 ),
10806 "@annotation" => (
10807 SyntaxKind::FontFeatureValuesAnnotationRule,
10808 AtRuleBlockKind::DeclarationList,
10809 ),
10810 "@historical-forms" => (
10811 SyntaxKind::FontFeatureValuesHistoricalFormsRule,
10812 AtRuleBlockKind::DeclarationList,
10813 ),
10814 "@view-transition" => (
10815 SyntaxKind::ViewTransitionRule,
10816 AtRuleBlockKind::DeclarationList,
10817 ),
10818 "@charset" => (SyntaxKind::CharsetRule, AtRuleBlockKind::Raw),
10819 "@import" => (SyntaxKind::ImportRule, AtRuleBlockKind::Raw),
10820 "@namespace" => (SyntaxKind::NamespaceRule, AtRuleBlockKind::Raw),
10821 "@custom-media" => (SyntaxKind::CustomMediaRule, AtRuleBlockKind::Raw),
10822 text if is_page_margin_at_rule(text) => {
10823 (SyntaxKind::PageMarginRule, AtRuleBlockKind::DeclarationList)
10824 }
10825 _ => return None,
10826 };
10827 Some(AtRuleSpec {
10828 node_kind,
10829 block_kind,
10830 })
10831}
10832
10833fn is_page_margin_at_rule(text: &str) -> bool {
10834 matches!(
10835 text,
10836 "@top-left-corner"
10837 | "@top-left"
10838 | "@top-center"
10839 | "@top-right"
10840 | "@top-right-corner"
10841 | "@bottom-left-corner"
10842 | "@bottom-left"
10843 | "@bottom-center"
10844 | "@bottom-right"
10845 | "@bottom-right-corner"
10846 | "@left-top"
10847 | "@left-middle"
10848 | "@left-bottom"
10849 | "@right-top"
10850 | "@right-middle"
10851 | "@right-bottom"
10852 )
10853}
10854
10855fn scss_at_rule_spec(text: &str) -> Option<AtRuleSpec> {
10856 let (node_kind, block_kind) = match text {
10857 "@use" => (SyntaxKind::ScssUseRule, AtRuleBlockKind::Raw),
10858 "@forward" => (SyntaxKind::ScssForwardRule, AtRuleBlockKind::Raw),
10859 "@mixin" => (
10860 SyntaxKind::ScssMixinDeclaration,
10861 AtRuleBlockKind::DeclarationList,
10862 ),
10863 "@include" => (
10864 SyntaxKind::ScssIncludeRule,
10865 AtRuleBlockKind::DeclarationList,
10866 ),
10867 "@function" => (
10868 SyntaxKind::ScssFunctionDeclaration,
10869 AtRuleBlockKind::DeclarationList,
10870 ),
10871 "@return" => (SyntaxKind::ScssReturnRule, AtRuleBlockKind::Raw),
10872 "@extend" => (SyntaxKind::ScssExtendRule, AtRuleBlockKind::Raw),
10873 "@if" => (SyntaxKind::ScssControlIf, AtRuleBlockKind::DeclarationList),
10874 "@else" => (
10875 SyntaxKind::ScssControlElse,
10876 AtRuleBlockKind::DeclarationList,
10877 ),
10878 "@each" => (
10879 SyntaxKind::ScssControlEach,
10880 AtRuleBlockKind::DeclarationList,
10881 ),
10882 "@for" => (SyntaxKind::ScssControlFor, AtRuleBlockKind::DeclarationList),
10883 "@while" => (
10884 SyntaxKind::ScssControlWhile,
10885 AtRuleBlockKind::DeclarationList,
10886 ),
10887 "@at-root" => (SyntaxKind::ScssAtRootRule, AtRuleBlockKind::DeclarationList),
10888 "@error" => (SyntaxKind::ScssErrorRule, AtRuleBlockKind::Raw),
10889 "@warn" => (SyntaxKind::ScssWarnRule, AtRuleBlockKind::Raw),
10890 "@debug" => (SyntaxKind::ScssDebugRule, AtRuleBlockKind::Raw),
10891 "@content" => (SyntaxKind::ScssContentRule, AtRuleBlockKind::Raw),
10892 _ => return None,
10893 };
10894 Some(AtRuleSpec {
10895 node_kind,
10896 block_kind,
10897 })
10898}
10899
10900fn is_selector_boundary(kind: SyntaxKind) -> bool {
10901 matches!(
10902 kind,
10903 SyntaxKind::Comma
10904 | SyntaxKind::LeftBrace
10905 | SyntaxKind::SassIndent
10906 | SyntaxKind::RightBrace
10907 | SyntaxKind::SassDedent
10908 | SyntaxKind::Semicolon
10909 | SyntaxKind::SassOptionalSemicolon
10910 )
10911}
10912
10913fn is_selector_boundary_until(kind: SyntaxKind, recovery: &[SyntaxKind]) -> bool {
10914 is_selector_boundary(kind) || recovery.contains(&kind)
10915}
10916
10917fn is_selector_list_pseudo_class(text: &str) -> bool {
10918 matches!(text, "is" | "where" | "local" | "global")
10919}
10920
10921fn is_nth_pseudo_class(text: &str) -> bool {
10922 matches!(
10923 text,
10924 "nth-child" | "nth-last-child" | "nth-of-type" | "nth-last-of-type"
10925 )
10926}
10927
10928fn language_tag_token_can_start(kind: SyntaxKind) -> bool {
10929 matches!(kind, SyntaxKind::Ident | SyntaxKind::String)
10930}
10931
10932fn selector_item_token_is_recoverable(kind: SyntaxKind) -> bool {
10933 matches!(
10934 kind,
10935 SyntaxKind::Whitespace
10936 | SyntaxKind::SassIndentedNewline
10937 | SyntaxKind::Dot
10938 | SyntaxKind::Comma
10939 | SyntaxKind::Hash
10940 | SyntaxKind::Ident
10941 | SyntaxKind::CustomPropertyName
10942 | SyntaxKind::String
10943 | SyntaxKind::Number
10944 | SyntaxKind::Percentage
10945 | SyntaxKind::Dimension
10946 | SyntaxKind::Star
10947 | SyntaxKind::Ampersand
10948 | SyntaxKind::ScssPlaceholder
10949 | SyntaxKind::LeftBracket
10950 | SyntaxKind::RightBracket
10951 | SyntaxKind::Colon
10952 | SyntaxKind::DoubleColon
10953 | SyntaxKind::LeftParen
10954 | SyntaxKind::RightParen
10955 | SyntaxKind::Equals
10956 | SyntaxKind::IncludesMatch
10957 | SyntaxKind::DashMatch
10958 | SyntaxKind::PrefixMatch
10959 | SyntaxKind::SuffixMatch
10960 | SyntaxKind::SubstringMatch
10961 | SyntaxKind::Pipe
10962 | SyntaxKind::ColumnCombinator
10963 | SyntaxKind::GreaterThan
10964 | SyntaxKind::Plus
10965 | SyntaxKind::Minus
10966 | SyntaxKind::Tilde
10967 | SyntaxKind::KeywordAnd
10968 | SyntaxKind::KeywordOr
10969 | SyntaxKind::KeywordNot
10970 )
10971}
10972
10973fn is_at_rule_prelude_boundary(kind: SyntaxKind) -> bool {
10974 matches!(
10975 kind,
10976 SyntaxKind::LeftBrace
10977 | SyntaxKind::SassIndent
10978 | SyntaxKind::Semicolon
10979 | SyntaxKind::SassOptionalSemicolon
10980 )
10981}
10982
10983fn is_statement_end(kind: SyntaxKind) -> bool {
10984 matches!(
10985 kind,
10986 SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon
10987 )
10988}
10989
10990fn sass_token_can_end_statement(kind: SyntaxKind) -> bool {
10991 !matches!(
10992 kind,
10993 SyntaxKind::Whitespace
10994 | SyntaxKind::LineComment
10995 | SyntaxKind::BlockComment
10996 | SyntaxKind::SassIndentedNewline
10997 | SyntaxKind::SassIndent
10998 | SyntaxKind::SassDedent
10999 | SyntaxKind::SassOptionalSemicolon
11000 | SyntaxKind::Comma
11001 | SyntaxKind::Colon
11002 | SyntaxKind::DoubleColon
11003 | SyntaxKind::LeftBrace
11004 | SyntaxKind::LeftParen
11005 | SyntaxKind::LeftBracket
11006 | SyntaxKind::Plus
11007 | SyntaxKind::Minus
11008 | SyntaxKind::Star
11009 | SyntaxKind::Slash
11010 | SyntaxKind::GreaterThan
11011 | SyntaxKind::LessThan
11012 | SyntaxKind::Equals
11013 | SyntaxKind::Arrow
11014 | SyntaxKind::Pipe
11015 | SyntaxKind::Tilde
11016 | SyntaxKind::Caret
11017 | SyntaxKind::Ampersand
11018 | SyntaxKind::DoubleAmpersand
11019 | SyntaxKind::ColumnCombinator
11020 | SyntaxKind::IncludesMatch
11021 | SyntaxKind::DashMatch
11022 | SyntaxKind::PrefixMatch
11023 | SyntaxKind::SuffixMatch
11024 | SyntaxKind::SubstringMatch
11025 | SyntaxKind::PlusEquals
11026 | SyntaxKind::MinusEquals
11027 | SyntaxKind::SlashEquals
11028 )
11029}
11030
11031fn function_argument_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11032 let mut kinds = vec![SyntaxKind::RightParen];
11033 for kind in recovery {
11034 if !kinds.contains(kind) {
11035 kinds.push(*kind);
11036 }
11037 }
11038 kinds
11039}
11040
11041fn bracketed_value_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11042 let mut kinds = vec![SyntaxKind::RightBracket];
11043 for kind in recovery {
11044 if !kinds.contains(kind) {
11045 kinds.push(*kind);
11046 }
11047 }
11048 kinds
11049}
11050
11051fn simple_block_recovery(close_kind: SyntaxKind, recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11052 let mut kinds = vec![close_kind];
11053 for kind in recovery {
11054 if !kinds.contains(kind) {
11055 kinds.push(*kind);
11056 }
11057 }
11058 kinds
11059}
11060
11061fn matching_simple_block_close(open_kind: SyntaxKind) -> Option<SyntaxKind> {
11062 match open_kind {
11063 SyntaxKind::LeftBrace => Some(SyntaxKind::RightBrace),
11064 SyntaxKind::LeftBracket => Some(SyntaxKind::RightBracket),
11065 SyntaxKind::LeftParen => Some(SyntaxKind::RightParen),
11066 _ => None,
11067 }
11068}
11069
11070fn value_list_item_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11071 let mut kinds = vec![SyntaxKind::Comma];
11072 for kind in recovery {
11073 if !kinds.contains(kind) {
11074 kinds.push(*kind);
11075 }
11076 }
11077 kinds
11078}
11079
11080fn comma_separated_component_value_list_item_recovery(recovery: &[SyntaxKind]) -> Vec<SyntaxKind> {
11081 let mut kinds = vec![SyntaxKind::Comma];
11082 for kind in recovery {
11083 if !kinds.contains(kind) {
11084 kinds.push(*kind);
11085 }
11086 }
11087 kinds
11088}
11089
11090fn variable_declaration_node_kind(kind: SyntaxKind, has_colon: bool) -> SyntaxKind {
11091 if has_colon {
11092 return kind;
11093 }
11094 match kind {
11095 SyntaxKind::ScssVariableDeclaration => SyntaxKind::BogusScssVariable,
11096 SyntaxKind::LessVariableDeclaration => SyntaxKind::BogusLessVariable,
11097 _ => kind,
11098 }
11099}
11100
11101fn is_attribute_matcher(kind: SyntaxKind) -> bool {
11102 matches!(
11103 kind,
11104 SyntaxKind::Equals
11105 | SyntaxKind::IncludesMatch
11106 | SyntaxKind::DashMatch
11107 | SyntaxKind::PrefixMatch
11108 | SyntaxKind::SuffixMatch
11109 | SyntaxKind::SubstringMatch
11110 )
11111}
11112
11113fn attribute_name_token_can_start(kind: SyntaxKind) -> bool {
11114 matches!(
11115 kind,
11116 SyntaxKind::Ident | SyntaxKind::CustomPropertyName | SyntaxKind::Star
11117 )
11118}
11119
11120fn attribute_name_token_can_continue(kind: SyntaxKind) -> bool {
11121 matches!(
11122 kind,
11123 SyntaxKind::Ident
11124 | SyntaxKind::CustomPropertyName
11125 | SyntaxKind::Star
11126 | SyntaxKind::Pipe
11127 | SyntaxKind::ColumnCombinator
11128 )
11129}
11130
11131fn attribute_value_token_can_start(kind: SyntaxKind) -> bool {
11132 matches!(
11133 kind,
11134 SyntaxKind::Ident
11135 | SyntaxKind::CustomPropertyName
11136 | SyntaxKind::String
11137 | SyntaxKind::Hash
11138 | SyntaxKind::Number
11139 | SyntaxKind::Dimension
11140 )
11141}
11142
11143fn is_combinator(kind: SyntaxKind) -> bool {
11144 matches!(
11145 kind,
11146 SyntaxKind::GreaterThan
11147 | SyntaxKind::Plus
11148 | SyntaxKind::Tilde
11149 | SyntaxKind::ColumnCombinator
11150 )
11151}
11152
11153fn infix_binding_power(kind: SyntaxKind) -> Option<(u8, u8)> {
11154 match kind {
11155 SyntaxKind::Plus | SyntaxKind::Minus => Some((1, 2)),
11156 SyntaxKind::Star | SyntaxKind::Slash | SyntaxKind::Percent => Some((3, 4)),
11157 _ => None,
11158 }
11159}
11160
11161fn specialized_function_kind(text: &str) -> Option<SyntaxKind> {
11162 if text.eq_ignore_ascii_case("var") {
11163 return Some(SyntaxKind::VarFunction);
11164 }
11165 if text.eq_ignore_ascii_case("calc") {
11166 return Some(SyntaxKind::CalcFunction);
11167 }
11168 if text.eq_ignore_ascii_case("env") {
11169 return Some(SyntaxKind::EnvFunction);
11170 }
11171 if text.eq_ignore_ascii_case("attr") {
11172 return Some(SyntaxKind::AttrFunction);
11173 }
11174 if matches_ignore_ascii_case(text, VALUES_L4_MATH_FUNCTION_NAMES) {
11175 return Some(SyntaxKind::MathFunction);
11176 }
11177 if matches_ignore_ascii_case(text, CSS_COLOR_FUNCTION_NAMES) {
11178 return Some(SyntaxKind::ColorValue);
11179 }
11180 if matches_ignore_ascii_case(text, CSS_GRADIENT_FUNCTION_NAMES) {
11181 return Some(SyntaxKind::GradientFunction);
11182 }
11183 if matches_ignore_ascii_case(text, CSS_TRANSFORM_FUNCTION_NAMES) {
11184 return Some(SyntaxKind::TransformFunction);
11185 }
11186 if matches_ignore_ascii_case(text, CSS_FILTER_FUNCTION_NAMES) {
11187 return Some(SyntaxKind::FilterFunction);
11188 }
11189 if matches_ignore_ascii_case(text, CSS_IMAGE_FUNCTION_NAMES) {
11190 return Some(SyntaxKind::ImageFunction);
11191 }
11192 if matches_ignore_ascii_case(text, CSS_SHAPE_FUNCTION_NAMES) {
11193 return Some(SyntaxKind::ShapeFunction);
11194 }
11195 None
11196}
11197
11198fn function_argument_count_is_valid(function_name: &str, argument_count: usize) -> bool {
11199 if function_name.eq_ignore_ascii_case("calc") {
11200 return argument_count == 1;
11201 }
11202 if matches_ignore_ascii_case(function_name, &["min", "max", "hypot"]) {
11203 return argument_count >= 1;
11204 }
11205 if function_name.eq_ignore_ascii_case("clamp") {
11206 return argument_count == 3;
11207 }
11208 if function_name.eq_ignore_ascii_case("round") {
11209 return (2..=3).contains(&argument_count);
11210 }
11211 if function_name.eq_ignore_ascii_case("log") {
11212 return (1..=2).contains(&argument_count);
11213 }
11214 if matches_ignore_ascii_case(function_name, &["mod", "rem", "pow", "atan2"]) {
11215 return argument_count == 2;
11216 }
11217 if matches_ignore_ascii_case(
11218 function_name,
11219 &[
11220 "sin", "cos", "tan", "asin", "acos", "atan", "sqrt", "exp", "abs", "sign",
11221 ],
11222 ) {
11223 return argument_count == 1;
11224 }
11225 if function_name.eq_ignore_ascii_case("color-mix") {
11226 return argument_count == 3;
11227 }
11228 if function_name.eq_ignore_ascii_case("light-dark") {
11229 return argument_count == 2;
11230 }
11231 if function_name.eq_ignore_ascii_case("contrast-color") {
11232 return argument_count == 1;
11233 }
11234 true
11235}
11236
11237fn function_requires_filled_top_level_arguments(function_name: &str) -> bool {
11238 function_name.eq_ignore_ascii_case("calc")
11239 || matches_ignore_ascii_case(function_name, VALUES_L4_MATH_FUNCTION_NAMES)
11240 || matches_ignore_ascii_case(
11241 function_name,
11242 &["color-mix", "light-dark", "contrast-color"],
11243 )
11244}
11245
11246fn at_rule_prelude_head_is_custom_property_name(kind: SyntaxKind) -> bool {
11247 kind == SyntaxKind::CustomPropertyName || is_interpolation_start(kind)
11248}
11249
11250fn at_rule_prelude_head_is_custom_ident(kind: SyntaxKind) -> bool {
11251 kind == SyntaxKind::Ident || is_interpolation_start(kind)
11252}
11253
11254fn is_dynamic_function_argument_head(kind: SyntaxKind) -> bool {
11255 matches!(
11256 kind,
11257 SyntaxKind::ScssVariable
11258 | SyntaxKind::LessVariable
11259 | SyntaxKind::ScssInterpolationStart
11260 | SyntaxKind::LessInterpolationStart
11261 )
11262}
11263
11264fn is_scss_module_source_token(kind: SyntaxKind) -> bool {
11265 matches!(
11266 kind,
11267 SyntaxKind::String | SyntaxKind::Url | SyntaxKind::ScssInterpolationStart
11268 )
11269}
11270
11271fn is_scss_module_namespace_token(kind: SyntaxKind) -> bool {
11272 matches!(
11273 kind,
11274 SyntaxKind::Ident | SyntaxKind::Star | SyntaxKind::ScssInterpolationStart
11275 )
11276}
11277
11278fn is_scss_module_visibility_name_token(kind: SyntaxKind) -> bool {
11279 matches!(
11280 kind,
11281 SyntaxKind::Ident
11282 | SyntaxKind::ScssVariable
11283 | SyntaxKind::ScssPlaceholder
11284 | SyntaxKind::ScssInterpolationStart
11285 )
11286}
11287
11288fn is_css_module_from_source_token(kind: SyntaxKind, text: &str) -> bool {
11289 matches!(
11290 kind,
11291 SyntaxKind::String
11292 | SyntaxKind::Url
11293 | SyntaxKind::ScssInterpolationStart
11294 | SyntaxKind::LessInterpolationStart
11295 ) || (kind == SyntaxKind::Ident && text == "global")
11296}
11297
11298fn is_scss_control_rule_kind(kind: SyntaxKind) -> bool {
11299 matches!(
11300 kind,
11301 SyntaxKind::ScssControlIf
11302 | SyntaxKind::ScssControlElse
11303 | SyntaxKind::ScssControlEach
11304 | SyntaxKind::ScssControlFor
11305 | SyntaxKind::ScssControlWhile
11306 )
11307}
11308
11309fn matches_ignore_ascii_case(value: &str, candidates: &[&str]) -> bool {
11310 candidates
11311 .iter()
11312 .any(|candidate| value.eq_ignore_ascii_case(candidate))
11313}
11314
11315fn css_module_scope_function_kind(text: &str) -> Option<SyntaxKind> {
11316 match text {
11317 "local" => Some(SyntaxKind::CssModuleLocalBlock),
11318 "global" => Some(SyntaxKind::CssModuleGlobalBlock),
11319 _ => None,
11320 }
11321}
11322
11323fn text_range(start: usize, end: usize) -> TextRange {
11324 TextRange::new(TextSize::from(start as u32), TextSize::from(end as u32))
11325}
11326
11327#[cfg(test)]
11328mod tests {
11329 use super::*;
11330
11331 #[test]
11332 fn builds_cst_root_for_plain_css() {
11333 let result = parse(".button { color: red; }", StyleDialect::Css);
11334
11335 assert_eq!(result.syntax().kind(), SyntaxKind::Root);
11336 assert_eq!(result.dialect(), StyleDialect::Css);
11337 assert!(
11338 result.errors().is_empty(),
11339 "unexpected parse errors: {:?}",
11340 result.errors()
11341 );
11342 assert!(result.token_count() > 0);
11343
11344 let kinds = node_kinds(&result.syntax());
11345 assert!(kinds.contains(&SyntaxKind::Rule));
11346 assert!(kinds.contains(&SyntaxKind::SelectorList));
11347 assert!(kinds.contains(&SyntaxKind::DeclarationList));
11348 assert!(kinds.contains(&SyntaxKind::Declaration));
11349 assert!(kinds.contains(&SyntaxKind::PropertyName));
11350 assert!(kinds.contains(&SyntaxKind::Value));
11351 }
11352
11353 #[test]
11354 fn exposes_css_syntax_parser_entry_points() {
11355 let rule_list = parse_entry_point(
11356 ".button { color: red; } @media (width >= 1px) { .card { color: blue; } }",
11357 StyleDialect::Css,
11358 ParseEntryPoint::RuleList,
11359 );
11360 let rule = parse_entry_point(
11361 ".button { color: red; }",
11362 StyleDialect::Css,
11363 ParseEntryPoint::Rule,
11364 );
11365 let declaration_list = parse_entry_point(
11366 "color: red; width: calc(1px + 2px);",
11367 StyleDialect::Css,
11368 ParseEntryPoint::DeclarationList,
11369 );
11370 let declaration = parse_entry_point(
11371 "color: red;",
11372 StyleDialect::Css,
11373 ParseEntryPoint::Declaration,
11374 );
11375 let value = parse_entry_point(
11376 "clamp(1rem, calc(2px + 3px), 4rem)",
11377 StyleDialect::Css,
11378 ParseEntryPoint::Value,
11379 );
11380 let component_value = parse_entry_point(
11381 "calc(100% - var(--gap))",
11382 StyleDialect::Css,
11383 ParseEntryPoint::ComponentValue,
11384 );
11385 let component_value_list = parse_entry_point(
11386 "red + calc(1px + 2px) [data-state]",
11387 StyleDialect::Css,
11388 ParseEntryPoint::ComponentValueList,
11389 );
11390 let comma_separated_component_value_list = parse_entry_point(
11391 "red, calc(1px + 2px), [data-state]",
11392 StyleDialect::Css,
11393 ParseEntryPoint::CommaSeparatedComponentValueList,
11394 );
11395 let simple_block = parse_entry_point(
11396 "{ color: red; [data-state] }",
11397 StyleDialect::Css,
11398 ParseEntryPoint::SimpleBlock,
11399 );
11400 let unclosed_simple_block = parse_entry_point(
11401 "{ color: red",
11402 StyleDialect::Css,
11403 ParseEntryPoint::SimpleBlock,
11404 );
11405
11406 assert!(rule_list.errors().is_empty());
11407 assert!(rule.errors().is_empty());
11408 assert!(declaration_list.errors().is_empty());
11409 assert!(declaration.errors().is_empty());
11410 assert!(value.errors().is_empty());
11411 assert!(component_value.errors().is_empty());
11412 assert!(component_value_list.errors().is_empty());
11413 assert!(comma_separated_component_value_list.errors().is_empty());
11414 assert!(simple_block.errors().is_empty());
11415 assert_eq!(unclosed_simple_block.errors().len(), 1);
11416 assert!(node_kinds(&rule_list.syntax()).contains(&SyntaxKind::RuleList));
11417 assert!(node_kinds(&rule.syntax()).contains(&SyntaxKind::Rule));
11418 assert!(node_kinds(&declaration_list.syntax()).contains(&SyntaxKind::DeclarationList));
11419 assert!(node_kinds(&declaration.syntax()).contains(&SyntaxKind::Declaration));
11420 assert!(node_kinds(&value.syntax()).contains(&SyntaxKind::Value));
11421 assert!(node_kinds(&value.syntax()).contains(&SyntaxKind::CalcFunction));
11422 assert!(node_kinds(&component_value.syntax()).contains(&SyntaxKind::ComponentValue));
11423 assert!(node_kinds(&component_value.syntax()).contains(&SyntaxKind::FunctionCall));
11424 assert!(
11425 node_kinds(&component_value_list.syntax()).contains(&SyntaxKind::ComponentValueList)
11426 );
11427 assert!(
11428 node_kinds(&comma_separated_component_value_list.syntax())
11429 .contains(&SyntaxKind::CommaSeparatedComponentValueList)
11430 );
11431 assert!(node_kinds(&simple_block.syntax()).contains(&SyntaxKind::SimpleBlock));
11432 assert!(node_kinds(&simple_block.syntax()).contains(&SyntaxKind::ComponentValue));
11433 assert!(
11434 node_kinds(&unclosed_simple_block.syntax()).contains(&SyntaxKind::BogusSimpleBlock)
11435 );
11436 }
11437
11438 #[test]
11439 fn tokenizes_multibyte_source_without_boundary_errors() {
11440 let result = parse(".카드 { --간격: \"좋음\"; }", StyleDialect::Css);
11441
11442 assert!(
11443 result.errors().is_empty(),
11444 "unexpected parse errors: {:?}",
11445 result.errors()
11446 );
11447 assert!(result.token_count() >= 8);
11448 }
11449
11450 #[test]
11451 fn reports_unterminated_constructs_without_panicking() {
11452 let comment = parse("/* open", StyleDialect::Css);
11453 let string = parse(".a { content: \"open; }", StyleDialect::Css);
11454 let block = parse(".a { color: red", StyleDialect::Css);
11455
11456 assert_eq!(
11457 comment.errors().first().map(|error| error.code),
11458 Some(ParseErrorCode::UnterminatedBlockComment),
11459 );
11460 assert_eq!(
11461 string.errors().first().map(|error| error.code),
11462 Some(ParseErrorCode::UnterminatedString),
11463 );
11464 assert_eq!(
11465 block.errors().first().map(|error| error.code),
11466 Some(ParseErrorCode::UnexpectedCharacter),
11467 );
11468 assert!(node_kinds(&block.syntax()).contains(&SyntaxKind::BogusTrivia));
11469 }
11470
11471 #[test]
11472 fn classifies_initial_dialect_tokens() {
11473 let scss = parse("$gap: 1rem;", StyleDialect::Scss);
11474 let less = parse("@gap: 1rem;", StyleDialect::Less);
11475 let less_at_rule = parse("@media screen {}", StyleDialect::Less);
11476 let scss_kinds = node_kinds(&scss.syntax());
11477 let less_kinds = node_kinds(&less.syntax());
11478
11479 assert_eq!(scss.syntax().kind(), SyntaxKind::Root);
11480 assert_eq!(less.syntax().kind(), SyntaxKind::Root);
11481 assert_eq!(less_at_rule.syntax().kind(), SyntaxKind::Root);
11482 assert!(scss.errors().is_empty());
11483 assert!(less.errors().is_empty());
11484 assert!(less_at_rule.errors().is_empty());
11485 assert!(scss_kinds.contains(&SyntaxKind::ScssVariableDeclaration));
11486 assert!(less_kinds.contains(&SyntaxKind::LessVariableDeclaration));
11487 }
11488
11489 #[test]
11490 fn exposes_lex_result_for_tokenizer_gates() {
11491 let scss = lex("$gap: 1rem;", StyleDialect::Scss);
11492 let less = lex("@gap: 1rem;", StyleDialect::Less);
11493 let less_at_rule = lex("@media screen {}", StyleDialect::Less);
11494 let css_slashes = lex("// not a css comment", StyleDialect::Css);
11495 let scss_slashes = lex("// scss comment", StyleDialect::Scss);
11496
11497 assert_eq!(
11498 scss.tokens().first().map(|token| token.kind),
11499 Some(SyntaxKind::ScssVariable)
11500 );
11501 assert_eq!(
11502 scss.tokens().first().map(|token| token.text.as_str()),
11503 Some("$gap")
11504 );
11505 assert_eq!(
11506 less.tokens().first().map(|token| token.kind),
11507 Some(SyntaxKind::LessVariable)
11508 );
11509 assert_eq!(
11510 less_at_rule.tokens().first().map(|token| token.kind),
11511 Some(SyntaxKind::AtKeyword),
11512 );
11513 assert_eq!(
11514 css_slashes.tokens().first().map(|token| token.kind),
11515 Some(SyntaxKind::Slash)
11516 );
11517 assert_eq!(
11518 scss_slashes.tokens().first().map(|token| token.kind),
11519 Some(SyntaxKind::LineComment),
11520 );
11521 }
11522
11523 #[test]
11524 fn summarizes_parser_lex_as_parser_owned_product() {
11525 let summary = summarize_omena_parser_lex(".card { color: red; }", StyleDialect::Css);
11526
11527 assert_eq!(summary.schema_version, "0");
11528 assert_eq!(summary.product, "omena-parser.lex-result");
11529 assert_eq!(summary.dialect, "css");
11530 assert_eq!(summary.parser_error_count, 0);
11531 assert!(summary.tokens.iter().any(|token| token.text == "card"));
11532 }
11533
11534 #[test]
11535 fn tokenizes_css_attribute_matchers_as_single_tokens() {
11536 let result = lex(
11537 ".a[data-state~=\"active\"][lang|=\"en\"][href^=\"/docs\"][href$=\".pdf\"][class*=\"btn\"] { width += 1px; }",
11538 StyleDialect::Css,
11539 );
11540 let kinds: Vec<SyntaxKind> = result.tokens().iter().map(|token| token.kind).collect();
11541
11542 assert!(result.errors().is_empty());
11543 assert!(kinds.contains(&SyntaxKind::IncludesMatch));
11544 assert!(kinds.contains(&SyntaxKind::DashMatch));
11545 assert!(kinds.contains(&SyntaxKind::PrefixMatch));
11546 assert!(kinds.contains(&SyntaxKind::SuffixMatch));
11547 assert!(kinds.contains(&SyntaxKind::SubstringMatch));
11548 assert!(kinds.contains(&SyntaxKind::PlusEquals));
11549 }
11550
11551 #[test]
11552 fn tokenizes_important_annotation_as_single_token() {
11553 let result = lex(".a { color: red !IMPORTANT; }", StyleDialect::Css);
11554 let kinds: Vec<SyntaxKind> = result.tokens().iter().map(|token| token.kind).collect();
11555
11556 assert!(result.errors().is_empty());
11557 assert!(kinds.contains(&SyntaxKind::Important));
11558 assert!(!kinds.contains(&SyntaxKind::Delim));
11559 }
11560
11561 #[test]
11562 fn tokenizes_cdo_cdc_and_ignores_them_at_top_level() {
11563 let result = parse("<!-- .a { color: red; } -->", StyleDialect::Css);
11564 let token_kinds = token_kinds(&result.syntax());
11565
11566 assert!(result.errors().is_empty());
11567 assert!(token_kinds.contains(&SyntaxKind::Cdo));
11568 assert!(token_kinds.contains(&SyntaxKind::Cdc));
11569 assert!(node_kinds(&result.syntax()).contains(&SyntaxKind::Rule));
11570 }
11571
11572 #[test]
11573 fn tokenizes_css_identifier_escapes_without_unexpected_errors() {
11574 let result = parse(".\\31 0 { color: var(--\\67 ap); }", StyleDialect::Css);
11575 let token_kinds = token_kinds(&result.syntax());
11576
11577 assert!(result.errors().is_empty());
11578 assert!(token_kinds.contains(&SyntaxKind::Ident));
11579 assert!(token_kinds.contains(&SyntaxKind::CustomPropertyName));
11580 assert!(node_kinds(&result.syntax()).contains(&SyntaxKind::ClassSelector));
11581 }
11582
11583 #[test]
11584 fn tokenizes_bare_hash_as_delim_and_hash_names_as_hash() {
11585 let bare = lex("# { color: red; }", StyleDialect::Css);
11586 let named = lex("#main { color: red; }", StyleDialect::Css);
11587 let escaped = lex("#\\31 0 { color: red; }", StyleDialect::Css);
11588 let bare_kinds: Vec<SyntaxKind> = bare.tokens().iter().map(|token| token.kind).collect();
11589 let named_kinds: Vec<SyntaxKind> = named.tokens().iter().map(|token| token.kind).collect();
11590 let escaped_kinds: Vec<SyntaxKind> =
11591 escaped.tokens().iter().map(|token| token.kind).collect();
11592
11593 assert!(bare.errors().is_empty());
11594 assert!(named.errors().is_empty());
11595 assert!(escaped.errors().is_empty());
11596 assert!(bare_kinds.contains(&SyntaxKind::Delim));
11597 assert!(!bare_kinds.contains(&SyntaxKind::Hash));
11598 assert!(named_kinds.contains(&SyntaxKind::Hash));
11599 assert!(escaped_kinds.contains(&SyntaxKind::Hash));
11600 }
11601
11602 #[test]
11603 fn tokenizes_dash_started_idents_and_custom_properties_by_ident_rules() {
11604 let vendor = lex("-webkit-transform", StyleDialect::Css);
11605 let custom = lex("--brand", StyleDialect::Css);
11606 let escaped_custom = lex("--\\31 0", StyleDialect::Css);
11607 let bare_dash = lex("--:", StyleDialect::Css);
11608 let vendor_kinds: Vec<SyntaxKind> =
11609 vendor.tokens().iter().map(|token| token.kind).collect();
11610 let custom_kinds: Vec<SyntaxKind> =
11611 custom.tokens().iter().map(|token| token.kind).collect();
11612 let escaped_custom_kinds: Vec<SyntaxKind> = escaped_custom
11613 .tokens()
11614 .iter()
11615 .map(|token| token.kind)
11616 .collect();
11617 let bare_dash_kinds: Vec<SyntaxKind> =
11618 bare_dash.tokens().iter().map(|token| token.kind).collect();
11619
11620 assert!(vendor.errors().is_empty());
11621 assert!(custom.errors().is_empty());
11622 assert!(escaped_custom.errors().is_empty());
11623 assert!(bare_dash.errors().is_empty());
11624 assert!(vendor_kinds.contains(&SyntaxKind::Ident));
11625 assert!(!vendor_kinds.contains(&SyntaxKind::Minus));
11626 assert!(custom_kinds.contains(&SyntaxKind::CustomPropertyName));
11627 assert!(escaped_custom_kinds.contains(&SyntaxKind::CustomPropertyName));
11628 assert!(!bare_dash_kinds.contains(&SyntaxKind::CustomPropertyName));
11629 assert!(bare_dash_kinds.contains(&SyntaxKind::Ident));
11630 }
11631
11632 #[test]
11633 fn tokenizes_signed_and_leading_dot_numbers_as_single_numeric_tokens() {
11634 let signed_number = lex("+1.5", StyleDialect::Css);
11635 let signed_dimension = lex("-2px", StyleDialect::Css);
11636 let leading_dot = lex(".5", StyleDialect::Css);
11637 let spaced_plus = lex("+ 1.5", StyleDialect::Css);
11638 let trailing_dot = lex("1.", StyleDialect::Css);
11639 let signed_number_kinds: Vec<SyntaxKind> = signed_number
11640 .tokens()
11641 .iter()
11642 .map(|token| token.kind)
11643 .collect();
11644 let signed_dimension_kinds: Vec<SyntaxKind> = signed_dimension
11645 .tokens()
11646 .iter()
11647 .map(|token| token.kind)
11648 .collect();
11649 let leading_dot_kinds: Vec<SyntaxKind> = leading_dot
11650 .tokens()
11651 .iter()
11652 .map(|token| token.kind)
11653 .collect();
11654 let spaced_plus_kinds: Vec<SyntaxKind> = spaced_plus
11655 .tokens()
11656 .iter()
11657 .map(|token| token.kind)
11658 .collect();
11659 let trailing_dot_kinds: Vec<SyntaxKind> = trailing_dot
11660 .tokens()
11661 .iter()
11662 .map(|token| token.kind)
11663 .collect();
11664
11665 assert!(signed_number.errors().is_empty());
11666 assert!(signed_dimension.errors().is_empty());
11667 assert!(leading_dot.errors().is_empty());
11668 assert!(spaced_plus.errors().is_empty());
11669 assert!(trailing_dot.errors().is_empty());
11670 assert_eq!(signed_number_kinds, vec![SyntaxKind::Number]);
11671 assert_eq!(signed_dimension_kinds, vec![SyntaxKind::Dimension]);
11672 assert_eq!(leading_dot_kinds, vec![SyntaxKind::Number]);
11673 assert!(spaced_plus_kinds.contains(&SyntaxKind::Plus));
11674 assert!(spaced_plus_kinds.contains(&SyntaxKind::Number));
11675 assert_eq!(
11676 trailing_dot_kinds,
11677 vec![SyntaxKind::Number, SyntaxKind::Dot]
11678 );
11679 }
11680
11681 #[test]
11682 fn tokenizes_exponent_numbers_before_dimension_suffixes() {
11683 let exponent = lex("1e3", StyleDialect::Css);
11684 let signed_exponent = lex("1e-3", StyleDialect::Css);
11685 let exponent_dimension = lex("1e3px", StyleDialect::Css);
11686 let plain_dimension = lex("1em", StyleDialect::Css);
11687 let exponent_kinds: Vec<SyntaxKind> =
11688 exponent.tokens().iter().map(|token| token.kind).collect();
11689 let signed_exponent_kinds: Vec<SyntaxKind> = signed_exponent
11690 .tokens()
11691 .iter()
11692 .map(|token| token.kind)
11693 .collect();
11694 let exponent_dimension_kinds: Vec<SyntaxKind> = exponent_dimension
11695 .tokens()
11696 .iter()
11697 .map(|token| token.kind)
11698 .collect();
11699 let plain_dimension_kinds: Vec<SyntaxKind> = plain_dimension
11700 .tokens()
11701 .iter()
11702 .map(|token| token.kind)
11703 .collect();
11704
11705 assert!(exponent.errors().is_empty());
11706 assert!(signed_exponent.errors().is_empty());
11707 assert!(exponent_dimension.errors().is_empty());
11708 assert!(plain_dimension.errors().is_empty());
11709 assert_eq!(exponent_kinds, vec![SyntaxKind::Number]);
11710 assert_eq!(signed_exponent_kinds, vec![SyntaxKind::Number]);
11711 assert_eq!(exponent_dimension_kinds, vec![SyntaxKind::Dimension]);
11712 assert_eq!(plain_dimension_kinds, vec![SyntaxKind::Dimension]);
11713 }
11714
11715 #[test]
11716 fn tokenizes_null_and_bom_without_unexpected_errors() {
11717 let result = parse("\u{feff}.a\0b { content: \0; }", StyleDialect::Css);
11718 let lexed = lex(
11719 "\u{feff}.a\0b { background: url(foo\0bar); }",
11720 StyleDialect::Css,
11721 );
11722 let token_kinds = token_kinds(&result.syntax());
11723 let ident = lexed
11724 .tokens()
11725 .iter()
11726 .find(|token| token.kind == SyntaxKind::Ident)
11727 .map(|token| token.text.as_str());
11728 let url = lexed
11729 .tokens()
11730 .iter()
11731 .find(|token| token.kind == SyntaxKind::Url)
11732 .map(|token| token.text.as_str());
11733
11734 assert!(result.errors().is_empty());
11735 assert!(lexed.errors().is_empty());
11736 assert_eq!(
11737 lexed.tokens().first().map(|token| token.kind),
11738 Some(SyntaxKind::Dot)
11739 );
11740 assert_eq!(ident, Some("a\u{fffd}b"));
11741 assert_eq!(url, Some("url(foo\u{fffd}bar)"));
11742 assert!(
11743 !lexed
11744 .tokens()
11745 .iter()
11746 .any(|token| token.text.contains('\0') || token.text.contains('\u{feff}'))
11747 );
11748 assert!(token_kinds.contains(&SyntaxKind::Whitespace));
11749 assert!(token_kinds.contains(&SyntaxKind::Ident));
11750 assert!(node_kinds(&result.syntax()).contains(&SyntaxKind::ClassSelector));
11751 }
11752
11753 #[test]
11754 fn tokenizes_unquoted_urls_and_bad_urls() {
11755 let good = lex(".a { background: url(images/bg.png); }", StyleDialect::Css);
11756 let bad = lex(".a { background: url(foo\"bar); }", StyleDialect::Css);
11757 let bad_whitespace = lex(".a { background: url(foo bar); }", StyleDialect::Css);
11758 let bad_escape = lex(".a { background: url(foo\\\nbar); }", StyleDialect::Css);
11759 let trailing_whitespace = lex(".a { background: url(foo \n ); }", StyleDialect::Css);
11760 let quoted = lex(
11761 ".a { background: url(\"images/bg.png\"); }",
11762 StyleDialect::Css,
11763 );
11764 let good_kinds: Vec<SyntaxKind> = good.tokens().iter().map(|token| token.kind).collect();
11765 let bad_kinds: Vec<SyntaxKind> = bad.tokens().iter().map(|token| token.kind).collect();
11766 let bad_whitespace_kinds: Vec<SyntaxKind> = bad_whitespace
11767 .tokens()
11768 .iter()
11769 .map(|token| token.kind)
11770 .collect();
11771 let bad_escape_kinds: Vec<SyntaxKind> =
11772 bad_escape.tokens().iter().map(|token| token.kind).collect();
11773 let trailing_whitespace_kinds: Vec<SyntaxKind> = trailing_whitespace
11774 .tokens()
11775 .iter()
11776 .map(|token| token.kind)
11777 .collect();
11778 let quoted_kinds: Vec<SyntaxKind> =
11779 quoted.tokens().iter().map(|token| token.kind).collect();
11780
11781 assert!(good.errors().is_empty());
11782 assert!(good_kinds.contains(&SyntaxKind::Url));
11783 assert!(bad_kinds.contains(&SyntaxKind::BadUrl));
11784 assert!(!bad.errors().is_empty());
11785 assert!(bad_whitespace_kinds.contains(&SyntaxKind::BadUrl));
11786 assert!(!bad_whitespace.errors().is_empty());
11787 assert!(bad_escape_kinds.contains(&SyntaxKind::BadUrl));
11788 assert!(!bad_escape.errors().is_empty());
11789 assert!(trailing_whitespace.errors().is_empty());
11790 assert!(trailing_whitespace_kinds.contains(&SyntaxKind::Url));
11791 assert!(quoted_kinds.contains(&SyntaxKind::Ident));
11792 assert!(quoted_kinds.contains(&SyntaxKind::String));
11793 assert!(!quoted_kinds.contains(&SyntaxKind::Url));
11794 }
11795
11796 #[test]
11797 fn tokenizes_unicode_ranges() {
11798 let result = lex(
11799 "@font-face { unicode-range: U+00A0-00FF, u+4??; }",
11800 StyleDialect::Css,
11801 );
11802 let kinds: Vec<SyntaxKind> = result.tokens().iter().map(|token| token.kind).collect();
11803
11804 assert!(result.errors().is_empty());
11805 assert_eq!(
11806 kinds
11807 .iter()
11808 .filter(|kind| **kind == SyntaxKind::UnicodeRange)
11809 .count(),
11810 2
11811 );
11812 }
11813
11814 #[test]
11815 fn tokenizes_scss_interpolation_delimiters() {
11816 let scss = lex(
11817 ".button-#{$variant} { color: #{$color}; }",
11818 StyleDialect::Scss,
11819 );
11820 let css = lex(".button-#{$variant} { color: red; }", StyleDialect::Css);
11821 let scss_kinds: Vec<SyntaxKind> = scss.tokens().iter().map(|token| token.kind).collect();
11822 let css_kinds: Vec<SyntaxKind> = css.tokens().iter().map(|token| token.kind).collect();
11823
11824 assert!(scss.errors().is_empty());
11825 assert!(scss_kinds.contains(&SyntaxKind::ScssInterpolationStart));
11826 assert!(scss_kinds.contains(&SyntaxKind::ScssInterpolationEnd));
11827 assert!(!css_kinds.contains(&SyntaxKind::ScssInterpolationStart));
11828 }
11829
11830 #[test]
11831 fn tokenizes_scss_placeholder_selectors() {
11832 let scss = lex("%button { color: red; }", StyleDialect::Scss);
11833 let css = lex("%button { color: red; }", StyleDialect::Css);
11834 let scss_kinds: Vec<SyntaxKind> = scss.tokens().iter().map(|token| token.kind).collect();
11835 let css_kinds: Vec<SyntaxKind> = css.tokens().iter().map(|token| token.kind).collect();
11836
11837 assert!(scss.errors().is_empty());
11838 assert!(scss_kinds.contains(&SyntaxKind::ScssPlaceholder));
11839 assert!(css_kinds.contains(&SyntaxKind::Percent));
11840 assert!(!css_kinds.contains(&SyntaxKind::ScssPlaceholder));
11841 }
11842
11843 #[test]
11844 fn tokenizes_sass_indented_block_markers() {
11845 let result = lex(
11846 ".card\n color: red // comment\n .title\n color: blue\n",
11847 StyleDialect::Sass,
11848 );
11849 let kinds: Vec<SyntaxKind> = result.tokens().iter().map(|token| token.kind).collect();
11850
11851 assert!(result.errors().is_empty());
11852 assert!(kinds.contains(&SyntaxKind::LineComment));
11853 assert!(kinds.contains(&SyntaxKind::SassIndentedNewline));
11854 assert!(kinds.contains(&SyntaxKind::SassOptionalSemicolon));
11855 assert_eq!(
11856 kinds
11857 .iter()
11858 .filter(|kind| **kind == SyntaxKind::SassIndent)
11859 .count(),
11860 2
11861 );
11862 assert_eq!(
11863 kinds
11864 .iter()
11865 .filter(|kind| **kind == SyntaxKind::SassDedent)
11866 .count(),
11867 2
11868 );
11869 }
11870
11871 #[test]
11872 fn tokenizes_less_interpolation_delimiters() {
11873 let less = lex(
11874 ".button-@{variant} { color: @{color}; }",
11875 StyleDialect::Less,
11876 );
11877 let css = lex(".button-@{variant} { color: red; }", StyleDialect::Css);
11878 let less_kinds: Vec<SyntaxKind> = less.tokens().iter().map(|token| token.kind).collect();
11879 let css_kinds: Vec<SyntaxKind> = css.tokens().iter().map(|token| token.kind).collect();
11880
11881 assert!(less.errors().is_empty());
11882 assert!(less_kinds.contains(&SyntaxKind::LessInterpolationStart));
11883 assert!(less_kinds.contains(&SyntaxKind::LessInterpolationEnd));
11884 assert!(!css_kinds.contains(&SyntaxKind::LessInterpolationStart));
11885 }
11886
11887 #[test]
11888 fn tokenizes_less_escaped_strings() {
11889 let less = lex(".a { filter: ~\"alpha(opacity=50)\"; }", StyleDialect::Less);
11890 let css = lex(".a { filter: ~\"alpha(opacity=50)\"; }", StyleDialect::Css);
11891 let less_kinds: Vec<SyntaxKind> = less.tokens().iter().map(|token| token.kind).collect();
11892 let css_kinds: Vec<SyntaxKind> = css.tokens().iter().map(|token| token.kind).collect();
11893
11894 assert!(less.errors().is_empty());
11895 assert!(less_kinds.contains(&SyntaxKind::LessEscapedString));
11896 assert!(!css_kinds.contains(&SyntaxKind::LessEscapedString));
11897 assert!(css_kinds.contains(&SyntaxKind::Tilde));
11898 assert!(css_kinds.contains(&SyntaxKind::String));
11899 }
11900
11901 #[test]
11902 fn tokenizes_less_property_variables_without_breaking_suffix_matchers() {
11903 let less = lex(
11904 ".a { background: $color; [data-x$=y] {} }",
11905 StyleDialect::Less,
11906 );
11907 let scss = lex(".a { background: $color; }", StyleDialect::Scss);
11908 let less_kinds: Vec<SyntaxKind> = less.tokens().iter().map(|token| token.kind).collect();
11909 let scss_kinds: Vec<SyntaxKind> = scss.tokens().iter().map(|token| token.kind).collect();
11910
11911 assert!(less.errors().is_empty());
11912 assert!(scss.errors().is_empty());
11913 assert!(less_kinds.contains(&SyntaxKind::LessPropertyVariableToken));
11914 assert!(less_kinds.contains(&SyntaxKind::SuffixMatch));
11915 assert!(!less_kinds.contains(&SyntaxKind::ScssVariable));
11916 assert!(scss_kinds.contains(&SyntaxKind::ScssVariable));
11917 }
11918
11919 #[test]
11920 fn tokenizes_newline_bad_strings() {
11921 let result = lex(".a { content: \"bad\nstill-here: red; }", StyleDialect::Css);
11922 let kinds: Vec<SyntaxKind> = result.tokens().iter().map(|token| token.kind).collect();
11923
11924 assert!(kinds.contains(&SyntaxKind::BadString));
11925 assert!(
11926 result
11927 .errors()
11928 .iter()
11929 .any(|error| error.code == ParseErrorCode::UnterminatedString)
11930 );
11931 }
11932
11933 #[test]
11934 fn exposes_recovery_token_sets() {
11935 assert!(RECOVERY_TOP.contains(SyntaxKind::AtKeyword));
11936 assert!(RECOVERY_DECLARATION.contains(SyntaxKind::Semicolon));
11937 assert!(RECOVERY_SELECTOR.contains(SyntaxKind::LeftBrace));
11938 assert!(!RECOVERY_SELECTOR.is_empty());
11939 }
11940
11941 #[test]
11942 fn builds_at_rule_and_bogus_nodes_for_partial_input() {
11943 let at_rule = parse("@media screen { .a { color: red; } }", StyleDialect::Css);
11944 let missing_colon = parse(".a { color red; }", StyleDialect::Css);
11945 let missing_block = parse(".a color: red;", StyleDialect::Css);
11946
11947 assert!(node_kinds(&at_rule.syntax()).contains(&SyntaxKind::AtRule));
11948 assert!(node_kinds(&missing_colon.syntax()).contains(&SyntaxKind::BogusDeclaration));
11949 assert!(node_kinds(&missing_block.syntax()).contains(&SyntaxKind::BogusRule));
11950 }
11951
11952 #[test]
11953 fn builds_bogus_nodes_for_selector_and_value_recovery() {
11954 let missing_class_name = parse(". { color: red; }", StyleDialect::Css);
11955 let missing_attribute_end = parse(".a[data-active { color: red; }", StyleDialect::Css);
11956 let missing_value_rhs = parse(".a { width: calc(1 + ); }", StyleDialect::Css);
11957 let unexpected_value_token = parse(".a { color: @; }", StyleDialect::Css);
11958
11959 assert_eq!(
11960 missing_class_name.errors().first().map(|error| error.code),
11961 Some(ParseErrorCode::ExpectedSelectorName)
11962 );
11963 assert_eq!(
11964 missing_attribute_end
11965 .errors()
11966 .first()
11967 .map(|error| error.code),
11968 Some(ParseErrorCode::UnterminatedAttributeSelector)
11969 );
11970 assert!(
11971 missing_value_rhs
11972 .errors()
11973 .iter()
11974 .any(|error| error.code == ParseErrorCode::ExpectedValue)
11975 );
11976 assert!(node_kinds(&missing_class_name.syntax()).contains(&SyntaxKind::BogusSelector));
11977 assert!(node_kinds(&missing_attribute_end.syntax()).contains(&SyntaxKind::BogusSelector));
11978 assert!(node_kinds(&missing_value_rhs.syntax()).contains(&SyntaxKind::BogusValue));
11979 assert!(node_kinds(&unexpected_value_token.syntax()).contains(&SyntaxKind::BogusValue));
11980 }
11981
11982 #[test]
11983 fn recovers_empty_declaration_values_without_rejecting_custom_properties() {
11984 let result = parse(".a { color: ; width: ; --empty: ; }", StyleDialect::Css);
11985 let kinds = node_kinds(&result.syntax());
11986 let empty_value_errors = result
11987 .errors()
11988 .iter()
11989 .filter(|error| error.message == "expected declaration value")
11990 .count();
11991 let bogus_value_count = kinds
11992 .iter()
11993 .filter(|kind| **kind == SyntaxKind::BogusValue)
11994 .count();
11995
11996 assert_eq!(empty_value_errors, 2);
11997 assert_eq!(bogus_value_count, 2);
11998 assert!(kinds.contains(&SyntaxKind::CustomPropertyValue));
11999 }
12000
12001 #[test]
12002 fn recovers_empty_variable_values_without_rejecting_less_detached_rulesets() {
12003 let scss = parse("$gap: ;", StyleDialect::Scss);
12004 let less = parse("@gap: ; @ruleset: { color: red; };", StyleDialect::Less);
12005 let scss_kinds = node_kinds(&scss.syntax());
12006 let less_kinds = node_kinds(&less.syntax());
12007 let empty_value_errors = scss
12008 .errors()
12009 .iter()
12010 .chain(less.errors())
12011 .filter(|error| error.message == "expected variable value")
12012 .count();
12013
12014 assert_eq!(empty_value_errors, 2);
12015 assert!(scss_kinds.contains(&SyntaxKind::BogusValue));
12016 assert!(less_kinds.contains(&SyntaxKind::BogusValue));
12017 assert!(less_kinds.contains(&SyntaxKind::LessDetachedRulesetNode));
12018 }
12019
12020 #[test]
12021 fn recovers_missing_semicolons_between_declarations() {
12022 let result = parse(
12023 ".a { color: red background: blue; margin: 0 padding: 1rem; }",
12024 StyleDialect::Css,
12025 );
12026 let custom_property = parse(
12027 ".a { --token: red background: blue; color: red; }",
12028 StyleDialect::Css,
12029 );
12030 let kinds = node_kinds(&result.syntax());
12031 let custom_property_kinds = node_kinds(&custom_property.syntax());
12032 let declaration_count = kinds
12033 .iter()
12034 .filter(|kind| **kind == SyntaxKind::Declaration)
12035 .count();
12036 let custom_property_declaration_count = custom_property_kinds
12037 .iter()
12038 .filter(|kind| **kind == SyntaxKind::Declaration)
12039 .count();
12040 let missing_semicolon_errors = result
12041 .errors()
12042 .iter()
12043 .filter(|error| error.message == "expected semicolon between declarations")
12044 .count();
12045
12046 assert_eq!(declaration_count, 4);
12047 assert_eq!(missing_semicolon_errors, 2);
12048 assert_eq!(custom_property_declaration_count, 2);
12049 assert!(custom_property.errors().is_empty());
12050 assert!(custom_property_kinds.contains(&SyntaxKind::CustomPropertyValue));
12051 }
12052
12053 #[test]
12054 fn populates_core_bogus_nodes_for_recoverable_structures() {
12055 let missing_function_close =
12056 parse(".a { width: calc(1 + ; color: red; }", StyleDialect::Css);
12057 let missing_media_close = parse(
12058 "@media (min-width: { .a { color: red; } }",
12059 StyleDialect::Css,
12060 );
12061 let mixed_media_close = parse(
12062 "@media screen, (min-width: { .a { color: red; } }",
12063 StyleDialect::Css,
12064 );
12065 let missing_supports_close = parse(
12066 "@supports (display: { .a { color: red; } }",
12067 StyleDialect::Css,
12068 );
12069 let missing_container_close = parse(
12070 "@container (inline-size > { .a { color: red; } }",
12071 StyleDialect::Css,
12072 );
12073 let missing_unknown_prelude_close =
12074 parse("@unknown (min-width: { color: red; }", StyleDialect::Css);
12075 let missing_scope_close = parse("@scope (.a { .b { color: red; } }", StyleDialect::Css);
12076 let empty_layer_statement = parse("@layer ;", StyleDialect::Css);
12077 let missing_keyframe_block =
12078 parse("@keyframes fade { from opacity: 0; }", StyleDialect::Css);
12079 let unclosed_rule = parse(".a { color: red;", StyleDialect::Css);
12080
12081 assert!(
12082 node_kinds(&missing_function_close.syntax()).contains(&SyntaxKind::BogusFunctionCall)
12083 );
12084 assert!(
12085 node_kinds(&missing_function_close.syntax())
12086 .contains(&SyntaxKind::BogusFunctionArguments)
12087 );
12088 assert!(node_kinds(&missing_media_close.syntax()).contains(&SyntaxKind::BogusMediaQuery));
12089 assert!(node_kinds(&mixed_media_close.syntax()).contains(&SyntaxKind::MediaQuery));
12090 assert!(node_kinds(&mixed_media_close.syntax()).contains(&SyntaxKind::BogusMediaQuery));
12091 assert!(
12092 node_kinds(&missing_supports_close.syntax())
12093 .contains(&SyntaxKind::BogusSupportsCondition)
12094 );
12095 assert!(
12096 node_kinds(&missing_container_close.syntax())
12097 .contains(&SyntaxKind::BogusContainerCondition)
12098 );
12099 assert!(
12100 node_kinds(&missing_unknown_prelude_close.syntax())
12101 .contains(&SyntaxKind::BogusAtRulePrelude)
12102 );
12103 assert!(node_kinds(&missing_scope_close.syntax()).contains(&SyntaxKind::BogusScopeRange));
12104 assert!(node_kinds(&empty_layer_statement.syntax()).contains(&SyntaxKind::BogusLayerName));
12105 assert!(
12106 node_kinds(&missing_keyframe_block.syntax()).contains(&SyntaxKind::BogusKeyframeBlock)
12107 );
12108 assert!(node_kinds(&unclosed_rule.syntax()).contains(&SyntaxKind::BogusDeclarationList));
12109 assert!(node_kinds(&unclosed_rule.syntax()).contains(&SyntaxKind::BogusTrivia));
12110 }
12111
12112 #[test]
12113 fn populates_dialect_and_selector_bogus_nodes() {
12114 let invalid_compound = parse("%bad { color: red; }", StyleDialect::Css);
12115 let dangling_combinator = parse(".a > { color: red; }", StyleDialect::Css);
12116 let missing_property = parse(".a { : red; }", StyleDialect::Css);
12117 let missing_colon_recovery = parse("$gap 1rem;", StyleDialect::Scss);
12118 let unexpected_value_token = parse(".a { width: ?; }", StyleDialect::Css);
12119 let missing_at_rule_name = parse("@ ;", StyleDialect::Css);
12120 let missing_scss_variable_colon = parse("$gap;", StyleDialect::Scss);
12121 let missing_less_variable_colon = parse("@gap;", StyleDialect::Less);
12122 let missing_scss_blocks =
12123 parse("@mixin card; @function double; @if $x;", StyleDialect::Scss);
12124 let inconsistent_sass_indentation =
12125 parse(".card\n color: red\n color: blue\n", StyleDialect::Sass);
12126 let missing_less_mixin_block = parse(".theme(@tone);", StyleDialect::Less);
12127 let missing_less_guard_condition =
12128 parse(".theme() when { color: red; }", StyleDialect::Less);
12129
12130 assert!(
12131 node_kinds(&invalid_compound.syntax()).contains(&SyntaxKind::BogusCompoundSelector)
12132 );
12133 assert!(node_kinds(&dangling_combinator.syntax()).contains(&SyntaxKind::BogusCombinator));
12134 assert!(node_kinds(&missing_property.syntax()).contains(&SyntaxKind::BogusPropertyName));
12135 assert!(node_kinds(&missing_colon_recovery.syntax()).contains(&SyntaxKind::BogusRecovery));
12136 assert!(node_kinds(&unexpected_value_token.syntax()).contains(&SyntaxKind::BogusToken));
12137 assert!(node_kinds(&missing_at_rule_name.syntax()).contains(&SyntaxKind::BogusAtRule));
12138 assert!(
12139 node_kinds(&missing_scss_variable_colon.syntax())
12140 .contains(&SyntaxKind::BogusScssVariable)
12141 );
12142 assert!(
12143 node_kinds(&missing_less_variable_colon.syntax())
12144 .contains(&SyntaxKind::BogusLessVariable)
12145 );
12146 assert!(node_kinds(&missing_scss_blocks.syntax()).contains(&SyntaxKind::BogusScssMixin));
12147 assert!(node_kinds(&missing_scss_blocks.syntax()).contains(&SyntaxKind::BogusScssFunction));
12148 assert!(node_kinds(&missing_scss_blocks.syntax()).contains(&SyntaxKind::BogusScssControl));
12149 assert!(
12150 node_kinds(&inconsistent_sass_indentation.syntax())
12151 .contains(&SyntaxKind::BogusSassIndentation)
12152 );
12153 assert!(
12154 node_kinds(&missing_less_mixin_block.syntax()).contains(&SyntaxKind::BogusLessMixin)
12155 );
12156 assert!(
12157 node_kinds(&missing_less_guard_condition.syntax())
12158 .contains(&SyntaxKind::BogusLessGuard)
12159 );
12160 }
12161
12162 #[test]
12163 fn populates_every_declared_bogus_kind_in_recovery_corpus() {
12164 let mut actual = BTreeSet::new();
12165 let mut collect = |result: ParseResult| {
12166 actual.extend(
12167 node_kinds(&result.syntax())
12168 .into_iter()
12169 .filter(|kind| kind.is_bogus()),
12170 );
12171 };
12172
12173 collect(parse("{ color: red; }", StyleDialect::Css));
12174 collect(parse(". { color: red; }", StyleDialect::Css));
12175 collect(parse("%bad { color: red; }", StyleDialect::Css));
12176 collect(parse(".a > { color: red; }", StyleDialect::Css));
12177 collect(parse(".a { : red; width: ?; }", StyleDialect::Css));
12178 collect(parse(
12179 ".a { width: ; height: calc(1 + ; }",
12180 StyleDialect::Css,
12181 ));
12182 collect(parse(".a { color: [red; }", StyleDialect::Css));
12183 collect(parse(".a { font-family: system, ; }", StyleDialect::Css));
12184 collect(parse("@ ;", StyleDialect::Css));
12185 collect(parse(
12186 "@unknown (min-width: { color: red; }",
12187 StyleDialect::Css,
12188 ));
12189 collect(parse(
12190 "@media screen, (min-width: { .a { color: red; } }",
12191 StyleDialect::Css,
12192 ));
12193 collect(parse(
12194 "@supports (display: { .a { color: red; } }",
12195 StyleDialect::Css,
12196 ));
12197 collect(parse(
12198 "@container (inline-size > { .a { color: red; } }",
12199 StyleDialect::Css,
12200 ));
12201 collect(parse("@layer ;", StyleDialect::Css));
12202 collect(parse(
12203 "@scope (.a { .b { color: red; } }",
12204 StyleDialect::Css,
12205 ));
12206 collect(parse(
12207 "@keyframes fade { from opacity: 0; }",
12208 StyleDialect::Css,
12209 ));
12210 collect(parse(
12211 "@value from; .bad { composes: from; } .missing { composes base; }",
12212 StyleDialect::Scss,
12213 ));
12214 collect(parse(
12215 "@use \"theme\" with ($gap: 1rem; .card { color: red; }",
12216 StyleDialect::Scss,
12217 ));
12218 collect(parse(
12219 "@mixin card; @function double; @if $x;",
12220 StyleDialect::Scss,
12221 ));
12222 collect(parse("$gap;", StyleDialect::Scss));
12223 collect(parse(".a { content: \"unterminated\n }", StyleDialect::Css));
12224 collect(parse(".a { color: #{$tone; }", StyleDialect::Scss));
12225 collect(parse(
12226 ".card\n color: red\n color: blue\n",
12227 StyleDialect::Sass,
12228 ));
12229 collect(parse("@gap;", StyleDialect::Less));
12230 collect(parse(".theme(@tone);", StyleDialect::Less));
12231 collect(parse(".theme() when { color: red; }", StyleDialect::Less));
12232 collect(parse("@detached: { .a { color: red; }", StyleDialect::Less));
12233 collect(parse("$gap 1rem;", StyleDialect::Scss));
12234 collect(parse_entry_point(
12235 "[red",
12236 StyleDialect::Css,
12237 ParseEntryPoint::SimpleBlock,
12238 ));
12239 collect(parse_entry_point(
12240 "red, ;",
12241 StyleDialect::Css,
12242 ParseEntryPoint::CommaSeparatedComponentValueList,
12243 ));
12244
12245 let declared = SyntaxKind::ALL
12246 .iter()
12247 .copied()
12248 .filter(|kind| kind.is_bogus())
12249 .collect::<BTreeSet<_>>();
12250 let missing = declared.difference(&actual).copied().collect::<Vec<_>>();
12251
12252 assert!(missing.is_empty(), "missing bogus kinds: {missing:?}");
12253 }
12254
12255 #[test]
12256 fn parses_css_module_value_and_composes_cst_nodes() {
12257 let result = parse(
12258 "@value primary: #fff; @value accent: primary; @value secondary as localSecondary from \"./tokens.module.scss\"; .btn { composes: base utility from \"./base.module.scss\"; }",
12259 StyleDialect::Scss,
12260 );
12261 let kinds = node_kinds(&result.syntax());
12262
12263 assert!(result.errors().is_empty());
12264 assert!(kinds.contains(&SyntaxKind::CssModuleExportBlock));
12265 assert!(kinds.contains(&SyntaxKind::CssModuleImportBlock));
12266 assert!(kinds.contains(&SyntaxKind::TokenDefinition));
12267 assert!(kinds.contains(&SyntaxKind::TokenReference));
12268 assert!(kinds.contains(&SyntaxKind::CssModuleComposesDeclaration));
12269 assert!(kinds.contains(&SyntaxKind::CssModuleComposesTarget));
12270 assert!(kinds.contains(&SyntaxKind::CssModuleFromClause));
12271 }
12272
12273 #[test]
12274 fn extracts_css_module_value_style_facts() {
12275 let facts = collect_style_facts(
12276 "@value primary: #fff; @value accent: primary; @value secondary as localSecondary from \"./tokens.module.scss\"; .btn { color: accent; }",
12277 StyleDialect::Css,
12278 );
12279 let definitions = facts
12280 .css_module_values
12281 .iter()
12282 .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
12283 .map(|value| value.name.as_str())
12284 .collect::<Vec<_>>();
12285 let references = facts
12286 .css_module_values
12287 .iter()
12288 .filter(|value| value.kind == ParsedCssModuleValueFactKind::Reference)
12289 .map(|value| value.name.as_str())
12290 .collect::<Vec<_>>();
12291 let import_sources = facts
12292 .css_module_values
12293 .iter()
12294 .filter(|value| value.kind == ParsedCssModuleValueFactKind::ImportSource)
12295 .map(|value| value.name.as_str())
12296 .collect::<Vec<_>>();
12297
12298 assert_eq!(facts.css_module_value_count, 7);
12299 assert_eq!(definitions, vec!["primary", "accent", "localSecondary"]);
12300 assert_eq!(references, vec!["primary", "secondary", "accent"]);
12301 assert_eq!(import_sources, vec!["./tokens.module.scss"]);
12302 assert_eq!(facts.css_module_value_import_edge_count, 1);
12303 assert_eq!(
12304 facts.css_module_value_import_edges[0].remote_name,
12305 "secondary"
12306 );
12307 assert_eq!(
12308 facts.css_module_value_import_edges[0].local_name,
12309 "localSecondary"
12310 );
12311 assert_eq!(
12312 facts.css_module_value_import_edges[0].import_source,
12313 "./tokens.module.scss"
12314 );
12315 assert_eq!(facts.css_module_value_definition_edge_count, 1);
12316 assert_eq!(
12317 facts.css_module_value_definition_edges[0].definition_name,
12318 "accent"
12319 );
12320 assert_eq!(
12321 facts.css_module_value_definition_edges[0].reference_names,
12322 vec!["primary"]
12323 );
12324 }
12325
12326 #[test]
12327 fn extracts_css_module_value_path_alias_import_edges() {
12328 let facts = collect_style_facts(
12329 "@value colors: \"./colors.module.scss\"; @value primary, secondary as accent from colors; .btn { color: primary; border-color: accent; }",
12330 StyleDialect::Css,
12331 );
12332 let definitions = facts
12333 .css_module_values
12334 .iter()
12335 .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
12336 .map(|value| value.name.as_str())
12337 .collect::<Vec<_>>();
12338 let import_sources = facts
12339 .css_module_values
12340 .iter()
12341 .filter(|value| value.kind == ParsedCssModuleValueFactKind::ImportSource)
12342 .map(|value| value.name.as_str())
12343 .collect::<Vec<_>>();
12344
12345 assert_eq!(definitions, vec!["primary", "accent"]);
12346 assert_eq!(import_sources, vec!["./colors.module.scss"]);
12347 assert_eq!(facts.css_module_value_import_edge_count, 2);
12348 assert_eq!(
12349 facts.css_module_value_import_edges[0].remote_name,
12350 "primary"
12351 );
12352 assert_eq!(facts.css_module_value_import_edges[0].local_name, "primary");
12353 assert_eq!(
12354 facts.css_module_value_import_edges[0].import_source,
12355 "./colors.module.scss"
12356 );
12357 assert_eq!(
12358 facts.css_module_value_import_edges[1].remote_name,
12359 "secondary"
12360 );
12361 assert_eq!(facts.css_module_value_import_edges[1].local_name, "accent");
12362 assert_eq!(
12363 facts.css_module_value_import_edges[1].import_source,
12364 "./colors.module.scss"
12365 );
12366 }
12367
12368 #[test]
12369 fn extracts_css_module_composes_style_facts() {
12370 let facts = collect_style_facts(
12371 ".btn { composes: base utility from \"./base.module.scss\"; } .global { composes: reset from global; }",
12372 StyleDialect::Css,
12373 );
12374 let targets = facts
12375 .css_module_composes
12376 .iter()
12377 .filter(|composes| composes.kind == ParsedCssModuleComposesFactKind::Target)
12378 .map(|composes| composes.name.as_str())
12379 .collect::<Vec<_>>();
12380 let import_sources = facts
12381 .css_module_composes
12382 .iter()
12383 .filter(|composes| composes.kind == ParsedCssModuleComposesFactKind::ImportSource)
12384 .map(|composes| composes.name.as_str())
12385 .collect::<Vec<_>>();
12386
12387 assert_eq!(facts.css_module_composes_count, 5);
12388 assert_eq!(targets, vec!["base", "utility", "reset"]);
12389 assert_eq!(import_sources, vec!["./base.module.scss", "global"]);
12390 assert_eq!(facts.css_module_composes_edge_count, 2);
12391 assert_eq!(
12392 facts.css_module_composes_edges[0].kind,
12393 ParsedCssModuleComposesEdgeKind::External
12394 );
12395 assert_eq!(
12396 facts.css_module_composes_edges[0].owner_selector_names,
12397 vec!["btn"]
12398 );
12399 assert_eq!(
12400 facts.css_module_composes_edges[0].target_names,
12401 vec!["base", "utility"]
12402 );
12403 assert_eq!(
12404 facts.css_module_composes_edges[0].import_source.as_deref(),
12405 Some("./base.module.scss")
12406 );
12407 assert_eq!(
12408 facts.css_module_composes_edges[1].kind,
12409 ParsedCssModuleComposesEdgeKind::Global
12410 );
12411 assert_eq!(
12412 facts.css_module_composes_edges[1].owner_selector_names,
12413 vec!["global"]
12414 );
12415 assert_eq!(
12416 facts.css_module_composes_edges[1].target_names,
12417 vec!["reset"]
12418 );
12419 assert_eq!(
12420 facts.css_module_composes_edges[1].import_source.as_deref(),
12421 Some("global")
12422 );
12423 }
12424
12425 #[test]
12426 fn parses_icss_import_export_blocks() {
12427 let result = parse(
12428 ":export { primary: #fff; } :import(\"./tokens.css\") { imported: primary; } .btn { composes: imported; }",
12429 StyleDialect::Css,
12430 );
12431 let invalid = parse(":import { imported: primary; }", StyleDialect::Css);
12432 let kinds = node_kinds(&result.syntax());
12433
12434 assert!(result.errors().is_empty());
12435 assert!(kinds.contains(&SyntaxKind::CssModuleExportBlock));
12436 assert!(kinds.contains(&SyntaxKind::CssModuleImportBlock));
12437 assert!(
12438 invalid
12439 .errors()
12440 .iter()
12441 .any(|error| error.message == "expected ICSS import source")
12442 );
12443 }
12444
12445 #[test]
12446 fn extracts_icss_style_facts() {
12447 let facts = collect_style_facts(
12448 ":export { primary: #fff; secondary: accent; } :import(\"./tokens.css\") { imported: primary; tone: themeTone; }",
12449 StyleDialect::Css,
12450 );
12451 let export_names = facts
12452 .icss
12453 .iter()
12454 .filter(|icss| icss.kind == ParsedIcssFactKind::ExportName)
12455 .map(|icss| icss.name.as_str())
12456 .collect::<Vec<_>>();
12457 let import_local_names = facts
12458 .icss
12459 .iter()
12460 .filter(|icss| icss.kind == ParsedIcssFactKind::ImportLocalName)
12461 .map(|icss| icss.name.as_str())
12462 .collect::<Vec<_>>();
12463 let import_remote_names = facts
12464 .icss
12465 .iter()
12466 .filter(|icss| icss.kind == ParsedIcssFactKind::ImportRemoteName)
12467 .map(|icss| icss.name.as_str())
12468 .collect::<Vec<_>>();
12469 let import_sources = facts
12470 .icss
12471 .iter()
12472 .filter(|icss| icss.kind == ParsedIcssFactKind::ImportSource)
12473 .map(|icss| icss.name.as_str())
12474 .collect::<Vec<_>>();
12475
12476 assert_eq!(facts.icss_count, 7);
12477 assert_eq!(export_names, vec!["primary", "secondary"]);
12478 assert_eq!(import_local_names, vec!["imported", "tone"]);
12479 assert_eq!(import_remote_names, vec!["primary", "themeTone"]);
12480 assert_eq!(import_sources, vec!["./tokens.css"]);
12481 assert_eq!(facts.icss_import_edge_count, 2);
12482 assert_eq!(facts.icss_import_edges[0].local_name, "imported");
12483 assert_eq!(facts.icss_import_edges[0].remote_name, "primary");
12484 assert_eq!(facts.icss_import_edges[0].import_source, "./tokens.css");
12485 assert_eq!(facts.icss_import_edges[1].local_name, "tone");
12486 assert_eq!(facts.icss_import_edges[1].remote_name, "themeTone");
12487 assert_eq!(facts.icss_import_edges[1].import_source, "./tokens.css");
12488 assert_eq!(facts.icss_export_edge_count, 1);
12489 assert_eq!(facts.icss_export_edges[0].export_name, "secondary");
12490 assert_eq!(facts.icss_export_edges[0].reference_names, vec!["accent"]);
12491 }
12492
12493 #[test]
12494 fn recovers_css_module_value_and_composes_bogus_nodes() {
12495 let result = parse(
12496 "@value from; .bad { composes: from; } .missing { composes base; } .invalid { composes: base from 123; } @value bad as alias from 123; .multi { composes: a from \"./a.css\", b from \"./b.css\"; }",
12497 StyleDialect::Scss,
12498 );
12499 let kinds = node_kinds(&result.syntax());
12500 let invalid_from_source_count = result
12501 .errors()
12502 .iter()
12503 .filter(|error| error.message == "invalid CSS Modules from-clause source")
12504 .count();
12505 let multiple_from_count = result
12506 .errors()
12507 .iter()
12508 .filter(|error| error.message == "multiple composes from clauses are not allowed")
12509 .count();
12510
12511 assert!(kinds.contains(&SyntaxKind::BogusCssModuleBlock));
12512 assert!(kinds.contains(&SyntaxKind::BogusFromClause));
12513 assert!(kinds.contains(&SyntaxKind::BogusComposesTarget));
12514 assert!(kinds.contains(&SyntaxKind::BogusComposesDeclaration));
12515 assert_eq!(invalid_from_source_count, 2);
12516 assert_eq!(multiple_from_count, 1);
12517 }
12518
12519 #[test]
12520 fn validates_composes_outside_css_module_global_scope() {
12521 let invalid = parse(
12522 ":global(.reset) { composes: base; } :global { .utility { composes: base; } } :local(.ok) { composes: base; }",
12523 StyleDialect::Css,
12524 );
12525 let outer_local = parse(
12526 ":local { :global(.ok) { composes: base; } }",
12527 StyleDialect::Css,
12528 );
12529 let mixed_local_global = parse(".foo :global(.bar) { composes: base; }", StyleDialect::Css);
12530 let global_composes_count = invalid
12531 .errors()
12532 .iter()
12533 .filter(|error| error.message == "composes is not allowed inside :global scope")
12534 .count();
12535
12536 assert_eq!(global_composes_count, 2);
12537 assert!(
12538 !outer_local
12539 .errors()
12540 .iter()
12541 .any(|error| error.message == "composes is not allowed inside :global scope")
12542 );
12543 assert!(
12544 !mixed_local_global
12545 .errors()
12546 .iter()
12547 .any(|error| error.message == "composes is not allowed inside :global scope")
12548 );
12549 }
12550
12551 #[test]
12552 fn parses_registered_group_at_rule_blocks() {
12553 let result = parse(
12554 "@media screen and (min-width: 40rem) { .card { color: red; } }",
12555 StyleDialect::Css,
12556 );
12557 let kinds = node_kinds(&result.syntax());
12558
12559 assert!(result.errors().is_empty());
12560 assert!(kinds.contains(&SyntaxKind::AtRule));
12561 assert!(kinds.contains(&SyntaxKind::MediaRule));
12562 assert!(kinds.contains(&SyntaxKind::RuleList));
12563 assert!(kinds.contains(&SyntaxKind::Rule));
12564 assert!(kinds.contains(&SyntaxKind::ClassSelector));
12565 }
12566
12567 #[test]
12568 fn parses_conditional_at_rule_preludes() {
12569 let result = parse(
12570 "@media screen and (min-width: 40rem), print { .card { color: red; } } @supports (display: grid) { .grid { display: grid; } } @container card (inline-size > 40rem) { .item { color: blue; } }",
12571 StyleDialect::Css,
12572 );
12573 let kinds = node_kinds(&result.syntax());
12574
12575 assert!(result.errors().is_empty());
12576 assert!(kinds.contains(&SyntaxKind::MediaQueryList));
12577 assert_eq!(
12578 kinds
12579 .iter()
12580 .filter(|kind| **kind == SyntaxKind::MediaQuery)
12581 .count(),
12582 2
12583 );
12584 assert!(kinds.contains(&SyntaxKind::MediaFeature));
12585 assert!(kinds.contains(&SyntaxKind::SupportsCondition));
12586 assert!(kinds.contains(&SyntaxKind::ContainerCondition));
12587 }
12588
12589 #[test]
12590 fn validates_media_query_list_preludes() {
12591 let result = parse(
12592 "@media { .a { color: red; } } @media , screen { .b { color: blue; } } @media screen, { .c { color: green; } } @media 1 { .d { color: black; } } @media screen and (min-width: 40rem), print { .e { color: white; } }",
12593 StyleDialect::Css,
12594 );
12595 let kinds = node_kinds(&result.syntax());
12596 let invalid_media_errors = result
12597 .errors()
12598 .iter()
12599 .filter(|error| error.message == "invalid @media prelude")
12600 .count();
12601 let bogus_media_queries = kinds
12602 .iter()
12603 .filter(|kind| **kind == SyntaxKind::BogusMediaQuery)
12604 .count();
12605
12606 assert_eq!(invalid_media_errors, 4);
12607 assert_eq!(bogus_media_queries, 4);
12608 assert!(kinds.contains(&SyntaxKind::MediaQuery));
12609 }
12610
12611 #[test]
12612 fn validates_supports_rule_preludes() {
12613 let result = parse(
12614 "@supports { .a { color: red; } } @supports display: grid { .b { color: blue; } } @supports not { .c { color: green; } } @supports (display: grid) { .d { color: black; } } @supports selector(:has(*)) { .e { color: white; } }",
12615 StyleDialect::Css,
12616 );
12617 let kinds = node_kinds(&result.syntax());
12618 let invalid_supports_errors = result
12619 .errors()
12620 .iter()
12621 .filter(|error| error.message == "invalid @supports prelude")
12622 .count();
12623 let bogus_supports_conditions = kinds
12624 .iter()
12625 .filter(|kind| **kind == SyntaxKind::BogusSupportsCondition)
12626 .count();
12627
12628 assert_eq!(invalid_supports_errors, 3);
12629 assert_eq!(bogus_supports_conditions, 3);
12630 assert!(kinds.contains(&SyntaxKind::SupportsCondition));
12631 }
12632
12633 #[test]
12634 fn validates_container_rule_preludes() {
12635 let result = parse(
12636 "@container { .a { color: red; } } @container card { .b { color: blue; } } @container 1 (width > 0) { .c { color: green; } } @container style(--theme: dark) { .d { color: white; } } @container card style(--theme: dark) { .e { color: black; } }",
12637 StyleDialect::Css,
12638 );
12639 let kinds = node_kinds(&result.syntax());
12640 let invalid_container_errors = result
12641 .errors()
12642 .iter()
12643 .filter(|error| error.message == "invalid @container prelude")
12644 .count();
12645 let bogus_container_conditions = kinds
12646 .iter()
12647 .filter(|kind| **kind == SyntaxKind::BogusContainerCondition)
12648 .count();
12649
12650 assert_eq!(invalid_container_errors, 3);
12651 assert_eq!(bogus_container_conditions, 3);
12652 assert!(kinds.contains(&SyntaxKind::ContainerCondition));
12653 }
12654
12655 #[test]
12656 fn classifies_css_at_rules_case_insensitively() {
12657 let source = "@MEDIA (width >= 1px) { .card { color: red; } } @KEYFRAMES fade { from { opacity: 0; } to { opacity: 1; } }";
12658 let result = parse(source, StyleDialect::Css);
12659 let facts = collect_style_facts(source, StyleDialect::Css);
12660 let kinds = node_kinds(&result.syntax());
12661 let at_rule_names: Vec<&str> = facts
12662 .at_rules
12663 .iter()
12664 .map(|at_rule| at_rule.name.as_str())
12665 .collect();
12666
12667 assert!(result.errors().is_empty());
12668 assert!(kinds.contains(&SyntaxKind::MediaRule));
12669 assert!(kinds.contains(&SyntaxKind::KeyframesRule));
12670 assert!(
12671 facts
12672 .selectors
12673 .iter()
12674 .any(|selector| selector.name == "card")
12675 );
12676 assert_eq!(at_rule_names, vec!["@media", "@keyframes"]);
12677 }
12678
12679 #[test]
12680 fn parses_import_layer_supports_media_prelude() {
12681 let result = parse(
12682 "@import url(\"theme.css\") layer(app.theme) supports(display: grid) screen and (min-width: 40rem);",
12683 StyleDialect::Css,
12684 );
12685 let less = parse(
12686 "@import (reference) \"theme.less\" screen and (min-width: 40rem);",
12687 StyleDialect::Less,
12688 );
12689 let kinds = node_kinds(&result.syntax());
12690 let less_kinds = node_kinds(&less.syntax());
12691
12692 assert!(result.errors().is_empty());
12693 assert!(less.errors().is_empty());
12694 assert!(kinds.contains(&SyntaxKind::ImportRule));
12695 assert!(kinds.contains(&SyntaxKind::UrlValue));
12696 assert!(kinds.contains(&SyntaxKind::LayerName));
12697 assert!(kinds.contains(&SyntaxKind::SupportsCondition));
12698 assert!(kinds.contains(&SyntaxKind::MediaQueryList));
12699 assert!(kinds.contains(&SyntaxKind::MediaFeature));
12700 assert!(less_kinds.contains(&SyntaxKind::ImportRule));
12701 assert!(less_kinds.contains(&SyntaxKind::AtRulePrelude));
12702 assert!(less_kinds.contains(&SyntaxKind::MediaQueryList));
12703 }
12704
12705 #[test]
12706 fn validates_import_sources() {
12707 let result = parse(
12708 "@import ; @import layer(app); @import 1; @import url(foo bar);",
12709 StyleDialect::Css,
12710 );
12711 let kinds = node_kinds(&result.syntax());
12712 let invalid_import_errors = result
12713 .errors()
12714 .iter()
12715 .filter(|error| error.message == "invalid @import source")
12716 .count();
12717 let bogus_preludes = kinds
12718 .iter()
12719 .filter(|kind| **kind == SyntaxKind::BogusAtRulePrelude)
12720 .count();
12721
12722 assert_eq!(invalid_import_errors, 4);
12723 assert_eq!(bogus_preludes, 4);
12724 }
12725
12726 #[test]
12727 fn validates_import_optional_tails() {
12728 let result = parse(
12729 "@import \"a.css\" layer(); @import \"b.css\" layer(1); @import \"c.css\" supports(); @import \"d.css\" supports screen; @import \"ok.css\" layer(app.theme) supports(display: grid) screen;",
12730 StyleDialect::Css,
12731 );
12732 let kinds = node_kinds(&result.syntax());
12733 let invalid_layer_tail_errors = result
12734 .errors()
12735 .iter()
12736 .filter(|error| error.message == "invalid @import layer tail")
12737 .count();
12738 let invalid_supports_tail_errors = result
12739 .errors()
12740 .iter()
12741 .filter(|error| error.message == "invalid @import supports tail")
12742 .count();
12743 let bogus_layer_names = kinds
12744 .iter()
12745 .filter(|kind| **kind == SyntaxKind::BogusLayerName)
12746 .count();
12747 let bogus_supports_conditions = kinds
12748 .iter()
12749 .filter(|kind| **kind == SyntaxKind::BogusSupportsCondition)
12750 .count();
12751
12752 assert_eq!(invalid_layer_tail_errors, 2);
12753 assert_eq!(invalid_supports_tail_errors, 2);
12754 assert_eq!(bogus_layer_names, 2);
12755 assert_eq!(bogus_supports_conditions, 2);
12756 assert!(kinds.contains(&SyntaxKind::LayerName));
12757 assert!(kinds.contains(&SyntaxKind::SupportsCondition));
12758 assert!(kinds.contains(&SyntaxKind::MediaQueryList));
12759 }
12760
12761 #[test]
12762 fn parses_layer_and_scope_preludes() {
12763 let result = parse(
12764 "@layer reset, app.ui; @layer components { .card { color: red; } } @layer { .anon { color: blue; } } @scope (.card) to (.card-content) { .title { color: red; } }",
12765 StyleDialect::Css,
12766 );
12767 let kinds = node_kinds(&result.syntax());
12768
12769 assert!(result.errors().is_empty());
12770 assert!(kinds.contains(&SyntaxKind::LayerRule));
12771 assert!(kinds.contains(&SyntaxKind::LayerName));
12772 assert!(kinds.contains(&SyntaxKind::ScopeRule));
12773 assert!(kinds.contains(&SyntaxKind::ScopeRange));
12774 assert!(kinds.contains(&SyntaxKind::RuleList));
12775 }
12776
12777 #[test]
12778 fn validates_layer_rule_preludes() {
12779 let result = parse(
12780 "@layer , reset; @layer app.; @layer 1; @layer ok.name;",
12781 StyleDialect::Css,
12782 );
12783 let kinds = node_kinds(&result.syntax());
12784 let invalid_layer_errors = result
12785 .errors()
12786 .iter()
12787 .filter(|error| error.message == "invalid @layer prelude")
12788 .count();
12789 let bogus_layer_names = kinds
12790 .iter()
12791 .filter(|kind| **kind == SyntaxKind::BogusLayerName)
12792 .count();
12793
12794 assert_eq!(invalid_layer_errors, 3);
12795 assert_eq!(bogus_layer_names, 3);
12796 assert!(kinds.contains(&SyntaxKind::LayerName));
12797 }
12798
12799 #[test]
12800 fn validates_scope_rule_preludes() {
12801 let result = parse(
12802 "@scope { .a { color: red; } } @scope .a { .b { color: blue; } } @scope (.a) to { .c { color: green; } } @scope (.a) to (.b) { .d { color: black; } }",
12803 StyleDialect::Css,
12804 );
12805 let kinds = node_kinds(&result.syntax());
12806 let invalid_scope_errors = result
12807 .errors()
12808 .iter()
12809 .filter(|error| error.message == "invalid @scope prelude")
12810 .count();
12811 let bogus_scope_ranges = kinds
12812 .iter()
12813 .filter(|kind| **kind == SyntaxKind::BogusScopeRange)
12814 .count();
12815
12816 assert_eq!(invalid_scope_errors, 3);
12817 assert_eq!(bogus_scope_ranges, 3);
12818 assert!(kinds.contains(&SyntaxKind::ScopeRange));
12819 }
12820
12821 #[test]
12822 fn validates_page_rule_preludes() {
12823 let result = parse(
12824 "@page { margin: 1cm; } @page :first { margin: 2cm; } @page chapter:left, appendix:right { margin: 3cm; } @page 1 { margin: 4cm; } @page chapter, { margin: 5cm; } @page chapter first { margin: 6cm; }",
12825 StyleDialect::Css,
12826 );
12827 let kinds = node_kinds(&result.syntax());
12828 let invalid_page_errors = result
12829 .errors()
12830 .iter()
12831 .filter(|error| error.message == "invalid @page prelude")
12832 .count();
12833 let bogus_preludes = kinds
12834 .iter()
12835 .filter(|kind| **kind == SyntaxKind::BogusAtRulePrelude)
12836 .count();
12837
12838 assert_eq!(invalid_page_errors, 3);
12839 assert_eq!(bogus_preludes, 3);
12840 assert!(kinds.contains(&SyntaxKind::PageRule));
12841 assert!(kinds.contains(&SyntaxKind::AtRulePrelude));
12842 }
12843
12844 #[test]
12845 fn parses_registered_keyframes_and_declaration_at_rules() {
12846 let keyframes = parse(
12847 "@keyframes fade { from { opacity: 0; } to { opacity: 1; } }",
12848 StyleDialect::Css,
12849 );
12850 let font_face = parse(
12851 "@font-face { font-family: \"Demo\"; src: url(demo.woff2); }",
12852 StyleDialect::Css,
12853 );
12854 let page_margin = parse(
12855 "@page :first { margin: 1cm; @top-left { content: \"A\"; } @bottom-center { content: counter(page); } }",
12856 StyleDialect::Css,
12857 );
12858 let conditional_l5 = parse(
12859 "@when media(width >= 1px) { .a { color: red; } } @else { .b { color: blue; } }",
12860 StyleDialect::Css,
12861 );
12862 let modern_declaration_rules = parse(
12863 "@counter-style thumbs { system: cyclic; symbols: \"yes\"; suffix: \" \"; } @font-palette-values --brand { font-family: Demo; base-palette: 1; } @color-profile --display-p3 { src: url(p3.icc); } @position-try --popover { inset-area: top; }",
12864 StyleDialect::Css,
12865 );
12866 let font_feature_values = parse(
12867 "@font-feature-values Demo { @stylistic { nice: 1; } @styleset { alt: 2; } @character-variant { nice: 3 4; } @swash { fancy: 1; } @ornaments { leaf: 1; } @annotation { circled: 1; } @historical-forms { old: 1; } } @view-transition { navigation: auto; }",
12868 StyleDialect::Css,
12869 );
12870 let less_css_at_rules = parse(
12871 "@font-feature-values Demo { @styleset { alt: 2; } } @view-transition { navigation: auto; }",
12872 StyleDialect::Less,
12873 );
12874 let nesting_and_custom_media = parse(
12875 ".card { @nest &__icon { color: red; &--active { color: blue; } } } @custom-media --narrow (width < 40rem);",
12876 StyleDialect::Css,
12877 );
12878 let keyframe_kinds = node_kinds(&keyframes.syntax());
12879 let font_face_kinds = node_kinds(&font_face.syntax());
12880 let page_margin_kinds = node_kinds(&page_margin.syntax());
12881 let conditional_l5_kinds = node_kinds(&conditional_l5.syntax());
12882 let modern_declaration_kinds = node_kinds(&modern_declaration_rules.syntax());
12883 let font_feature_value_kinds = node_kinds(&font_feature_values.syntax());
12884 let less_css_at_rule_kinds = node_kinds(&less_css_at_rules.syntax());
12885 let nesting_and_custom_media_kinds = node_kinds(&nesting_and_custom_media.syntax());
12886
12887 assert!(keyframes.errors().is_empty());
12888 assert!(font_face.errors().is_empty());
12889 assert!(page_margin.errors().is_empty());
12890 assert!(conditional_l5.errors().is_empty());
12891 assert!(modern_declaration_rules.errors().is_empty());
12892 assert!(font_feature_values.errors().is_empty());
12893 assert!(less_css_at_rules.errors().is_empty());
12894 assert!(nesting_and_custom_media.errors().is_empty());
12895 assert!(keyframe_kinds.contains(&SyntaxKind::KeyframesRule));
12896 assert!(keyframe_kinds.contains(&SyntaxKind::AtRulePrelude));
12897 assert!(keyframe_kinds.contains(&SyntaxKind::KeyframeBlock));
12898 assert!(font_face_kinds.contains(&SyntaxKind::FontFaceRule));
12899 assert!(font_face_kinds.contains(&SyntaxKind::DeclarationList));
12900 assert!(page_margin_kinds.contains(&SyntaxKind::PageRule));
12901 assert!(page_margin_kinds.contains(&SyntaxKind::PageMarginRule));
12902 assert!(conditional_l5_kinds.contains(&SyntaxKind::WhenRule));
12903 assert!(conditional_l5_kinds.contains(&SyntaxKind::ElseRule));
12904 assert!(conditional_l5_kinds.contains(&SyntaxKind::RuleList));
12905 assert!(modern_declaration_kinds.contains(&SyntaxKind::CounterStyleRule));
12906 assert!(modern_declaration_kinds.contains(&SyntaxKind::FontPaletteValuesRule));
12907 assert!(modern_declaration_kinds.contains(&SyntaxKind::ColorProfileRule));
12908 assert!(modern_declaration_kinds.contains(&SyntaxKind::PositionTryRule));
12909 assert!(modern_declaration_kinds.contains(&SyntaxKind::DeclarationList));
12910 assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesRule));
12911 assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesStylisticRule));
12912 assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesStylesetRule));
12913 assert!(
12914 font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesCharacterVariantRule)
12915 );
12916 assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesSwashRule));
12917 assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesOrnamentsRule));
12918 assert!(font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesAnnotationRule));
12919 assert!(
12920 font_feature_value_kinds.contains(&SyntaxKind::FontFeatureValuesHistoricalFormsRule)
12921 );
12922 assert!(font_feature_value_kinds.contains(&SyntaxKind::ViewTransitionRule));
12923 assert!(less_css_at_rule_kinds.contains(&SyntaxKind::FontFeatureValuesRule));
12924 assert!(less_css_at_rule_kinds.contains(&SyntaxKind::FontFeatureValuesStylesetRule));
12925 assert!(less_css_at_rule_kinds.contains(&SyntaxKind::ViewTransitionRule));
12926 assert!(nesting_and_custom_media_kinds.contains(&SyntaxKind::NestRule));
12927 assert!(nesting_and_custom_media_kinds.contains(&SyntaxKind::CustomMediaRule));
12928 assert!(nesting_and_custom_media_kinds.contains(&SyntaxKind::DeclarationList));
12929 }
12930
12931 #[test]
12932 fn validates_property_at_rule_names() {
12933 let valid = parse(
12934 "@property --accent { syntax: \"<color>\"; inherits: false; initial-value: red; }",
12935 StyleDialect::Css,
12936 );
12937 let dynamic = parse(
12938 "@property #{$name} { syntax: \"<color>\"; inherits: false; initial-value: red; }",
12939 StyleDialect::Scss,
12940 );
12941 let invalid = parse(
12942 "@property accent { syntax: \"<color>\"; inherits: false; initial-value: red; }",
12943 StyleDialect::Css,
12944 );
12945 let invalid_property_name_count = invalid
12946 .errors()
12947 .iter()
12948 .filter(|error| error.message == "invalid @property name")
12949 .count();
12950
12951 assert!(valid.errors().is_empty());
12952 assert!(dynamic.errors().is_empty());
12953 assert_eq!(invalid_property_name_count, 1);
12954 }
12955
12956 #[test]
12957 fn validates_named_declaration_at_rule_preludes() {
12958 let valid = parse(
12959 "@counter-style thumbs { system: cyclic; symbols: \"yes\"; } @font-palette-values --brand { font-family: Demo; } @color-profile --display-p3 { src: url(p3.icc); } @position-try --popover { inset-area: top; } @custom-media --narrow (width < 40rem);",
12960 StyleDialect::Css,
12961 );
12962 let dynamic = parse(
12963 "@counter-style #{$style} { system: cyclic; symbols: \"yes\"; } @font-palette-values #{$palette} { font-family: Demo; } @custom-media #{$query} (width < 40rem);",
12964 StyleDialect::Scss,
12965 );
12966 let invalid = parse(
12967 "@counter-style --bad { system: cyclic; } @font-palette-values brand { font-family: Demo; } @color-profile display-p3 { src: url(p3.icc); } @position-try popover { inset-area: top; } @custom-media narrow (width < 40rem); @custom-media --missing;",
12968 StyleDialect::Css,
12969 );
12970 let custom_property_name_errors = invalid
12971 .errors()
12972 .iter()
12973 .filter(|error| error.message == "invalid at-rule custom property name")
12974 .count();
12975 let custom_media_prelude_errors = invalid
12976 .errors()
12977 .iter()
12978 .filter(|error| error.message == "invalid @custom-media prelude")
12979 .count();
12980 let counter_style_name_errors = invalid
12981 .errors()
12982 .iter()
12983 .filter(|error| error.message == "invalid @counter-style name")
12984 .count();
12985
12986 assert!(valid.errors().is_empty());
12987 assert!(dynamic.errors().is_empty());
12988 assert_eq!(custom_property_name_errors, 3);
12989 assert_eq!(custom_media_prelude_errors, 2);
12990 assert_eq!(counter_style_name_errors, 1);
12991 }
12992
12993 #[test]
12994 fn validates_charset_and_namespace_at_rule_preludes() {
12995 let valid = parse(
12996 "@charset \"UTF-8\"; @namespace \"http://www.w3.org/1999/xhtml\"; @namespace svg url(\"http://www.w3.org/2000/svg\"); @namespace math url(http://www.w3.org/1998/Math/MathML);",
12997 StyleDialect::Css,
12998 );
12999 let dynamic = parse(
13000 "@namespace #{$url}; @namespace svg #{$url};",
13001 StyleDialect::Scss,
13002 );
13003 let invalid = parse("@charset UTF-8; @namespace svg;", StyleDialect::Css);
13004 let charset_errors = invalid
13005 .errors()
13006 .iter()
13007 .filter(|error| error.message == "invalid @charset prelude")
13008 .count();
13009 let namespace_errors = invalid
13010 .errors()
13011 .iter()
13012 .filter(|error| error.message == "invalid @namespace prelude")
13013 .count();
13014
13015 assert!(valid.errors().is_empty());
13016 assert!(dynamic.errors().is_empty());
13017 assert_eq!(charset_errors, 1);
13018 assert_eq!(namespace_errors, 1);
13019 }
13020
13021 #[test]
13022 fn validates_keyframes_at_rule_names() {
13023 let valid = parse(
13024 "@keyframes fade { from { opacity: 0; } } @keyframes \"slide\" { to { opacity: 1; } }",
13025 StyleDialect::Css,
13026 );
13027 let dynamic = parse(
13028 "@keyframes #{$animation-name} { from { opacity: 0; } }",
13029 StyleDialect::Scss,
13030 );
13031 let invalid = parse(
13032 "@keyframes 50% { from { opacity: 0; } } @keyframes fade extra { to { opacity: 1; } }",
13033 StyleDialect::Css,
13034 );
13035 let invalid_name_errors = invalid
13036 .errors()
13037 .iter()
13038 .filter(|error| error.message == "invalid @keyframes name")
13039 .count();
13040
13041 assert!(valid.errors().is_empty());
13042 assert!(dynamic.errors().is_empty());
13043 assert_eq!(invalid_name_errors, 2);
13044 }
13045
13046 #[test]
13047 fn validates_keyframe_selector_lists() {
13048 let valid = parse(
13049 "@keyframes fade { from { opacity: 0; } 50%, 75% { opacity: .5; } to { opacity: 1; } }",
13050 StyleDialect::Css,
13051 );
13052 let dynamic = parse(
13053 "@keyframes fade { #{$step} { opacity: .5; } }",
13054 StyleDialect::Scss,
13055 );
13056 let invalid = parse(
13057 "@keyframes fade { middle { opacity: .5; } 120px { opacity: 1; } 50%, { opacity: .8; } }",
13058 StyleDialect::Css,
13059 );
13060 let invalid_selector_errors = invalid
13061 .errors()
13062 .iter()
13063 .filter(|error| error.message == "invalid keyframe selector")
13064 .count();
13065
13066 assert!(valid.errors().is_empty());
13067 assert!(dynamic.errors().is_empty());
13068 assert_eq!(invalid_selector_errors, 3);
13069 }
13070
13071 #[test]
13072 fn validates_empty_block_at_rule_preludes() {
13073 let valid = parse(
13074 "@font-face { font-family: Demo; } @starting-style { .card { opacity: 0; } } @view-transition { navigation: auto; } @page { @top-left { content: \"A\"; } } @font-feature-values Demo { @styleset { alt: 2; } }",
13075 StyleDialect::Css,
13076 );
13077 let invalid = parse(
13078 "@font-face Demo { font-family: Demo; } @starting-style demo { .card { opacity: 0; } } @view-transition demo { navigation: auto; } @page { @top-left header { content: \"A\"; } } @font-feature-values Demo { @styleset alt { alt: 2; } }",
13079 StyleDialect::Css,
13080 );
13081 let unexpected_prelude_errors = invalid
13082 .errors()
13083 .iter()
13084 .filter(|error| error.message == "unexpected at-rule prelude")
13085 .count();
13086
13087 assert!(valid.errors().is_empty());
13088 assert_eq!(unexpected_prelude_errors, 5);
13089 }
13090
13091 #[test]
13092 fn validates_font_feature_values_preludes() {
13093 let valid = parse(
13094 "@font-feature-values Demo, \"Brand Font\" { @styleset { alt: 2; } }",
13095 StyleDialect::Css,
13096 );
13097 let dynamic = parse(
13098 "@font-feature-values #{$family} { @styleset { alt: 2; } }",
13099 StyleDialect::Scss,
13100 );
13101 let invalid = parse(
13102 "@font-feature-values { @styleset { alt: 2; } } @font-feature-values 123 { @styleset { alt: 2; } }",
13103 StyleDialect::Css,
13104 );
13105 let invalid_family_name_errors = invalid
13106 .errors()
13107 .iter()
13108 .filter(|error| error.message == "invalid @font-feature-values family name")
13109 .count();
13110
13111 assert!(valid.errors().is_empty());
13112 assert!(dynamic.errors().is_empty());
13113 assert_eq!(invalid_family_name_errors, 2);
13114 }
13115
13116 #[test]
13117 fn classifies_initial_scss_at_rule_nodes() {
13118 let module_rules = parse(
13119 "@use \"sass:map\"; @forward \"tokens\";",
13120 StyleDialect::Scss,
13121 );
13122 let mixin_rule = parse("@mixin card($gap) { padding: $gap; }", StyleDialect::Scss);
13123 let module_kinds = node_kinds(&module_rules.syntax());
13124 let mixin_kinds = node_kinds(&mixin_rule.syntax());
13125
13126 assert!(module_rules.errors().is_empty());
13127 assert!(mixin_rule.errors().is_empty());
13128 assert!(module_kinds.contains(&SyntaxKind::ScssUseRule));
13129 assert!(module_kinds.contains(&SyntaxKind::ScssForwardRule));
13130 assert!(mixin_kinds.contains(&SyntaxKind::ScssMixinDeclaration));
13131 }
13132
13133 #[test]
13134 fn parses_scss_module_config_preludes() {
13135 let result = parse(
13136 "@use \"theme\" as * with ($gap: 1rem, $enabled: true); @forward \"tokens\" as token-* show $color, mixin with ($color: red);",
13137 StyleDialect::Scss,
13138 );
13139 let kinds = node_kinds(&result.syntax());
13140 let config_count = kinds
13141 .iter()
13142 .filter(|kind| **kind == SyntaxKind::ScssModuleConfig)
13143 .count();
13144
13145 assert!(result.errors().is_empty());
13146 assert!(kinds.contains(&SyntaxKind::ScssUseRule));
13147 assert!(kinds.contains(&SyntaxKind::ScssForwardRule));
13148 assert_eq!(config_count, 2);
13149 }
13150
13151 #[test]
13152 fn validates_scss_module_prelude_clauses() {
13153 let invalid = parse(
13154 "@use as *; @use \"theme\" as ; @use \"theme\" show foo; @forward \"tokens\" hide ; @forward \"tokens\" with $gap;",
13155 StyleDialect::Scss,
13156 );
13157
13158 assert_eq!(
13159 invalid
13160 .errors()
13161 .iter()
13162 .filter(|error| error.message == "expected SCSS module source")
13163 .count(),
13164 1
13165 );
13166 assert_eq!(
13167 invalid
13168 .errors()
13169 .iter()
13170 .filter(|error| error.message == "expected SCSS module namespace")
13171 .count(),
13172 1
13173 );
13174 assert_eq!(
13175 invalid
13176 .errors()
13177 .iter()
13178 .filter(|error| error.message == "unexpected SCSS module visibility clause")
13179 .count(),
13180 1
13181 );
13182 assert_eq!(
13183 invalid
13184 .errors()
13185 .iter()
13186 .filter(|error| error.message == "expected SCSS module visibility name")
13187 .count(),
13188 1
13189 );
13190 assert_eq!(
13191 invalid
13192 .errors()
13193 .iter()
13194 .filter(|error| error.message == "expected SCSS module configuration")
13195 .count(),
13196 1
13197 );
13198 }
13199
13200 #[test]
13201 fn recovers_unclosed_scss_module_config_as_bogus() {
13202 let result = parse(
13203 "@use \"theme\" with ($gap: 1rem; .card { color: red; }",
13204 StyleDialect::Scss,
13205 );
13206 let kinds = node_kinds(&result.syntax());
13207
13208 assert!(
13209 result
13210 .errors()
13211 .iter()
13212 .any(|error| error.message == "unterminated parenthesized prelude")
13213 );
13214 assert!(kinds.contains(&SyntaxKind::BogusScssModuleConfig));
13215 assert!(!kinds.contains(&SyntaxKind::ScssModuleConfig));
13216 }
13217
13218 #[test]
13219 fn parses_scss_placeholder_selectors_and_extend_refs() {
13220 let result = parse(
13221 "%button { color: red; } .primary { @extend %button; }",
13222 StyleDialect::Scss,
13223 );
13224 let kinds = node_kinds(&result.syntax());
13225
13226 assert!(result.errors().is_empty());
13227 assert!(kinds.contains(&SyntaxKind::ScssPlaceholderSelector));
13228 assert!(kinds.contains(&SyntaxKind::ScssExtendRule));
13229 assert!(token_kinds(&result.syntax()).contains(&SyntaxKind::ScssPlaceholder));
13230 }
13231
13232 #[test]
13233 fn parses_structured_scss_at_rule_bodies() {
13234 let result = parse(
13235 "@mixin card($gap) { .item { gap: $gap; } } @function double($x) { @return $x * 2; } @if $enabled { .on { color: green; } } @for $i from 1 through 3 { .n { order: $i; } } @each $k, $v in $map { .e { color: $v; } } @while $enabled { .w { color: red; } }",
13236 StyleDialect::Scss,
13237 );
13238 let kinds = node_kinds(&result.syntax());
13239
13240 assert!(result.errors().is_empty());
13241 assert!(kinds.contains(&SyntaxKind::ScssMixinDeclaration));
13242 assert!(kinds.contains(&SyntaxKind::ScssFunctionDeclaration));
13243 assert!(kinds.contains(&SyntaxKind::ScssReturnRule));
13244 assert!(kinds.contains(&SyntaxKind::ScssControlIf));
13245 assert!(kinds.contains(&SyntaxKind::ScssControlFor));
13246 assert!(kinds.contains(&SyntaxKind::ScssControlEach));
13247 assert!(kinds.contains(&SyntaxKind::ScssControlWhile));
13248 assert!(kinds.contains(&SyntaxKind::DeclarationList));
13249 assert!(kinds.contains(&SyntaxKind::Rule));
13250 assert!(kinds.contains(&SyntaxKind::ClassSelector));
13251 assert!(kinds.contains(&SyntaxKind::ScssVariableReference));
13252 }
13253
13254 #[test]
13255 fn validates_scss_control_preludes() {
13256 let invalid = parse(
13257 "@if { .a { color: red; } } @while { .b { color: red; } } @for i from 1 through 3 { .c { color: red; } } @for $i from 1 { .d { color: red; } } @each item of $items { .e { color: red; } }",
13258 StyleDialect::Scss,
13259 );
13260 let invalid_control_prelude_count = invalid
13261 .errors()
13262 .iter()
13263 .filter(|error| error.message == "invalid SCSS control prelude")
13264 .count();
13265
13266 assert_eq!(invalid_control_prelude_count, 5);
13267 }
13268
13269 #[test]
13270 fn extracts_scss_control_block_style_facts() {
13271 let facts = collect_style_facts(
13272 "@if $enabled { .on { color: green; } } @for $i from 1 through 3 { .n { order: $i; } } @each $k, $v in $map { .e { color: $v; } } @while $enabled { .w { color: red; } }",
13273 StyleDialect::Scss,
13274 );
13275 let class_names = facts
13276 .selectors
13277 .iter()
13278 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
13279 .map(|selector| selector.name.as_str())
13280 .collect::<Vec<_>>();
13281
13282 assert_eq!(class_names, vec!["on", "n", "e", "w"]);
13283 }
13284
13285 #[test]
13286 fn extracts_scss_include_content_block_style_facts() {
13287 let source =
13288 ".card { @include interactive($tone) using ($state) { &--active { color: red; } } }";
13289 let parsed = parse(source, StyleDialect::Scss);
13290 let facts = collect_style_facts(source, StyleDialect::Scss);
13291 let class_names = facts
13292 .selectors
13293 .iter()
13294 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
13295 .map(|selector| selector.name.as_str())
13296 .collect::<Vec<_>>();
13297
13298 assert!(parsed.errors().is_empty());
13299 assert!(node_kinds(&parsed.syntax()).contains(&SyntaxKind::ScssIncludeRule));
13300 assert_eq!(class_names, vec!["card", "card--active"]);
13301 }
13302
13303 #[test]
13304 fn parses_scss_nested_property_blocks() {
13305 let result = parse(
13306 ".card { font: { size: 1rem; weight: 600; } border: 1px solid { color: red; } }",
13307 StyleDialect::Scss,
13308 );
13309 let kinds = node_kinds(&result.syntax());
13310 let nested_property_count = kinds
13311 .iter()
13312 .filter(|kind| **kind == SyntaxKind::ScssNestedProperty)
13313 .count();
13314
13315 assert!(result.errors().is_empty());
13316 assert_eq!(nested_property_count, 2);
13317 assert!(kinds.contains(&SyntaxKind::DeclarationList));
13318 assert!(kinds.contains(&SyntaxKind::Value));
13319 assert!(kinds.contains(&SyntaxKind::DimensionValue));
13320 }
13321
13322 #[test]
13323 fn parses_sass_indented_nested_property_blocks() {
13324 let result = parse(
13325 ".card\n font:\n size: 1rem\n weight: 600\n",
13326 StyleDialect::Sass,
13327 );
13328 let kinds = node_kinds(&result.syntax());
13329
13330 assert!(result.errors().is_empty());
13331 assert!(kinds.contains(&SyntaxKind::ScssNestedProperty));
13332 assert!(kinds.contains(&SyntaxKind::SassIndentedBlock));
13333 assert!(kinds.contains(&SyntaxKind::DeclarationList));
13334 assert!(kinds.contains(&SyntaxKind::DimensionValue));
13335 }
13336
13337 #[test]
13338 fn parses_scss_utility_at_rules() {
13339 let result = parse(
13340 "@mixin slot { @content; } @at-root { .rooted { color: red; } } @warn $message; @debug $message; @error $message;",
13341 StyleDialect::Scss,
13342 );
13343 let kinds = node_kinds(&result.syntax());
13344
13345 assert!(result.errors().is_empty());
13346 assert!(kinds.contains(&SyntaxKind::ScssContentRule));
13347 assert!(kinds.contains(&SyntaxKind::ScssAtRootRule));
13348 assert!(kinds.contains(&SyntaxKind::ScssWarnRule));
13349 assert!(kinds.contains(&SyntaxKind::ScssDebugRule));
13350 assert!(kinds.contains(&SyntaxKind::ScssErrorRule));
13351 assert!(kinds.contains(&SyntaxKind::Rule));
13352 }
13353
13354 #[test]
13355 fn structures_css_value_function_calls() {
13356 let result = parse(".a { width: calc(var(--gap) + 1rem); }", StyleDialect::Css);
13357 let kinds = node_kinds(&result.syntax());
13358
13359 assert!(result.errors().is_empty());
13360 assert!(kinds.contains(&SyntaxKind::Value));
13361 assert!(kinds.contains(&SyntaxKind::FunctionCall));
13362 assert!(kinds.contains(&SyntaxKind::FunctionArguments));
13363 assert!(kinds.contains(&SyntaxKind::CalcFunction));
13364 assert!(kinds.contains(&SyntaxKind::VarFunction));
13365 assert!(kinds.contains(&SyntaxKind::BinaryExpression));
13366 }
13367
13368 #[test]
13369 fn structures_modern_css_value_functions() {
13370 let result = parse(
13371 ".a { color: color-mix(in oklch, var(--brand), white 20%); accent-color: device-cmyk(0 1 1 0); width: clamp(1rem, 2vw, 3rem); content: attr(data-label string, \"x\"); padding: env(safe-area-inset-top); background-image: linear-gradient(red, blue); transform: translateX(1rem) rotate(10deg); filter: blur(2px) brightness(1.1); image-set: image-set(url(a.png) 1x); offset-path: path(\"M0,0 L1,1\"); }",
13372 StyleDialect::Css,
13373 );
13374 let kinds = node_kinds(&result.syntax());
13375
13376 assert!(result.errors().is_empty());
13377 assert!(kinds.contains(&SyntaxKind::ColorValue));
13378 assert!(kinds.contains(&SyntaxKind::MathFunction));
13379 assert!(kinds.contains(&SyntaxKind::AttrFunction));
13380 assert!(kinds.contains(&SyntaxKind::EnvFunction));
13381 assert!(kinds.contains(&SyntaxKind::VarFunction));
13382 assert!(kinds.contains(&SyntaxKind::GradientFunction));
13383 assert!(kinds.contains(&SyntaxKind::TransformFunction));
13384 assert!(kinds.contains(&SyntaxKind::FilterFunction));
13385 assert!(kinds.contains(&SyntaxKind::ImageFunction));
13386 assert!(kinds.contains(&SyntaxKind::ShapeFunction));
13387 }
13388
13389 #[test]
13390 fn validates_color_function_micro_grammars() {
13391 let valid = parse(
13392 ".a { color: color-mix(in srgb, red, blue 30%); background: light-dark(white, black); border-color: contrast-color(red); }",
13393 StyleDialect::Css,
13394 );
13395 let dynamic = parse(
13396 ".a { color: color-mix(#{$space}, red, blue); }",
13397 StyleDialect::Scss,
13398 );
13399 let invalid = parse(
13400 ".a { color: color-mix(srgb, red, blue); background: light-dark(white); border-color: contrast-color(red, blue); outline-color: color-mix(in srgb, red); }",
13401 StyleDialect::Css,
13402 );
13403 let invalid_argument_head_count = invalid
13404 .errors()
13405 .iter()
13406 .filter(|error| error.message == "invalid function argument head")
13407 .count();
13408 let invalid_argument_count = invalid
13409 .errors()
13410 .iter()
13411 .filter(|error| error.message == "invalid function argument count")
13412 .count();
13413
13414 assert!(valid.errors().is_empty());
13415 assert!(dynamic.errors().is_empty());
13416 assert_eq!(invalid_argument_head_count, 1);
13417 assert_eq!(invalid_argument_count, 3);
13418 }
13419
13420 #[test]
13421 fn classifies_css_value_functions_case_insensitively() {
13422 let result = parse(
13423 ".a { width: CALC(1px + 2px); color: COLOR-MIX(in srgb, red, blue); transform: TRANSLATEX(1px); filter: BLUR(2px); clip-path: POLYGON(0 0, 100% 0, 100% 100%); }",
13424 StyleDialect::Css,
13425 );
13426 let kinds = node_kinds(&result.syntax());
13427
13428 assert!(result.errors().is_empty());
13429 assert!(kinds.contains(&SyntaxKind::CalcFunction));
13430 assert!(kinds.contains(&SyntaxKind::ColorValue));
13431 assert!(kinds.contains(&SyntaxKind::TransformFunction));
13432 assert!(kinds.contains(&SyntaxKind::FilterFunction));
13433 assert!(kinds.contains(&SyntaxKind::ShapeFunction));
13434 }
13435
13436 #[test]
13437 fn validates_values_l4_math_function_argument_counts() {
13438 let valid = parse(
13439 ".a { width: calc(1px + 2px); min-width: min(1px, 2px); max-width: max(1px); margin: round(nearest, 10px, 3px); padding: hypot(3px, 4px); opacity: log(8, 2); }",
13440 StyleDialect::Css,
13441 );
13442 let invalid = parse(
13443 ".a { width: calc(1px, 2px); min-width: min(); max-width: clamp(1px, 2px); margin: mod(10px); padding: sin(); opacity: atan2(1); }",
13444 StyleDialect::Css,
13445 );
13446 let invalid_argument_count = invalid
13447 .errors()
13448 .iter()
13449 .filter(|error| error.message == "invalid function argument count")
13450 .count();
13451
13452 assert!(valid.errors().is_empty());
13453 assert_eq!(invalid_argument_count, 6);
13454 }
13455
13456 #[test]
13457 fn validates_values_l4_math_function_empty_arguments() {
13458 let valid_fallback = parse(
13459 ".a { color: var(--brand,); padding: env(safe-area-inset-top,); }",
13460 StyleDialect::Css,
13461 );
13462 let invalid = parse(
13463 ".a { width: min(, 1px); height: max(1px,); inset: clamp(1px, , 3px); }",
13464 StyleDialect::Css,
13465 );
13466 let empty_argument_count = invalid
13467 .errors()
13468 .iter()
13469 .filter(|error| error.message == "empty function argument")
13470 .count();
13471
13472 assert!(valid_fallback.errors().is_empty());
13473 assert_eq!(empty_argument_count, 3);
13474 }
13475
13476 #[test]
13477 fn validates_var_env_attr_function_argument_heads() {
13478 let valid = parse(
13479 ".a { color: var(--brand, red, blue); padding: env(safe-area-inset-top, 0px); content: attr(data-label string, \"x\"); }",
13480 StyleDialect::Css,
13481 );
13482 let dynamic = parse(
13483 ".a { color: var(#{$name}); padding: env($area); content: attr(#{$attribute}); }",
13484 StyleDialect::Scss,
13485 );
13486 let invalid = parse(
13487 ".a { color: var(color); padding: env(, 0px); content: attr(123); }",
13488 StyleDialect::Css,
13489 );
13490 let invalid_head_count = invalid
13491 .errors()
13492 .iter()
13493 .filter(|error| error.message == "invalid function argument head")
13494 .count();
13495
13496 assert!(valid.errors().is_empty());
13497 assert!(dynamic.errors().is_empty());
13498 assert_eq!(invalid_head_count, 3);
13499 }
13500
13501 #[test]
13502 fn structures_css_value_atoms_and_function_argument_lists() {
13503 let result = parse(
13504 ".a { color: #fff; width: clamp(1rem, calc(2px + 3px), 4rem); opacity: 50%; z-index: 1; font-family: system, \"Demo\"; unicode-range: U+00A0-00FF; }",
13505 StyleDialect::Css,
13506 );
13507 let kinds = node_kinds(&result.syntax());
13508 let dimension_value_count = kinds
13509 .iter()
13510 .filter(|kind| **kind == SyntaxKind::DimensionValue)
13511 .count();
13512 let number_value_count = kinds
13513 .iter()
13514 .filter(|kind| **kind == SyntaxKind::NumberValue)
13515 .count();
13516 let percentage_value_count = kinds
13517 .iter()
13518 .filter(|kind| **kind == SyntaxKind::PercentageValue)
13519 .count();
13520
13521 assert!(result.errors().is_empty());
13522 assert!(kinds.contains(&SyntaxKind::ColorValue));
13523 assert!(kinds.contains(&SyntaxKind::ValueList));
13524 assert!(kinds.contains(&SyntaxKind::CalcFunction));
13525 assert!(kinds.contains(&SyntaxKind::BinaryExpression));
13526 assert!(kinds.contains(&SyntaxKind::IdentifierValue));
13527 assert!(kinds.contains(&SyntaxKind::StringValue));
13528 assert!(kinds.contains(&SyntaxKind::UnicodeRangeValue));
13529 assert!(dimension_value_count >= 4);
13530 assert!(number_value_count >= 1);
13531 assert!(percentage_value_count >= 1);
13532 }
13533
13534 #[test]
13535 fn parses_custom_property_values_as_component_value_lists() {
13536 let result = parse(
13537 ".a { --api: { display: none }; --empty: ; color: red; }",
13538 StyleDialect::Css,
13539 );
13540 let kinds = node_kinds(&result.syntax());
13541 let tokens = token_kinds(&result.syntax());
13542 let component_value_list_count = kinds
13543 .iter()
13544 .filter(|kind| **kind == SyntaxKind::ComponentValueList)
13545 .count();
13546
13547 assert!(result.errors().is_empty());
13548 assert!(tokens.contains(&SyntaxKind::CustomPropertyName));
13549 assert!(kinds.contains(&SyntaxKind::CustomPropertyValue));
13550 assert!(kinds.contains(&SyntaxKind::SimpleBlock));
13551 assert_eq!(component_value_list_count, 2);
13552 assert!(!kinds.contains(&SyntaxKind::BogusValue));
13553 }
13554
13555 #[test]
13556 fn structures_top_level_value_lists_without_function_comma_confusion() {
13557 let result = parse(
13558 ".a { font-family: system, sans-serif; color: color-mix(in oklch, red, blue); }",
13559 StyleDialect::Css,
13560 );
13561 let kinds = node_kinds(&result.syntax());
13562
13563 assert!(result.errors().is_empty());
13564 assert!(kinds.contains(&SyntaxKind::ValueList));
13565 assert!(!kinds.contains(&SyntaxKind::BogusValueList));
13566 assert!(kinds.contains(&SyntaxKind::ColorValue));
13567 }
13568
13569 #[test]
13570 fn structures_bracketed_value_atoms_and_recovery() {
13571 let closed = parse(
13572 ".grid { grid-template-columns: [full-start] minmax(0, 1fr) [full-end]; }",
13573 StyleDialect::Css,
13574 );
13575 let missing_close = parse(
13576 ".grid { grid-template-columns: [full-start 1fr; }",
13577 StyleDialect::Css,
13578 );
13579
13580 assert!(closed.errors().is_empty());
13581 assert!(node_kinds(&closed.syntax()).contains(&SyntaxKind::BracketedValue));
13582 assert!(node_kinds(&missing_close.syntax()).contains(&SyntaxKind::BogusBracketedValue));
13583 }
13584
13585 #[test]
13586 fn recovers_bogus_top_level_value_lists() {
13587 let result = parse(".a { font-family: system, ; }", StyleDialect::Css);
13588 let kinds = node_kinds(&result.syntax());
13589
13590 assert!(kinds.contains(&SyntaxKind::BogusValueList));
13591 }
13592
13593 #[test]
13594 fn keeps_important_annotation_in_declaration_values() {
13595 let result = parse(".a { color: red !important; }", StyleDialect::Css);
13596 let split = parse(
13597 ".a { color: red ! /* keep */ important; }",
13598 StyleDialect::Css,
13599 );
13600 let kinds = node_kinds(&result.syntax());
13601 let split_kinds = node_kinds(&split.syntax());
13602
13603 assert!(result.errors().is_empty());
13604 assert!(split.errors().is_empty());
13605 assert!(kinds.contains(&SyntaxKind::Declaration));
13606 assert!(kinds.contains(&SyntaxKind::Value));
13607 assert!(kinds.contains(&SyntaxKind::ImportantAnnotation));
13608 assert!(split_kinds.contains(&SyntaxKind::ImportantAnnotation));
13609 assert!(token_kinds(&result.syntax()).contains(&SyntaxKind::Important));
13610 assert!(token_kinds(&split.syntax()).contains(&SyntaxKind::Ident));
13611 }
13612
13613 #[test]
13614 fn structures_url_values() {
13615 let result = parse(
13616 ".a { background: url(images/bg.png); mask: url(\"icons/mask.svg\"); }",
13617 StyleDialect::Css,
13618 );
13619 let kinds = node_kinds(&result.syntax());
13620 let url_value_count = kinds
13621 .iter()
13622 .filter(|kind| **kind == SyntaxKind::UrlValue)
13623 .count();
13624
13625 assert!(result.errors().is_empty());
13626 assert!(kinds.contains(&SyntaxKind::Value));
13627 assert!(kinds.contains(&SyntaxKind::FunctionCall));
13628 assert_eq!(url_value_count, 2);
13629 assert!(token_kinds(&result.syntax()).contains(&SyntaxKind::Url));
13630 }
13631
13632 #[test]
13633 fn structures_bad_strings_as_bogus_values() {
13634 let result = parse(".a { content: \"bad\ncolor: red; }", StyleDialect::Css);
13635 let kinds = node_kinds(&result.syntax());
13636
13637 assert!(
13638 result
13639 .errors()
13640 .iter()
13641 .any(|error| error.code == ParseErrorCode::UnterminatedString)
13642 );
13643 assert!(kinds.contains(&SyntaxKind::BogusValue));
13644 assert!(token_kinds(&result.syntax()).contains(&SyntaxKind::BadString));
13645 }
13646
13647 #[test]
13648 fn structures_scss_interpolation_in_selector_property_and_value() {
13649 let result = parse(
13650 ".button-#{$variant} { #{$prop}: #{$value}; }",
13651 StyleDialect::Scss,
13652 );
13653 let kinds = node_kinds(&result.syntax());
13654 let interpolation_count = kinds
13655 .iter()
13656 .filter(|kind| **kind == SyntaxKind::Interpolation)
13657 .count();
13658
13659 assert!(result.errors().is_empty());
13660 assert_eq!(interpolation_count, 3);
13661 assert!(kinds.contains(&SyntaxKind::ClassSelector));
13662 assert!(kinds.contains(&SyntaxKind::PropertyName));
13663 assert!(kinds.contains(&SyntaxKind::Value));
13664 }
13665
13666 #[test]
13667 fn structures_less_interpolation_in_selector_property_and_value() {
13668 let result = parse(
13669 ".button-@{variant} { @{prop}: @{value}; }",
13670 StyleDialect::Less,
13671 );
13672 let kinds = node_kinds(&result.syntax());
13673 let interpolation_count = kinds
13674 .iter()
13675 .filter(|kind| **kind == SyntaxKind::Interpolation)
13676 .count();
13677
13678 assert!(result.errors().is_empty());
13679 assert_eq!(interpolation_count, 3);
13680 assert!(kinds.contains(&SyntaxKind::ClassSelector));
13681 assert!(kinds.contains(&SyntaxKind::PropertyName));
13682 assert!(kinds.contains(&SyntaxKind::Value));
13683 }
13684
13685 #[test]
13686 fn structures_less_escaped_strings_as_values() {
13687 let result = parse(".a { filter: ~\"alpha(opacity=50)\"; }", StyleDialect::Less);
13688 let kinds = node_kinds(&result.syntax());
13689 let token_kinds = token_kinds(&result.syntax());
13690
13691 assert!(result.errors().is_empty());
13692 assert!(kinds.contains(&SyntaxKind::Value));
13693 assert!(token_kinds.contains(&SyntaxKind::LessEscapedString));
13694 }
13695
13696 #[test]
13697 fn structures_less_property_variables_as_values() {
13698 let result = parse(".a { color: red; background: $color; }", StyleDialect::Less);
13699 let kinds = node_kinds(&result.syntax());
13700 let token_kinds = token_kinds(&result.syntax());
13701
13702 assert!(result.errors().is_empty());
13703 assert!(kinds.contains(&SyntaxKind::LessPropertyVariable));
13704 assert!(token_kinds.contains(&SyntaxKind::LessPropertyVariableToken));
13705 }
13706
13707 #[test]
13708 fn structures_unclosed_interpolation_as_bogus() {
13709 let scss = parse(".button-#{$variant", StyleDialect::Scss);
13710 let less = parse(".button-@{variant", StyleDialect::Less);
13711
13712 assert!(node_kinds(&scss.syntax()).contains(&SyntaxKind::BogusInterpolation));
13713 assert!(node_kinds(&less.syntax()).contains(&SyntaxKind::BogusInterpolation));
13714 assert!(
13715 scss.errors()
13716 .iter()
13717 .any(|error| error.code == ParseErrorCode::UnexpectedCharacter)
13718 );
13719 assert!(
13720 less.errors()
13721 .iter()
13722 .any(|error| error.code == ParseErrorCode::UnexpectedCharacter)
13723 );
13724 }
13725
13726 #[test]
13727 fn structures_css_value_unary_and_precedence_expressions() {
13728 let result = parse(".a { margin: -(1rem + 2px) * 3; }", StyleDialect::Css);
13729 let kinds = node_kinds(&result.syntax());
13730
13731 assert!(result.errors().is_empty());
13732 assert!(kinds.contains(&SyntaxKind::UnaryExpression));
13733 assert!(kinds.contains(&SyntaxKind::ParenthesizedExpression));
13734 assert!(kinds.contains(&SyntaxKind::BinaryExpression));
13735 }
13736
13737 #[test]
13738 fn structures_dialect_variable_references_in_values() {
13739 let scss = parse(".a { margin: $gap; }", StyleDialect::Scss);
13740 let less = parse(".a { margin: @gap; }", StyleDialect::Less);
13741
13742 assert!(scss.errors().is_empty());
13743 assert!(less.errors().is_empty());
13744 assert!(node_kinds(&scss.syntax()).contains(&SyntaxKind::ScssVariableReference));
13745 assert!(node_kinds(&less.syntax()).contains(&SyntaxKind::LessVariableReference));
13746 }
13747
13748 #[test]
13749 fn structures_scss_variable_flags() {
13750 let result = parse(
13751 "$gap: 1rem ! /* keep */ default !global;",
13752 StyleDialect::Scss,
13753 );
13754 let kinds = node_kinds(&result.syntax());
13755 let flag_count = kinds
13756 .iter()
13757 .filter(|kind| **kind == SyntaxKind::ScssVariableFlag)
13758 .count();
13759
13760 assert!(result.errors().is_empty());
13761 assert!(kinds.contains(&SyntaxKind::ScssVariableDeclaration));
13762 assert_eq!(flag_count, 2);
13763 }
13764
13765 #[test]
13766 fn parses_less_mixin_declarations_calls_and_guards() {
13767 let result = parse(
13768 ".theme(@color) when (iscolor(@color)) { color: @color; .rounded(); } .card { .theme(#fff); }",
13769 StyleDialect::Less,
13770 );
13771 let kinds = node_kinds(&result.syntax());
13772
13773 assert!(result.errors().is_empty());
13774 assert!(kinds.contains(&SyntaxKind::LessMixinDeclaration));
13775 assert!(kinds.contains(&SyntaxKind::LessMixinGuard));
13776 assert!(kinds.contains(&SyntaxKind::LessMixinCall));
13777 assert!(kinds.contains(&SyntaxKind::LessVariableReference));
13778 assert!(kinds.contains(&SyntaxKind::Rule));
13779 }
13780
13781 #[test]
13782 fn parses_less_extend_pseudo_class_without_mixin_confusion() {
13783 let less = parse(
13784 ".nav:extend(.inline all) { color: red; }",
13785 StyleDialect::Less,
13786 );
13787 let css = parse(
13788 ".nav:extend(.inline all) { color: red; }",
13789 StyleDialect::Css,
13790 );
13791 let less_kinds = node_kinds(&less.syntax());
13792 let css_kinds = node_kinds(&css.syntax());
13793
13794 assert!(less.errors().is_empty());
13795 assert!(css.errors().is_empty());
13796 assert!(less_kinds.contains(&SyntaxKind::Rule));
13797 assert!(less_kinds.contains(&SyntaxKind::LessExtendRule));
13798 assert!(less_kinds.contains(&SyntaxKind::PseudoSelectorArgument));
13799 assert!(!less_kinds.contains(&SyntaxKind::LessMixinDeclaration));
13800 assert!(!css_kinds.contains(&SyntaxKind::LessExtendRule));
13801 assert!(css_kinds.contains(&SyntaxKind::PseudoClassSelector));
13802 }
13803
13804 #[test]
13805 fn parses_less_detached_ruleset_variable_values() {
13806 let result = parse(
13807 "@rules: { color: red; .rounded(); }; .card { color: blue; }",
13808 StyleDialect::Less,
13809 );
13810 let kinds = node_kinds(&result.syntax());
13811
13812 assert!(result.errors().is_empty());
13813 assert!(kinds.contains(&SyntaxKind::LessVariableDeclaration));
13814 assert!(kinds.contains(&SyntaxKind::LessDetachedRulesetNode));
13815 assert!(kinds.contains(&SyntaxKind::DeclarationList));
13816 assert!(kinds.contains(&SyntaxKind::Declaration));
13817 assert!(kinds.contains(&SyntaxKind::LessMixinCall));
13818 assert!(kinds.contains(&SyntaxKind::Rule));
13819 }
13820
13821 #[test]
13822 fn recovers_unclosed_less_detached_rulesets_as_bogus() {
13823 let result = parse("@rules: { color: red;", StyleDialect::Less);
13824 let kinds = node_kinds(&result.syntax());
13825
13826 assert!(kinds.contains(&SyntaxKind::BogusLessDetachedRuleset));
13827 assert!(
13828 result
13829 .errors()
13830 .iter()
13831 .any(|error| error.code == ParseErrorCode::UnexpectedCharacter)
13832 );
13833 }
13834
13835 #[test]
13836 fn parses_less_namespace_access_calls() {
13837 let result = parse(
13838 ".card { #bundle > .rounded(); color: blue; }",
13839 StyleDialect::Less,
13840 );
13841 let kinds = node_kinds(&result.syntax());
13842
13843 assert!(result.errors().is_empty());
13844 assert!(kinds.contains(&SyntaxKind::LessNamespaceAccess));
13845 assert!(kinds.contains(&SyntaxKind::LessMixinCall));
13846 assert!(kinds.contains(&SyntaxKind::Declaration));
13847 }
13848
13849 #[test]
13850 fn keeps_nested_selectors_separate_from_less_namespace_access() {
13851 let result = parse(
13852 ".card { #child > .leaf { color: red; } }",
13853 StyleDialect::Less,
13854 );
13855 let kinds = node_kinds(&result.syntax());
13856
13857 assert!(result.errors().is_empty());
13858 assert!(kinds.contains(&SyntaxKind::Rule));
13859 assert!(!kinds.contains(&SyntaxKind::LessNamespaceAccess));
13860 }
13861
13862 #[test]
13863 fn extracts_initial_style_facts_from_parser_surface() {
13864 let facts = collect_style_facts(
13865 "@use \"tokens\"; $gap: 1rem; %surface { color: red; } .card#main { --space: $gap; }",
13866 StyleDialect::Scss,
13867 );
13868
13869 assert_eq!(facts.product, "omena-parser.style-facts");
13870 assert_eq!(facts.dialect, StyleDialect::Scss);
13871 assert_eq!(facts.selector_count, 3);
13872 assert_eq!(facts.variable_count, 3);
13873 assert_eq!(facts.at_rule_count, 1);
13874 assert!(facts.selectors.iter().any(|selector| {
13875 selector.kind == ParsedSelectorFactKind::Class && selector.name == "card"
13876 }));
13877 assert!(facts.selectors.iter().any(|selector| {
13878 selector.kind == ParsedSelectorFactKind::Id && selector.name == "main"
13879 }));
13880 assert!(facts.selectors.iter().any(|selector| {
13881 selector.kind == ParsedSelectorFactKind::Placeholder && selector.name == "surface"
13882 }));
13883 assert!(facts.variables.iter().any(|variable| {
13884 variable.kind == ParsedVariableFactKind::ScssDeclaration && variable.name == "$gap"
13885 }));
13886 assert!(facts.variables.iter().any(|variable| {
13887 variable.kind == ParsedVariableFactKind::ScssReference && variable.name == "$gap"
13888 }));
13889 assert!(facts.variables.iter().any(|variable| {
13890 variable.kind == ParsedVariableFactKind::CustomPropertyDeclaration
13891 && variable.name == "--space"
13892 }));
13893 assert_eq!(facts.at_rules[0].node_kind, Some(SyntaxKind::ScssUseRule));
13894 }
13895
13896 #[test]
13897 fn summarizes_style_facts_as_parser_owned_product() {
13898 let summary = summarize_omena_parser_style_facts(
13899 "@use \"tokens\"; $gap: 1rem; .card { --space: $gap; }",
13900 StyleDialect::Scss,
13901 );
13902
13903 assert_eq!(summary.schema_version, "0");
13904 assert_eq!(summary.product, "omena-parser.style-facts");
13905 assert_eq!(summary.dialect, "scss");
13906 assert_eq!(summary.parser_error_count, 0);
13907 assert_eq!(summary.class_selector_names, vec!["card".to_string()]);
13908 assert_eq!(summary.variable_names, vec!["$gap".to_string()]);
13909 assert_eq!(summary.custom_property_names, vec!["--space".to_string()]);
13910 assert_eq!(summary.sass_module_use_sources, vec!["tokens".to_string()]);
13911 }
13912
13913 #[test]
13914 fn extracts_sass_symbol_style_facts() {
13915 let facts = collect_style_facts(
13916 "@mixin tone($color) { color: $color; } @function double($x) { @return $x * 2; } .card { @include tone(red); width: double(2px); }",
13917 StyleDialect::Scss,
13918 );
13919 let symbol_kinds = facts
13920 .sass_symbols
13921 .iter()
13922 .map(|symbol| (symbol.kind, symbol.name.as_str(), symbol.role))
13923 .collect::<Vec<_>>();
13924
13925 assert_eq!(facts.sass_symbol_count, 8);
13926 assert!(symbol_kinds.contains(&(
13927 ParsedSassSymbolFactKind::MixinDeclaration,
13928 "tone",
13929 "declaration"
13930 )));
13931 assert!(symbol_kinds.contains(&(
13932 ParsedSassSymbolFactKind::MixinInclude,
13933 "tone",
13934 "include"
13935 )));
13936 assert!(symbol_kinds.contains(&(
13937 ParsedSassSymbolFactKind::FunctionDeclaration,
13938 "double",
13939 "declaration"
13940 )));
13941 assert!(symbol_kinds.contains(&(ParsedSassSymbolFactKind::FunctionCall, "double", "call")));
13942 assert!(symbol_kinds.contains(&(
13943 ParsedSassSymbolFactKind::VariableDeclaration,
13944 "color",
13945 "declaration"
13946 )));
13947 assert!(symbol_kinds.contains(&(
13948 ParsedSassSymbolFactKind::VariableReference,
13949 "color",
13950 "reference"
13951 )));
13952 }
13953
13954 #[test]
13955 fn extracts_namespaced_sass_symbol_style_facts() {
13956 let facts = collect_style_facts(
13957 r#"@use "./tokens" as tokens; .card { color: tokens.$brand; @include tokens.tone(red); width: tokens.double(2px); }"#,
13958 StyleDialect::Scss,
13959 );
13960 let symbol_kinds = facts
13961 .sass_symbols
13962 .iter()
13963 .map(|symbol| {
13964 (
13965 symbol.kind,
13966 symbol.name.as_str(),
13967 symbol.role,
13968 symbol.namespace.as_deref(),
13969 )
13970 })
13971 .collect::<Vec<_>>();
13972
13973 assert_eq!(facts.sass_symbol_count, 3);
13974 assert!(symbol_kinds.contains(&(
13975 ParsedSassSymbolFactKind::VariableReference,
13976 "brand",
13977 "reference",
13978 Some("tokens")
13979 )));
13980 assert!(symbol_kinds.contains(&(
13981 ParsedSassSymbolFactKind::MixinInclude,
13982 "tone",
13983 "include",
13984 Some("tokens")
13985 )));
13986 assert!(symbol_kinds.contains(&(
13987 ParsedSassSymbolFactKind::FunctionCall,
13988 "double",
13989 "call",
13990 Some("tokens")
13991 )));
13992 assert_eq!(facts.sass_include_count, 1);
13993 assert_eq!(facts.sass_includes[0].name, "tone");
13994 assert_eq!(facts.sass_includes[0].namespace.as_deref(), Some("tokens"));
13995 assert_eq!(facts.sass_includes[0].params, "(red)");
13996 }
13997
13998 #[test]
13999 fn extracts_sass_module_edge_style_facts() {
14000 let facts = collect_style_facts(
14001 r#"@use "./tokens" as tokens; @use "./reset" as *; @use "sass:map"; @forward "./theme" show $brand, tone; @import "legacy", url("print.css");"#,
14002 StyleDialect::Scss,
14003 );
14004
14005 assert_eq!(facts.sass_module_edge_count, 6);
14006 assert!(facts.sass_module_edges.iter().any(|edge| {
14007 edge.kind == ParsedSassModuleEdgeFactKind::Use
14008 && edge.source == "./tokens"
14009 && edge.namespace_kind == Some("alias")
14010 && edge.namespace.as_deref() == Some("tokens")
14011 }));
14012 assert!(facts.sass_module_edges.iter().any(|edge| {
14013 edge.kind == ParsedSassModuleEdgeFactKind::Use
14014 && edge.source == "./reset"
14015 && edge.namespace_kind == Some("wildcard")
14016 && edge.namespace.is_none()
14017 }));
14018 assert!(facts.sass_module_edges.iter().any(|edge| {
14019 edge.kind == ParsedSassModuleEdgeFactKind::Use
14020 && edge.source == "sass:map"
14021 && edge.namespace_kind == Some("default")
14022 && edge.namespace.as_deref() == Some("map")
14023 }));
14024 assert!(facts.sass_module_edges.iter().any(|edge| {
14025 edge.kind == ParsedSassModuleEdgeFactKind::Forward
14026 && edge.source == "./theme"
14027 && edge.visibility_filter_kind == Some("show")
14028 && edge.visibility_filter_names == vec!["brand", "tone"]
14029 }));
14030 assert!(facts.sass_module_edges.iter().any(|edge| {
14031 edge.kind == ParsedSassModuleEdgeFactKind::Import && edge.source == "legacy"
14032 }));
14033 }
14034
14035 #[test]
14036 fn extracts_animation_name_style_facts() {
14037 let facts = collect_style_facts(
14038 "@keyframes fade { from { opacity: 0; } to { opacity: 1; } } @keyframes \"slide\" { to { opacity: 1; } } .card { animation-name: fade, \"slide\", none; }",
14039 StyleDialect::Css,
14040 );
14041 let keyframe_names = facts
14042 .animations
14043 .iter()
14044 .filter(|animation| animation.kind == ParsedAnimationFactKind::KeyframesDeclaration)
14045 .map(|animation| animation.name.as_str())
14046 .collect::<Vec<_>>();
14047 let reference_names = facts
14048 .animations
14049 .iter()
14050 .filter(|animation| animation.kind == ParsedAnimationFactKind::AnimationNameReference)
14051 .map(|animation| animation.name.as_str())
14052 .collect::<Vec<_>>();
14053
14054 assert_eq!(facts.animation_count, 4);
14055 assert_eq!(keyframe_names, vec!["fade", "slide"]);
14056 assert_eq!(reference_names, vec!["fade", "slide"]);
14057 }
14058
14059 #[test]
14060 fn extracts_animation_shorthand_style_facts() {
14061 let facts = collect_style_facts(
14062 "@keyframes fade { to { opacity: 1; } } @keyframes \"slide\" { to { opacity: 1; } } .card { animation: 1s ease-in fade, \"slide\" 2s linear both, none 1s, var(--anim) 1s; }",
14063 StyleDialect::Css,
14064 );
14065 let keyframe_names = facts
14066 .animations
14067 .iter()
14068 .filter(|animation| animation.kind == ParsedAnimationFactKind::KeyframesDeclaration)
14069 .map(|animation| animation.name.as_str())
14070 .collect::<Vec<_>>();
14071 let reference_names = facts
14072 .animations
14073 .iter()
14074 .filter(|animation| animation.kind == ParsedAnimationFactKind::AnimationNameReference)
14075 .map(|animation| animation.name.as_str())
14076 .collect::<Vec<_>>();
14077
14078 assert_eq!(facts.animation_count, 4);
14079 assert_eq!(keyframe_names, vec!["fade", "slide"]);
14080 assert_eq!(reference_names, vec!["fade", "slide"]);
14081 }
14082
14083 #[test]
14084 fn keeps_at_rule_header_dashed_idents_out_of_custom_property_facts() {
14085 let facts = collect_style_facts(
14086 "@property --accent { syntax: \"<color>\"; inherits: false; initial-value: red; } @font-palette-values --brand { font-family: Demo; } @color-profile --display-p3 { src: url(p3.icc); } @position-try --popover { inset-area: top; }",
14087 StyleDialect::Css,
14088 );
14089 let custom_properties: Vec<&str> = facts
14090 .variables
14091 .iter()
14092 .filter(|variable| {
14093 matches!(
14094 variable.kind,
14095 ParsedVariableFactKind::CustomPropertyDeclaration
14096 | ParsedVariableFactKind::CustomPropertyReference
14097 )
14098 })
14099 .map(|variable| variable.name.as_str())
14100 .collect();
14101
14102 assert_eq!(custom_properties, vec!["--accent"]);
14103 }
14104
14105 #[test]
14106 fn extracts_all_top_level_classes_from_complex_selector_headers() {
14107 let facts = collect_style_facts(
14108 "#app.theme > .card:has(> .icon) { color: red; }",
14109 StyleDialect::Css,
14110 );
14111 let class_names: Vec<&str> = facts
14112 .selectors
14113 .iter()
14114 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14115 .map(|selector| selector.name.as_str())
14116 .collect();
14117
14118 assert_eq!(class_names, vec!["theme", "card"]);
14119 }
14120
14121 #[test]
14122 fn extracts_css_nesting_at_rule_selector_facts() {
14123 let facts = collect_style_facts(
14124 ".card { @nest &__icon { color: red; &--active { color: blue; } } }",
14125 StyleDialect::Css,
14126 );
14127 let class_names: Vec<&str> = facts
14128 .selectors
14129 .iter()
14130 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14131 .map(|selector| selector.name.as_str())
14132 .collect();
14133
14134 assert_eq!(
14135 class_names,
14136 vec!["card", "card__icon", "card__icon--active"]
14137 );
14138 }
14139
14140 #[test]
14141 fn parses_mid_typing_char_boundary_edits_without_panicking() {
14142 let fixtures = [
14143 (
14144 StyleDialect::Css,
14145 ".card { color: color-mix(in oklch, red, blue); }",
14146 ),
14147 (
14148 StyleDialect::Scss,
14149 "@use \"tokens\" with ($gap: 1rem); .card { &__아이콘 { color: $gap; } }",
14150 ),
14151 (
14152 StyleDialect::Sass,
14153 ".card\n color: red\n &__icon\n color: blue\n",
14154 ),
14155 (
14156 StyleDialect::Less,
14157 "@tone: red; .card() when (iscolor(@tone)) { color: @tone; }",
14158 ),
14159 ];
14160 let insertions = [" ", "{", "}", ":", "@media (", "한"];
14161
14162 for (dialect, source) in fixtures {
14163 for offset in char_boundary_offsets(source) {
14164 for insertion in insertions {
14165 let mut edited = source.to_string();
14166 edited.insert_str(offset, insertion);
14167 let _ = parse(&edited, dialect);
14168 }
14169 }
14170 }
14171 }
14172
14173 #[test]
14174 fn parses_deterministic_malformed_byte_corpus_without_panicking() {
14175 let mut byte_fixtures = vec![
14176 Vec::new(),
14177 b"\0".to_vec(),
14178 b"\xef\xbb\xbf.card { color: red; }".to_vec(),
14179 b".a { content: \"unterminated".to_vec(),
14180 b".a { background: url(foo bar) }".to_vec(),
14181 b"@media screen { .a { color: red".to_vec(),
14182 b".a { --x: { [ ( ; }".to_vec(),
14183 vec![0xff, b'.', b'a', b' ', b'{', b'}'],
14184 vec![0xe1, 0x84, b'.', b'a', b'{', b'c', b':', b'r'],
14185 ];
14186 for seed in 0..32u32 {
14187 byte_fixtures.push(deterministic_byte_fixture(seed));
14188 }
14189
14190 for bytes in byte_fixtures {
14191 let source = String::from_utf8_lossy(&bytes).into_owned();
14192 for dialect in [
14193 StyleDialect::Css,
14194 StyleDialect::Scss,
14195 StyleDialect::Sass,
14196 StyleDialect::Less,
14197 ] {
14198 let parse_result = std::panic::catch_unwind(|| parse(&source, dialect));
14199 assert!(
14200 parse_result.is_ok(),
14201 "parse panicked for dialect={dialect:?} source={source:?}"
14202 );
14203 let Ok(parse_result) = parse_result else {
14204 continue;
14205 };
14206
14207 let lex_result = std::panic::catch_unwind(|| lex(&source, dialect));
14208 assert!(
14209 lex_result.is_ok(),
14210 "lex panicked for dialect={dialect:?} source={source:?}"
14211 );
14212 let Ok(lex_result) = lex_result else {
14213 continue;
14214 };
14215
14216 assert_eq!(parse_result.syntax().kind(), SyntaxKind::Root);
14217 assert_lex_ranges_are_char_boundaries(&source, lex_result.tokens());
14218 }
14219 }
14220 }
14221
14222 #[test]
14223 fn preserves_lossless_cst_text_for_valid_corpus() {
14224 let fixtures = [
14225 (
14226 StyleDialect::Css,
14227 ".card { color: red; --space: calc(1rem + 2px); }",
14228 ),
14229 (
14230 StyleDialect::Scss,
14231 "@use \"tokens\"; .card { &__icon { color: $accent; } }",
14232 ),
14233 (
14234 StyleDialect::Sass,
14235 ".card\n color: red\n &__icon\n color: blue\n",
14236 ),
14237 (
14238 StyleDialect::Less,
14239 "@tone: red; .card() when (iscolor(@tone)) { color: @tone; }",
14240 ),
14241 ];
14242
14243 for (dialect, source) in fixtures {
14244 let result = parse(source, dialect);
14245 let syntax = result.syntax();
14246
14247 assert_eq!(syntax.kind(), SyntaxKind::Root);
14248 assert_eq!(source_text(&syntax).as_deref(), Some(source));
14249 assert_eq!(result.source_text().as_deref(), Some(source));
14250
14251 let reparsed = parse(&result.source_text().unwrap_or_default(), dialect);
14252 assert_eq!(reparsed.source_text().as_deref(), Some(source));
14253 assert_eq!(reparsed.syntax().kind(), SyntaxKind::Root);
14254 }
14255 }
14256
14257 #[test]
14258 fn extracts_nested_bem_style_facts_with_parent_context() {
14259 let facts = collect_style_facts(
14260 ".card { &__icon { &--small { color: red; } } --space: 1rem; color: var(--space); }",
14261 StyleDialect::Scss,
14262 );
14263 let class_names: Vec<&str> = facts
14264 .selectors
14265 .iter()
14266 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14267 .map(|selector| selector.name.as_str())
14268 .collect();
14269 let custom_properties: Vec<&str> = facts
14270 .variables
14271 .iter()
14272 .map(|variable| variable.name.as_str())
14273 .collect();
14274
14275 assert_eq!(class_names, vec!["card", "card__icon", "card__icon--small"]);
14276 assert!(custom_properties.contains(&"--space"));
14277 assert!(!custom_properties.contains(&"--small"));
14278 assert_eq!(facts.error_count, 0);
14279 }
14280
14281 #[test]
14282 fn extracts_non_bem_ampersand_suffix_style_facts() {
14283 let facts = collect_style_facts(
14284 ".btn { &-legacy {} &_legacy {} &suffix {} }",
14285 StyleDialect::Scss,
14286 );
14287 let class_names: Vec<&str> = facts
14288 .selectors
14289 .iter()
14290 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14291 .map(|selector| selector.name.as_str())
14292 .collect();
14293
14294 assert_eq!(
14295 class_names,
14296 vec!["btn", "btn-legacy", "btn_legacy", "btnsuffix"]
14297 );
14298 assert_eq!(facts.error_count, 0);
14299 }
14300
14301 #[test]
14302 fn ignores_non_defining_selector_function_arguments() {
14303 let facts = collect_style_facts(
14304 ".btn:is(.active, .primary):has(#target, %surface) { color: red; }",
14305 StyleDialect::Scss,
14306 );
14307 let class_names: Vec<&str> = facts
14308 .selectors
14309 .iter()
14310 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14311 .map(|selector| selector.name.as_str())
14312 .collect();
14313 let id_names: Vec<&str> = facts
14314 .selectors
14315 .iter()
14316 .filter(|selector| selector.kind == ParsedSelectorFactKind::Id)
14317 .map(|selector| selector.name.as_str())
14318 .collect();
14319 let placeholder_names: Vec<&str> = facts
14320 .selectors
14321 .iter()
14322 .filter(|selector| selector.kind == ParsedSelectorFactKind::Placeholder)
14323 .map(|selector| selector.name.as_str())
14324 .collect();
14325
14326 assert_eq!(class_names, vec!["btn"]);
14327 assert!(id_names.is_empty());
14328 assert!(placeholder_names.is_empty());
14329 }
14330
14331 #[test]
14332 fn filters_css_module_global_scope_selector_facts() {
14333 let facts = collect_style_facts(
14334 ":global { .reset { color: red; } } :global(.standalone) { color: red; } .card :global(.child) { color: red; } :local(.button) { color: blue; }",
14335 StyleDialect::Css,
14336 );
14337 let outer_local = collect_style_facts(
14338 ":local { :global { .kept { color: green; } } }",
14339 StyleDialect::Css,
14340 );
14341 let class_names = facts
14342 .selectors
14343 .iter()
14344 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14345 .map(|selector| selector.name.as_str())
14346 .collect::<Vec<_>>();
14347 let outer_local_class_names = outer_local
14348 .selectors
14349 .iter()
14350 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14351 .map(|selector| selector.name.as_str())
14352 .collect::<Vec<_>>();
14353
14354 assert_eq!(class_names, vec!["card", "button"]);
14355 assert_eq!(outer_local_class_names, vec!["kept"]);
14356 }
14357
14358 #[test]
14359 fn extracts_css_module_local_id_selector_facts() {
14360 let facts = collect_style_facts(
14361 ":local(#panel) { color: red; } :global(#reset) { color: red; } .card :global(#child) { color: blue; }",
14362 StyleDialect::Css,
14363 );
14364 let class_names = facts
14365 .selectors
14366 .iter()
14367 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14368 .map(|selector| selector.name.as_str())
14369 .collect::<Vec<_>>();
14370 let id_names = facts
14371 .selectors
14372 .iter()
14373 .filter(|selector| selector.kind == ParsedSelectorFactKind::Id)
14374 .map(|selector| selector.name.as_str())
14375 .collect::<Vec<_>>();
14376
14377 assert_eq!(class_names, vec!["card"]);
14378 assert_eq!(id_names, vec!["panel"]);
14379 }
14380
14381 #[test]
14382 fn extracts_css_module_local_selector_list_facts() {
14383 let facts = collect_style_facts(
14384 ":local(.button, .link:hover) { color: red; } :global(.reset, .theme) { color: blue; }",
14385 StyleDialect::Css,
14386 );
14387 let class_names = facts
14388 .selectors
14389 .iter()
14390 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14391 .map(|selector| selector.name.as_str())
14392 .collect::<Vec<_>>();
14393
14394 assert_eq!(class_names, vec!["button", "link"]);
14395 }
14396
14397 #[test]
14398 fn keeps_trailing_local_selector_group_classes() {
14399 let facts = collect_style_facts(
14400 ":local(.button) .icon, :local(.card).active { color: red; }",
14401 StyleDialect::Css,
14402 );
14403 let mut class_names = facts
14404 .selectors
14405 .iter()
14406 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14407 .map(|selector| selector.name.as_str())
14408 .collect::<Vec<_>>();
14409 class_names.sort_unstable();
14410
14411 assert_eq!(class_names, vec!["active", "button", "card", "icon"]);
14412 }
14413
14414 #[test]
14415 fn parses_functional_pseudo_selector_lists_with_bogus_item_recovery() {
14416 let result = parse(
14417 ".btn:is(#it/typo, .ok):where(.wide, .compact) { color: red; }",
14418 StyleDialect::Css,
14419 );
14420 let kinds = node_kinds(&result.syntax());
14421 let selector_list_count = kinds
14422 .iter()
14423 .filter(|kind| **kind == SyntaxKind::SelectorList)
14424 .count();
14425 let class_selector_count = kinds
14426 .iter()
14427 .filter(|kind| **kind == SyntaxKind::ClassSelector)
14428 .count();
14429
14430 assert!(kinds.contains(&SyntaxKind::Rule));
14431 assert!(kinds.contains(&SyntaxKind::Declaration));
14432 assert!(kinds.contains(&SyntaxKind::PseudoSelectorArgument));
14433 assert!(kinds.contains(&SyntaxKind::BogusSelector));
14434 assert!(!kinds.contains(&SyntaxKind::BogusRule));
14435 assert!(selector_list_count >= 3);
14436 assert!(class_selector_count >= 4);
14437 assert!(
14438 result
14439 .errors()
14440 .iter()
14441 .any(|error| error.message == "invalid selector in selector list")
14442 );
14443 }
14444
14445 #[test]
14446 fn parses_not_arguments_as_strict_selector_lists() {
14447 let forgiving = parse(".btn:is(#it/typo, .ok) { color: red; }", StyleDialect::Css);
14448 let strict = parse(".btn:not(#it/typo, .ok) { color: red; }", StyleDialect::Css);
14449 let forgiving_kinds = node_kinds(&forgiving.syntax());
14450 let strict_kinds = node_kinds(&strict.syntax());
14451
14452 assert!(forgiving_kinds.contains(&SyntaxKind::BogusSelector));
14453 assert!(!forgiving_kinds.contains(&SyntaxKind::BogusSelectorList));
14454 assert!(strict_kinds.contains(&SyntaxKind::BogusSelector));
14455 assert!(strict_kinds.contains(&SyntaxKind::BogusSelectorList));
14456 }
14457
14458 #[test]
14459 fn parses_nth_child_of_selector_lists_as_cst_nodes() {
14460 let result = parse(
14461 ".grid > :nth-child(2n + 1 of .item, [data-active]) { color: red; }",
14462 StyleDialect::Css,
14463 );
14464 let kinds = node_kinds(&result.syntax());
14465 let selector_list_count = kinds
14466 .iter()
14467 .filter(|kind| **kind == SyntaxKind::SelectorList)
14468 .count();
14469
14470 assert!(result.errors().is_empty());
14471 assert!(kinds.contains(&SyntaxKind::NthSelectorArgument));
14472 assert!(kinds.contains(&SyntaxKind::NthSelectorFormula));
14473 assert!(kinds.contains(&SyntaxKind::NthSelectorOfSelectorList));
14474 assert!(kinds.contains(&SyntaxKind::ClassSelector));
14475 assert!(kinds.contains(&SyntaxKind::AttributeSelector));
14476 assert!(selector_list_count >= 2);
14477 }
14478
14479 #[test]
14480 fn parses_nth_of_type_arguments_as_formula_cst_nodes() {
14481 let result = parse("li:nth-of-type(2n + 1) { color: red; }", StyleDialect::Css);
14482 let kinds = node_kinds(&result.syntax());
14483
14484 assert!(result.errors().is_empty());
14485 assert!(kinds.contains(&SyntaxKind::NthSelectorArgument));
14486 assert!(kinds.contains(&SyntaxKind::NthSelectorFormula));
14487 assert!(!kinds.contains(&SyntaxKind::NthSelectorOfSelectorList));
14488 }
14489
14490 #[test]
14491 fn parses_has_arguments_as_relative_selector_lists() {
14492 let result = parse(
14493 ".card:has(> .icon, + [data-active], :has(~ .nested)) { color: red; }",
14494 StyleDialect::Css,
14495 );
14496 let kinds = node_kinds(&result.syntax());
14497 let relative_selector_count = kinds
14498 .iter()
14499 .filter(|kind| **kind == SyntaxKind::RelativeSelector)
14500 .count();
14501 let relative_list_count = kinds
14502 .iter()
14503 .filter(|kind| **kind == SyntaxKind::RelativeSelectorList)
14504 .count();
14505
14506 assert!(result.errors().is_empty());
14507 assert_eq!(relative_list_count, 2);
14508 assert_eq!(relative_selector_count, 4);
14509 assert!(kinds.contains(&SyntaxKind::Combinator));
14510 assert!(kinds.contains(&SyntaxKind::AttributeSelector));
14511 assert!(kinds.contains(&SyntaxKind::PseudoClassSelector));
14512 }
14513
14514 #[test]
14515 fn parses_lang_and_dir_arguments_as_cst_nodes() {
14516 let result = parse(
14517 ":lang(en-US, \"ko\") .card:dir(rtl) { color: red; }",
14518 StyleDialect::Css,
14519 );
14520 let kinds = node_kinds(&result.syntax());
14521 let language_tag_count = kinds
14522 .iter()
14523 .filter(|kind| **kind == SyntaxKind::LanguageTag)
14524 .count();
14525
14526 assert!(
14527 result.errors().is_empty(),
14528 "unexpected parse errors: {:?}",
14529 result.errors()
14530 );
14531 assert!(kinds.contains(&SyntaxKind::LanguageSelectorArgument));
14532 assert!(kinds.contains(&SyntaxKind::DirectionalitySelectorArgument));
14533 assert_eq!(language_tag_count, 2);
14534 }
14535
14536 #[test]
14537 fn decomposes_selector_lists_into_selector_nodes() {
14538 let result = parse(
14539 ".card:hover > #title, article.card || .icon[data-active] { color: red; }",
14540 StyleDialect::Css,
14541 );
14542 let kinds = node_kinds(&result.syntax());
14543
14544 assert!(result.errors().is_empty());
14545 assert!(kinds.contains(&SyntaxKind::Selector));
14546 assert!(kinds.contains(&SyntaxKind::ComplexSelector));
14547 assert!(kinds.contains(&SyntaxKind::CompoundSelector));
14548 assert!(kinds.contains(&SyntaxKind::ClassSelector));
14549 assert!(kinds.contains(&SyntaxKind::IdSelector));
14550 assert!(kinds.contains(&SyntaxKind::TypeSelector));
14551 assert!(kinds.contains(&SyntaxKind::PseudoClassSelector));
14552 assert!(kinds.contains(&SyntaxKind::AttributeSelector));
14553 assert!(kinds.contains(&SyntaxKind::Combinator));
14554 }
14555
14556 #[test]
14557 fn parses_namespace_qualified_selectors() {
14558 let result = parse(
14559 "@namespace svg url(\"http://www.w3.org/2000/svg\"); svg|a, *|button, |main, svg|*, *|* { color: red; }",
14560 StyleDialect::Css,
14561 );
14562 let kinds = node_kinds(&result.syntax());
14563 let namespace_prefix_count = kinds
14564 .iter()
14565 .filter(|kind| **kind == SyntaxKind::NamespacePrefix)
14566 .count();
14567 let type_selector_count = kinds
14568 .iter()
14569 .filter(|kind| **kind == SyntaxKind::TypeSelector)
14570 .count();
14571 let universal_selector_count = kinds
14572 .iter()
14573 .filter(|kind| **kind == SyntaxKind::UniversalSelector)
14574 .count();
14575
14576 assert!(result.errors().is_empty());
14577 assert_eq!(namespace_prefix_count, 5);
14578 assert_eq!(type_selector_count, 3);
14579 assert_eq!(universal_selector_count, 2);
14580 }
14581
14582 #[test]
14583 fn decomposes_attribute_matchers_into_cst_nodes() {
14584 let result = parse(
14585 ".a[data-state~=\"active\"][lang|=\"en\"][href^=\"/docs\"][href$=\".pdf\"][class*=\"btn\"][data-mode=\"x\" i] { color: red; }",
14586 StyleDialect::Css,
14587 );
14588 let kinds = node_kinds(&result.syntax());
14589 let matcher_count = kinds
14590 .iter()
14591 .filter(|kind| **kind == SyntaxKind::AttributeMatcher)
14592 .count();
14593 let name_count = kinds
14594 .iter()
14595 .filter(|kind| **kind == SyntaxKind::AttributeName)
14596 .count();
14597 let value_count = kinds
14598 .iter()
14599 .filter(|kind| **kind == SyntaxKind::AttributeValue)
14600 .count();
14601
14602 assert!(result.errors().is_empty());
14603 assert!(kinds.contains(&SyntaxKind::AttributeSelector));
14604 assert_eq!(matcher_count, 6);
14605 assert_eq!(name_count, 6);
14606 assert_eq!(value_count, 6);
14607 assert!(kinds.contains(&SyntaxKind::AttributeModifier));
14608 }
14609
14610 #[test]
14611 fn decomposes_css_module_scope_functions_into_cst_nodes() {
14612 let result = parse(
14613 ":local(.button) { color: red; } :global(.reset) { box-sizing: border-box; }",
14614 StyleDialect::Css,
14615 );
14616 let kinds = node_kinds(&result.syntax());
14617
14618 assert!(result.errors().is_empty());
14619 assert!(kinds.contains(&SyntaxKind::PseudoClassSelector));
14620 assert!(kinds.contains(&SyntaxKind::PseudoSelectorArgument));
14621 assert!(kinds.contains(&SyntaxKind::CssModuleLocalBlock));
14622 assert!(kinds.contains(&SyntaxKind::CssModuleGlobalBlock));
14623 }
14624
14625 #[test]
14626 fn decomposes_nested_and_pseudo_element_selectors() {
14627 let result = parse("&::before { content: \"\"; }", StyleDialect::Scss);
14628 let kinds = node_kinds(&result.syntax());
14629
14630 assert!(result.errors().is_empty());
14631 assert!(kinds.contains(&SyntaxKind::NestingSelectorNode));
14632 assert!(kinds.contains(&SyntaxKind::PseudoElementSelector));
14633 }
14634
14635 #[test]
14636 fn parses_sass_indented_blocks_as_rule_declaration_lists() {
14637 let result = parse(
14638 ".card\n color: red\n .title\n color: blue\n",
14639 StyleDialect::Sass,
14640 );
14641 let kinds = node_kinds(&result.syntax());
14642
14643 assert!(result.errors().is_empty());
14644 assert!(kinds.contains(&SyntaxKind::SassIndentedBlock));
14645 assert!(kinds.contains(&SyntaxKind::Rule));
14646 assert!(kinds.contains(&SyntaxKind::DeclarationList));
14647 assert!(kinds.contains(&SyntaxKind::Declaration));
14648 assert!(kinds.contains(&SyntaxKind::ClassSelector));
14649 }
14650
14651 #[test]
14652 fn extracts_sass_indented_nested_bem_style_facts() {
14653 let facts = collect_style_facts(".card\n &__icon\n color: red\n", StyleDialect::Sass);
14654 let class_names: Vec<&str> = facts
14655 .selectors
14656 .iter()
14657 .filter(|selector| selector.kind == ParsedSelectorFactKind::Class)
14658 .map(|selector| selector.name.as_str())
14659 .collect();
14660
14661 assert_eq!(class_names, vec!["card", "card__icon"]);
14662 assert_eq!(facts.error_count, 0);
14663 }
14664
14665 #[test]
14666 fn exposes_typed_cst_wrapper_slice() {
14667 let result = parse(
14668 ".card { color: red; --accent: blue; } @media (width >= 1px) { .button { color: var(--accent); } }",
14669 StyleDialect::Css,
14670 );
14671 let cst = result.cst();
14672 let stylesheet = cst.stylesheet();
14673 let rules = cst.rules();
14674 let selectors = cst.selectors();
14675 let declarations = cst.declarations();
14676 let values = cst.values();
14677 let component_values = parse_entry_point(
14678 "calc(1px + 2px)",
14679 StyleDialect::Css,
14680 ParseEntryPoint::ComponentValue,
14681 )
14682 .cst()
14683 .component_values();
14684 let simple_blocks = parse_entry_point(
14685 "{ color: red; (width >= 1px) }",
14686 StyleDialect::Css,
14687 ParseEntryPoint::SimpleBlock,
14688 )
14689 .cst()
14690 .simple_blocks();
14691 let component_value_lists = parse_entry_point(
14692 "red calc(1px + 2px)",
14693 StyleDialect::Css,
14694 ParseEntryPoint::ComponentValueList,
14695 )
14696 .cst()
14697 .component_value_lists();
14698 let comma_separated_component_value_lists = parse_entry_point(
14699 "red, calc(1px + 2px)",
14700 StyleDialect::Css,
14701 ParseEntryPoint::CommaSeparatedComponentValueList,
14702 )
14703 .cst()
14704 .comma_separated_component_value_lists();
14705 let custom_property_values = result.cst().custom_property_values();
14706 let at_rules = cst.at_rules();
14707
14708 assert_eq!(
14709 stylesheet.as_ref().map(TypedCstNode::kind),
14710 Some(SyntaxKind::Stylesheet)
14711 );
14712 assert_eq!(rules.len(), 2);
14713 assert_eq!(selectors.len(), 2);
14714 assert_eq!(declarations.len(), 3);
14715 assert_eq!(values.len(), 3);
14716 assert!(!component_values.is_empty());
14717 assert!(!simple_blocks.is_empty());
14718 assert!(!component_value_lists.is_empty());
14719 assert!(!comma_separated_component_value_lists.is_empty());
14720 assert_eq!(custom_property_values.len(), 1);
14721 assert!(!at_rules.is_empty());
14722 assert!(
14723 at_rules
14724 .iter()
14725 .any(|at_rule| at_rule.kind() == SyntaxKind::MediaRule)
14726 );
14727 assert!(
14728 stylesheet
14729 .and_then(|node| RuleCstNode::cast(node.into_syntax()))
14730 .is_none()
14731 );
14732 }
14733
14734 #[test]
14735 fn exposes_typed_bogus_cst_wrapper_slice() {
14736 let result = parse(".card { color: @; width: ?; }", StyleDialect::Css);
14737 let cst = result.cst();
14738 let bogus_kinds: Vec<SyntaxKind> =
14739 cst.bogus_nodes().iter().map(TypedCstNode::kind).collect();
14740
14741 assert!(cst.has_bogus_nodes());
14742 assert!(bogus_kinds.contains(&SyntaxKind::BogusValue));
14743 assert!(bogus_kinds.contains(&SyntaxKind::BogusToken));
14744 assert!(bogus_kinds.iter().all(|kind| kind.is_bogus()));
14745 }
14746
14747 #[test]
14748 fn consumes_parser_style_fact_names_through_typed_interner() {
14749 let db = salsa::DatabaseImpl::default();
14750 let summary = summarize_parser_semantic_name_consumption(
14751 r#"@use "./tokens" as t;
14752@mixin tone { color: $brand; }
14753.button { --brand: red; animation: fade 1s; composes: base from "./base.module.css"; }
14754@keyframes fade { from { opacity: 0; } to { opacity: 1; } }"#,
14755 StyleDialect::Scss,
14756 &db,
14757 );
14758
14759 assert_eq!(summary.product, "omena-parser.semantic-name-consumption");
14760 assert_eq!(summary.dialect, StyleDialect::Scss);
14761 assert_eq!(summary.invalid_name_count, 0);
14762 assert_eq!(summary.semantic_name_count, summary.interned_name_count);
14763 assert!(summary.class_name_count >= 2);
14764 assert!(summary.custom_property_name_count >= 1);
14765 assert!(summary.css_ident_count >= 1);
14766 assert!(summary.keyframes_name_count >= 1);
14767 assert!(summary.mixin_name_count >= 1);
14768 assert!(summary.file_path_count >= 1);
14769 assert!(
14770 summary
14771 .ready_surfaces
14772 .contains(&"parserSemanticNameConsumption")
14773 );
14774 }
14775
14776 #[test]
14777 fn summarizes_parser_cst_equivalence_contract() {
14778 let summary = summarize_parser_cst_equivalence(
14779 r#"@media (min-width: 1px) { .card { --tone: red; color: var(--tone); } }"#,
14780 StyleDialect::Css,
14781 );
14782
14783 assert_eq!(summary.product, "omena-parser.cst-equivalence");
14784 assert_eq!(summary.dialect, StyleDialect::Css);
14785 assert_eq!(summary.root_kind, SyntaxKind::Root);
14786 assert!(summary.parser_node_count > 1);
14787 assert!(summary.parser_token_count > 1);
14788 assert!(summary.typed_wrapper_count > 4);
14789 assert!(summary.source_text_round_trip_ready);
14790 assert!(summary.syntax_kind_round_trip_ready);
14791 assert!(summary.zero_unknown_kind_ready);
14792 assert!(summary.typed_cst_wrapper_ready);
14793 assert!(summary.ready_surfaces.contains(&"parserCstEquivalence"));
14794 }
14795
14796 #[test]
14797 fn summarizes_green_field_parser_boundary() {
14798 let summary = summarize_parser_boundary();
14799
14800 assert_eq!(summary.product, "omena-parser.boundary");
14801 assert_eq!(summary.dialect_count, 4);
14802 assert_eq!(summary.shared_name_kind_count, 8);
14803 assert!(summary.ready_surfaces.contains(&"selectorCstSkeleton"));
14804 assert!(summary.ready_surfaces.contains(&"lexedTokenTextSurface"));
14805 assert!(
14806 summary
14807 .ready_surfaces
14808 .contains(&"recursiveDescentParserCore")
14809 );
14810 assert!(
14811 summary
14812 .ready_surfaces
14813 .contains(&"recursiveDescentCoverageSummary")
14814 );
14815 assert!(summary.ready_surfaces.contains(&"atRuleRegistrySkeleton"));
14816 assert!(
14817 summary
14818 .ready_surfaces
14819 .contains(&"prattValueExpressionSkeleton")
14820 );
14821 assert!(summary.ready_surfaces.contains(&"prattValueParserCore"));
14822 assert!(
14823 summary
14824 .ready_surfaces
14825 .contains(&"prattValueCoverageSummary")
14826 );
14827 assert!(
14828 summary
14829 .ready_surfaces
14830 .contains(&"attributeMatcherTokenization")
14831 );
14832 assert!(summary.ready_surfaces.contains(&"attributeMatcherCstNodes"));
14833 assert!(
14834 summary
14835 .ready_surfaces
14836 .contains(&"attributeNameValueModifierCstNodes")
14837 );
14838 assert!(
14839 summary
14840 .ready_surfaces
14841 .contains(&"specializedValueFunctionCstNodes")
14842 );
14843 assert!(
14844 summary
14845 .ready_surfaces
14846 .contains(&"caseInsensitiveFunctionRegistry")
14847 );
14848 assert!(
14849 summary
14850 .ready_surfaces
14851 .contains(&"caseInsensitiveAtRuleRegistry")
14852 );
14853 assert!(summary.ready_surfaces.contains(&"identifierValueCstNodes"));
14854 assert!(summary.ready_surfaces.contains(&"stringValueCstNodes"));
14855 assert!(
14856 summary
14857 .ready_surfaces
14858 .contains(&"unicodeRangeValueCstNodes")
14859 );
14860 assert!(
14861 summary
14862 .ready_surfaces
14863 .contains(&"cssModuleScopeFunctionCstNodes")
14864 );
14865 assert!(
14866 summary
14867 .ready_surfaces
14868 .contains(&"cssModuleGlobalSelectorFactFiltering")
14869 );
14870 assert!(
14871 summary
14872 .ready_surfaces
14873 .contains(&"cssModuleLocalIdSelectorFacts")
14874 );
14875 assert!(summary.ready_surfaces.contains(&"cssModuleValueStyleFacts"));
14876 assert!(
14877 summary
14878 .ready_surfaces
14879 .contains(&"cssModuleValueDeclarationReferenceFacts")
14880 );
14881 assert!(
14882 summary
14883 .ready_surfaces
14884 .contains(&"cssModuleComposesStyleFacts")
14885 );
14886 assert!(summary.ready_surfaces.contains(&"icssStyleFacts"));
14887 assert!(summary.ready_surfaces.contains(&"animationNameStyleFacts"));
14888 assert!(
14889 summary
14890 .ready_surfaces
14891 .contains(&"animationShorthandStyleFacts")
14892 );
14893 assert!(
14894 summary
14895 .ready_surfaces
14896 .contains(&"scssStructuredBlockAtRules")
14897 );
14898 assert!(
14899 summary
14900 .ready_surfaces
14901 .contains(&"scssControlPreludeValidation")
14902 );
14903 assert!(
14904 summary
14905 .ready_surfaces
14906 .contains(&"scssControlStyleFactExtraction")
14907 );
14908 assert!(
14909 summary
14910 .ready_surfaces
14911 .contains(&"scssIncludeContentBlockStyleFacts")
14912 );
14913 assert!(summary.ready_surfaces.contains(&"scssUtilityAtRules"));
14914 assert!(summary.ready_surfaces.contains(&"scssVariableFlagCstNodes"));
14915 assert!(
14916 summary
14917 .ready_surfaces
14918 .contains(&"scssModulePreludeSourceValidation")
14919 );
14920 assert!(
14921 summary
14922 .ready_surfaces
14923 .contains(&"scssModulePreludeClauseValidation")
14924 );
14925 assert!(
14926 summary
14927 .ready_surfaces
14928 .contains(&"lessMixinDeclarationCstNodes")
14929 );
14930 assert!(summary.ready_surfaces.contains(&"lessMixinCallCstNodes"));
14931 assert!(summary.ready_surfaces.contains(&"lessMixinGuardCstNodes"));
14932 assert!(summary.ready_surfaces.contains(&"lessExtendPseudoCstNodes"));
14933 assert!(
14934 summary
14935 .ready_surfaces
14936 .contains(&"lessDetachedRulesetCstNodes")
14937 );
14938 assert!(
14939 summary
14940 .ready_surfaces
14941 .contains(&"lessNamespaceAccessCstNodes")
14942 );
14943 assert!(
14944 summary
14945 .ready_surfaces
14946 .contains(&"lessPropertyVariableTokenization")
14947 );
14948 assert!(
14949 summary
14950 .ready_surfaces
14951 .contains(&"lessPropertyVariableCstNodes")
14952 );
14953 assert!(
14954 summary
14955 .ready_surfaces
14956 .contains(&"lessEscapedStringTokenization")
14957 );
14958 assert!(
14959 summary
14960 .ready_surfaces
14961 .contains(&"lessEscapedStringValueCstNodes")
14962 );
14963 assert!(
14964 summary
14965 .ready_surfaces
14966 .contains(&"importantAnnotationTokenization")
14967 );
14968 assert!(summary.ready_surfaces.contains(&"urlTokenization"));
14969 assert!(summary.ready_surfaces.contains(&"urlValueCstNodes"));
14970 assert!(
14971 summary
14972 .ready_surfaces
14973 .contains(&"quotedUrlFunctionValueCstNodes")
14974 );
14975 assert!(
14976 summary
14977 .ready_surfaces
14978 .contains(&"conditionalAtRulePreludeCstNodes")
14979 );
14980 assert!(
14981 summary
14982 .ready_surfaces
14983 .contains(&"supportsAtRulePreludeValidation")
14984 );
14985 assert!(
14986 summary
14987 .ready_surfaces
14988 .contains(&"conditionalLevel5AtRuleCstNodes")
14989 );
14990 assert!(summary.ready_surfaces.contains(&"mediaQueryCstNodes"));
14991 assert!(summary.ready_surfaces.contains(&"mediaQueryListValidation"));
14992 assert!(summary.ready_surfaces.contains(&"importPreludeCstNodes"));
14993 assert!(
14994 summary
14995 .ready_surfaces
14996 .contains(&"importSourcePreludeValidation")
14997 );
14998 assert!(
14999 summary
15000 .ready_surfaces
15001 .contains(&"importTailPreludeValidation")
15002 );
15003 assert!(
15004 summary
15005 .ready_surfaces
15006 .contains(&"customMediaPreludeValidation")
15007 );
15008 assert!(
15009 summary
15010 .ready_surfaces
15011 .contains(&"propertyAtRuleNameValidation")
15012 );
15013 assert!(
15014 summary
15015 .ready_surfaces
15016 .contains(&"namedAtRulePreludeValidation")
15017 );
15018 assert!(
15019 summary
15020 .ready_surfaces
15021 .contains(&"containerAtRulePreludeValidation")
15022 );
15023 assert!(
15024 summary
15025 .ready_surfaces
15026 .contains(&"charsetNamespaceAtRulePreludeValidation")
15027 );
15028 assert!(
15029 summary
15030 .ready_surfaces
15031 .contains(&"keyframesAtRuleNameValidation")
15032 );
15033 assert!(
15034 summary
15035 .ready_surfaces
15036 .contains(&"emptyBlockAtRulePreludeValidation")
15037 );
15038 assert!(
15039 summary
15040 .ready_surfaces
15041 .contains(&"layerScopePreludeCstNodes")
15042 );
15043 assert!(
15044 summary
15045 .ready_surfaces
15046 .contains(&"layerAtRulePreludeValidation")
15047 );
15048 assert!(
15049 summary
15050 .ready_surfaces
15051 .contains(&"scopeAtRulePreludeValidation")
15052 );
15053 assert!(
15054 summary
15055 .ready_surfaces
15056 .contains(&"pageAtRulePreludeValidation")
15057 );
15058 assert!(summary.ready_surfaces.contains(&"pageMarginAtRuleCstNodes"));
15059 assert!(
15060 summary
15061 .ready_surfaces
15062 .contains(&"modernDeclarationAtRuleCstNodes")
15063 );
15064 assert!(
15065 summary
15066 .ready_surfaces
15067 .contains(&"fontFeatureValuesAtRuleCstNodes")
15068 );
15069 assert!(
15070 summary
15071 .ready_surfaces
15072 .contains(&"fontFeatureValuesPreludeValidation")
15073 );
15074 assert!(
15075 summary
15076 .ready_surfaces
15077 .contains(&"keyframeSelectorListValidation")
15078 );
15079 assert!(
15080 summary
15081 .ready_surfaces
15082 .contains(&"viewTransitionAtRuleCstNodes")
15083 );
15084 assert!(
15085 summary
15086 .ready_surfaces
15087 .contains(&"genericAtRulePreludeCstNodes")
15088 );
15089 assert!(
15090 summary
15091 .ready_surfaces
15092 .contains(&"bogusAtRulePreludeCstNodes")
15093 );
15094 assert!(summary.ready_surfaces.contains(&"nestingAtRuleCstNodes"));
15095 assert!(
15096 summary
15097 .ready_surfaces
15098 .contains(&"customMediaAtRuleCstNodes")
15099 );
15100 assert!(summary.ready_surfaces.contains(&"cssColorFunctionCstNodes"));
15101 assert!(
15102 summary
15103 .ready_surfaces
15104 .contains(&"colorFunctionArgumentChecks")
15105 );
15106 assert!(summary.ready_surfaces.contains(&"gradientFunctionCstNodes"));
15107 assert!(
15108 summary
15109 .ready_surfaces
15110 .contains(&"transformFunctionCstNodes")
15111 );
15112 assert!(summary.ready_surfaces.contains(&"filterFunctionCstNodes"));
15113 assert!(summary.ready_surfaces.contains(&"imageFunctionCstNodes"));
15114 assert!(summary.ready_surfaces.contains(&"shapeFunctionCstNodes"));
15115 assert!(summary.ready_surfaces.contains(&"envAttrFunctionCstNodes"));
15116 assert!(summary.ready_surfaces.contains(&"mathFunctionCstNodes"));
15117 assert!(summary.ready_surfaces.contains(&"mathFunctionArityChecks"));
15118 assert!(
15119 summary
15120 .ready_surfaces
15121 .contains(&"mathFunctionEmptyArgumentChecks")
15122 );
15123 assert!(
15124 summary
15125 .ready_surfaces
15126 .contains(&"varEnvAttrFunctionHeadChecks")
15127 );
15128 assert!(
15129 summary
15130 .ready_surfaces
15131 .contains(&"scssInterpolationTokenization")
15132 );
15133 assert!(
15134 summary
15135 .ready_surfaces
15136 .contains(&"scssInterpolationCstNodes")
15137 );
15138 assert!(
15139 summary
15140 .ready_surfaces
15141 .contains(&"lessInterpolationTokenization")
15142 );
15143 assert!(
15144 summary
15145 .ready_surfaces
15146 .contains(&"lessInterpolationCstNodes")
15147 );
15148 assert!(
15149 summary
15150 .ready_surfaces
15151 .contains(&"interpolationBogusRecovery")
15152 );
15153 assert!(summary.ready_surfaces.contains(&"unicodeRangeTokenization"));
15154 assert!(summary.ready_surfaces.contains(&"badStringTokenRecovery"));
15155 assert!(summary.ready_surfaces.contains(&"badStringValueBogusNodes"));
15156 assert!(
15157 summary
15158 .ready_surfaces
15159 .contains(&"emptyDeclarationValueRecovery")
15160 );
15161 assert!(
15162 summary
15163 .ready_surfaces
15164 .contains(&"emptyVariableValueRecovery")
15165 );
15166 assert!(
15167 summary
15168 .ready_surfaces
15169 .contains(&"missingSemicolonDeclarationRecovery")
15170 );
15171 assert!(summary.ready_surfaces.contains(&"coreBogusPopulationSlice"));
15172 assert!(
15173 summary
15174 .ready_surfaces
15175 .contains(&"dialectBogusPopulationSlice")
15176 );
15177 assert!(summary.ready_surfaces.contains(&"cssModuleValueCstNodes"));
15178 assert!(
15179 summary
15180 .ready_surfaces
15181 .contains(&"cssModuleComposesCstNodes")
15182 );
15183 assert!(summary.ready_surfaces.contains(&"icssModuleBlockCstNodes"));
15184 assert!(
15185 summary
15186 .ready_surfaces
15187 .contains(&"icssImportSourceValidation")
15188 );
15189 assert!(
15190 summary
15191 .ready_surfaces
15192 .contains(&"cssModuleFromClauseSourceValidation")
15193 );
15194 assert!(
15195 summary
15196 .ready_surfaces
15197 .contains(&"cssModuleComposesMultipleFromValidation")
15198 );
15199 assert!(
15200 summary
15201 .ready_surfaces
15202 .contains(&"cssModuleGlobalComposesValidation")
15203 );
15204 assert!(summary.ready_surfaces.contains(&"cssModuleBogusRecovery"));
15205 assert!(summary.ready_surfaces.contains(&"valueListCstNodes"));
15206 assert!(summary.ready_surfaces.contains(&"valueListBogusRecovery"));
15207 assert!(
15208 summary
15209 .ready_surfaces
15210 .contains(&"genericRecoveryBogusNodes")
15211 );
15212 assert!(
15213 summary
15214 .ready_surfaces
15215 .contains(&"lightningCssDifferentialCorpusSlice")
15216 );
15217 assert!(
15218 summary
15219 .ready_surfaces
15220 .contains(&"midTypingNoPanicPropertySlice")
15221 );
15222 assert!(
15223 summary
15224 .ready_surfaces
15225 .contains(&"deterministicPanicFreeCorpus")
15226 );
15227 assert!(
15228 summary
15229 .ready_surfaces
15230 .contains(&"losslessCstTextRoundTripSmoke")
15231 );
15232 assert!(
15233 summary
15234 .ready_surfaces
15235 .contains(&"parseResultSourceTextSurface")
15236 );
15237 assert!(
15238 summary
15239 .ready_surfaces
15240 .contains(&"parseSourceParseRoundTripSmoke")
15241 );
15242 assert!(
15243 summary
15244 .ready_surfaces
15245 .contains(&"typedNumericValueAtomCstNodes")
15246 );
15247 assert!(summary.ready_surfaces.contains(&"bracketedValueCstNodes"));
15248 assert!(
15249 summary
15250 .ready_surfaces
15251 .contains(&"importantAnnotationCstNodes")
15252 );
15253 assert!(
15254 summary
15255 .ready_surfaces
15256 .contains(&"splitImportantAnnotationCstNodes")
15257 );
15258 assert!(
15259 summary
15260 .ready_surfaces
15261 .contains(&"unexpectedValueTokenBogusNodes")
15262 );
15263 assert!(summary.ready_surfaces.contains(&"cdoCdcTokenization"));
15264 assert!(
15265 summary
15266 .ready_surfaces
15267 .contains(&"cssIdentifierEscapeTokenization")
15268 );
15269 assert!(
15270 summary
15271 .ready_surfaces
15272 .contains(&"nullAndBomInputPreprocessingSlice")
15273 );
15274 assert!(
15275 summary
15276 .ready_surfaces
15277 .contains(&"hashDelimiterTokenization")
15278 );
15279 assert!(summary.ready_surfaces.contains(&"cssDashIdentTokenization"));
15280 assert!(
15281 summary
15282 .ready_surfaces
15283 .contains(&"signedNumericTokenization")
15284 );
15285 assert!(
15286 summary
15287 .ready_surfaces
15288 .contains(&"exponentNumericTokenization")
15289 );
15290 assert!(summary.ready_surfaces.contains(&"badUrlWhitespaceRecovery"));
15291 assert!(summary.ready_surfaces.contains(&"parserEntryPointApiSlice"));
15292 assert!(
15293 summary
15294 .ready_surfaces
15295 .contains(&"ruleListEntryPointApiSlice")
15296 );
15297 assert!(
15298 summary
15299 .ready_surfaces
15300 .contains(&"componentValueEntryPointApiSlice")
15301 );
15302 assert!(
15303 summary
15304 .ready_surfaces
15305 .contains(&"componentValueListEntryPointApiSlice")
15306 );
15307 assert!(
15308 summary
15309 .ready_surfaces
15310 .contains(&"commaSeparatedComponentValueListEntryPointApiSlice")
15311 );
15312 assert!(
15313 summary
15314 .ready_surfaces
15315 .contains(&"simpleBlockEntryPointApiSlice")
15316 );
15317 assert!(summary.ready_surfaces.contains(&"typedCstWrapperSlice"));
15318 assert!(summary.ready_surfaces.contains(&"parserCstEquivalence"));
15319 assert!(
15320 summary
15321 .ready_surfaces
15322 .contains(&"typedBogusCstWrapperSlice")
15323 );
15324 assert!(summary.ready_surfaces.contains(&"componentValueCstNodes"));
15325 assert!(summary.ready_surfaces.contains(&"simpleBlockCstNodes"));
15326 assert!(summary.ready_surfaces.contains(&"fullBogusPopulation"));
15327 assert!(
15328 summary
15329 .ready_surfaces
15330 .contains(&"componentValueListCstNodes")
15331 );
15332 assert!(
15333 summary
15334 .ready_surfaces
15335 .contains(&"commaSeparatedComponentValueListCstNodes")
15336 );
15337 assert!(
15338 summary
15339 .ready_surfaces
15340 .contains(&"customPropertyAnyValueComponentList")
15341 );
15342 assert!(
15343 summary
15344 .ready_surfaces
15345 .contains(&"customPropertyValueCstNodes")
15346 );
15347 assert!(
15348 summary
15349 .ready_surfaces
15350 .contains(&"functionalPseudoSelectorListCstNodes")
15351 );
15352 assert!(
15353 summary
15354 .ready_surfaces
15355 .contains(&"strictNotPseudoSelectorListCstNodes")
15356 );
15357 assert!(
15358 summary
15359 .ready_surfaces
15360 .contains(&"nthSelectorOfSelectorListCstNodes")
15361 );
15362 assert!(
15363 summary
15364 .ready_surfaces
15365 .contains(&"nthSelectorFormulaCstNodes")
15366 );
15367 assert!(
15368 summary
15369 .ready_surfaces
15370 .contains(&"hasRelativeSelectorListCstNodes")
15371 );
15372 assert!(
15373 summary
15374 .ready_surfaces
15375 .contains(&"langDirSelectorArgumentCstNodes")
15376 );
15377 assert!(
15378 summary
15379 .ready_surfaces
15380 .contains(&"namespaceQualifiedSelectorCstNodes")
15381 );
15382 assert!(
15383 summary
15384 .ready_surfaces
15385 .contains(&"selectorFunctionArgumentFactExclusion")
15386 );
15387 assert!(
15388 summary
15389 .ready_surfaces
15390 .contains(&"missingBlockCloseBogusTrivia")
15391 );
15392 assert!(
15393 summary
15394 .ready_surfaces
15395 .contains(&"initialDialectStatementNodes")
15396 );
15397 assert!(
15398 summary
15399 .ready_surfaces
15400 .contains(&"scssNestedPropertyCstNodes")
15401 );
15402 assert!(summary.ready_surfaces.contains(&"scssModuleConfigCstNodes"));
15403 assert!(
15404 summary
15405 .ready_surfaces
15406 .contains(&"scssModuleConfigBogusRecovery")
15407 );
15408 assert!(
15409 summary
15410 .ready_surfaces
15411 .contains(&"scssPlaceholderSelectorCstNodes")
15412 );
15413 assert!(summary.ready_surfaces.contains(&"recoveryBogusSkeleton"));
15414 assert!(
15415 summary
15416 .ready_surfaces
15417 .contains(&"styleFactExtractionSurface")
15418 );
15419 assert!(
15420 summary
15421 .ready_surfaces
15422 .contains(&"parserSemanticNameConsumption")
15423 );
15424 assert!(summary.ready_surfaces.contains(&"differentialCorpus"));
15425 assert!(!summary.not_ready_surfaces.contains(&"differentialCorpus"));
15426 assert!(
15427 summary
15428 .ready_surfaces
15429 .contains(&"lightningCssSelectorIdAndAtRuleDifferentialSlice")
15430 );
15431 assert!(!summary.not_ready_surfaces.contains(&"fullPrattValueParser"));
15432 assert!(
15433 summary
15434 .not_ready_surfaces
15435 .contains(&"fullPropertyValueGrammarRegistry")
15436 );
15437 assert!(
15438 !summary
15439 .not_ready_surfaces
15440 .contains(&"fullRecursiveDescentGrammar")
15441 );
15442 assert!(
15443 summary
15444 .not_ready_surfaces
15445 .contains(&"completeExternalSpecMirror")
15446 );
15447 assert!(summary.not_ready_surfaces.contains(&"productCutover"));
15448 }
15449
15450 #[test]
15451 fn summarizes_recursive_descent_parser_coverage_without_claiming_full_spec_mirror() {
15452 let summary = summarize_recursive_descent_parser_coverage();
15453
15454 assert_eq!(summary.product, "omena-parser.recursive-descent-coverage");
15455 assert_eq!(summary.dialect_count, 4);
15456 assert_eq!(summary.entry_point_count, 10);
15457 assert!(summary.selector_surface_count >= 12);
15458 assert!(summary.at_rule_surface_count >= 19);
15459 assert!(summary.dialect_extension_surface_count >= 17);
15460 assert!(summary.recovery_surface_count >= 8);
15461 assert!(
15462 summary
15463 .ready_surfaces
15464 .contains(&"recursiveDescentParserCore")
15465 );
15466 assert!(summary.ready_surfaces.contains(&"sassIndentedBlocks"));
15467 assert!(
15468 summary
15469 .next_surfaces
15470 .contains(&"completeExternalSpecMirror")
15471 );
15472 }
15473
15474 #[test]
15475 fn summarizes_pratt_value_parser_coverage_without_overclaiming_property_grammar() {
15476 let summary = summarize_pratt_value_parser_coverage();
15477
15478 assert_eq!(summary.product, "omena-parser.pratt-value-coverage");
15479 assert!(summary.infix_operator_kinds.contains(&SyntaxKind::Plus));
15480 assert!(summary.infix_operator_kinds.contains(&SyntaxKind::Star));
15481 assert!(summary.prefix_operator_kinds.contains(&SyntaxKind::Minus));
15482 assert!(
15483 summary
15484 .value_expression_node_kinds
15485 .contains(&SyntaxKind::BinaryExpression)
15486 );
15487 assert!(
15488 summary
15489 .value_expression_node_kinds
15490 .contains(&SyntaxKind::FunctionArguments)
15491 );
15492 assert!(summary.specialized_function_family_count >= 10);
15493 assert!(summary.css_values_l4_math_function_count >= 20);
15494 assert!(summary.css_color_function_count >= 14);
15495 assert!(summary.ready_surfaces.contains(&"prattValueParserCore"));
15496 assert!(
15497 summary
15498 .next_surfaces
15499 .contains(&"fullPropertyValueGrammarRegistry")
15500 );
15501 }
15502
15503 fn char_boundary_offsets(source: &str) -> Vec<usize> {
15504 source
15505 .char_indices()
15506 .map(|(offset, _)| offset)
15507 .chain(std::iter::once(source.len()))
15508 .collect()
15509 }
15510
15511 fn deterministic_byte_fixture(seed: u32) -> Vec<u8> {
15512 let mut state = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
15513 let len = (state as usize % 96) + 1;
15514 let mut bytes = Vec::with_capacity(len);
15515 for _ in 0..len {
15516 state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
15517 bytes.push((state >> 24) as u8);
15518 }
15519 bytes
15520 }
15521
15522 fn assert_lex_ranges_are_char_boundaries(source: &str, tokens: &[LexedToken]) {
15523 for token in tokens {
15524 let start = u32::from(token.range.start()) as usize;
15525 let end = u32::from(token.range.end()) as usize;
15526 assert!(
15527 source.is_char_boundary(start),
15528 "token start is not a char boundary: token={token:?} source={source:?}"
15529 );
15530 assert!(
15531 source.is_char_boundary(end),
15532 "token end is not a char boundary: token={token:?} source={source:?}"
15533 );
15534 }
15535 }
15536
15537 fn source_text(node: &SyntaxNode<SyntaxKind>) -> Option<String> {
15538 let mut text = String::new();
15539 for token in node
15540 .descendants_with_tokens()
15541 .filter_map(|element| element.into_token())
15542 {
15543 if let Some(resolver) = token.resolver() {
15544 text.push_str(token.resolve_text(&**resolver));
15545 } else if let Some(static_text) = token.static_text() {
15546 text.push_str(static_text);
15547 } else {
15548 return None;
15549 }
15550 }
15551 Some(text)
15552 }
15553
15554 fn node_kinds(node: &SyntaxNode<SyntaxKind>) -> Vec<SyntaxKind> {
15555 let mut kinds = vec![node.kind()];
15556 for child in node.children() {
15557 kinds.extend(node_kinds(child));
15558 }
15559 kinds
15560 }
15561
15562 fn token_kinds(node: &SyntaxNode<SyntaxKind>) -> Vec<SyntaxKind> {
15563 node.descendants_with_tokens()
15564 .filter_map(|element| element.into_token().map(|token| token.kind()))
15565 .collect()
15566 }
15567}