1use std::collections::HashMap;
2
3use cstree::syntax::SyntaxNode;
4use omena_syntax::ident::{
5 AuthoredPropertyTextV0, CanonicalPropertyKeyV0, PropertyNameKindV0, PropertyNameV0,
6};
7use omena_syntax::{SyntaxKind, css_keyword};
8
9use crate::{ParseResult, ParserByteSpanV0, StyleDialect, is_at_rule_node_kind, parse};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct ParserDeclarationSelectorContextV0 {
18 pub reset_to_root: bool,
19 pub selector_members: Vec<String>,
20}
21
22#[derive(Debug, Clone)]
24pub struct ParserDeclarationSyntaxFactV0 {
25 pub byte_span: ParserByteSpanV0,
26 pub property_name: AuthoredPropertyTextV0,
28 pub property_key: CanonicalPropertyKeyV0,
30 pub value_span: ParserByteSpanV0,
31 pub value_text: String,
32 pub important: bool,
33 pub selector_contexts: Vec<ParserDeclarationSelectorContextV0>,
34 pub condition_contexts: Vec<String>,
35 pub source_order: usize,
36}
37
38impl PartialEq for ParserDeclarationSyntaxFactV0 {
39 fn eq(&self, other: &Self) -> bool {
40 self.byte_span == other.byte_span
41 && self.property_key == other.property_key
42 && self.value_span == other.value_span
43 && self.value_text == other.value_text
44 && self.important == other.important
45 && self.selector_contexts == other.selector_contexts
46 && self.condition_contexts == other.condition_contexts
47 && self.source_order == other.source_order
48 }
49}
50
51impl Eq for ParserDeclarationSyntaxFactV0 {}
52
53#[derive(Default)]
54struct DeclarationContextCache {
55 selector_contexts: HashMap<(usize, usize), Option<ParserDeclarationSelectorContextV0>>,
56 condition_contexts: HashMap<(usize, usize), Option<String>>,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60struct CssModuleValueSyntaxV0 {
61 span: ParserByteSpanV0,
62 value_span: Option<ParserByteSpanV0>,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Default)]
66pub struct ProductSyntaxIndexV0 {
67 css_module_values: Vec<CssModuleValueSyntaxV0>,
68 scss_forward_rules: Vec<ParserByteSpanV0>,
69 keyframes_rules: Vec<ParserByteSpanV0>,
70 declarations: Vec<ParserDeclarationSyntaxFactV0>,
71 sass_parameter_lists: Vec<ParserByteSpanV0>,
72}
73
74impl ProductSyntaxIndexV0 {
75 pub fn new(source: &str, parsed: &ParseResult) -> Self {
76 let mut index = Self::default();
77 let mut declaration_source_order = 0usize;
78 let mut declaration_context_cache = DeclarationContextCache::default();
79 for node in parsed.syntax().descendants() {
80 match node.kind() {
81 SyntaxKind::CssModuleExportBlock | SyntaxKind::CssModuleImportBlock => {
82 index.css_module_values.push(CssModuleValueSyntaxV0 {
83 span: node_span(node),
84 value_span: value_span_after_colon(node),
85 });
86 }
87 SyntaxKind::ScssForwardRule => {
88 index.scss_forward_rules.push(node_span(node));
89 }
90 SyntaxKind::KeyframesRule => {
91 index.keyframes_rules.push(node_span(node));
92 }
93 SyntaxKind::Declaration | SyntaxKind::CustomPropertyDeclaration => {
94 if let Some(declaration) = declaration_syntax(
95 source,
96 node,
97 declaration_source_order,
98 &mut declaration_context_cache,
99 ) {
100 index.declarations.push(declaration);
101 declaration_source_order = declaration_source_order.saturating_add(1);
102 }
103 }
104 SyntaxKind::ScssMixinDeclaration | SyntaxKind::ScssFunctionDeclaration => {
105 if let Some(span) = parameter_list_span(node) {
106 index.sass_parameter_lists.push(span);
107 }
108 }
109 _ => {}
110 }
111 }
112 index
113 }
114
115 pub fn declarations(&self) -> &[ParserDeclarationSyntaxFactV0] {
116 self.declarations.as_slice()
117 }
118
119 pub fn declarations_from_parse(
122 source: &str,
123 parsed: &ParseResult,
124 ) -> Vec<ParserDeclarationSyntaxFactV0> {
125 let mut declarations = Vec::new();
126 let root = parsed.syntax();
127 collect_declarations_from_subtree(
128 source,
129 &root,
130 &mut Vec::new(),
131 &mut Vec::new(),
132 &mut declarations,
133 );
134 declarations
135 }
136
137 pub fn into_declarations(self) -> Vec<ParserDeclarationSyntaxFactV0> {
140 self.declarations
141 }
142
143 pub(super) fn css_module_value_span_for_offset(
144 &self,
145 offset: usize,
146 ) -> Option<ParserByteSpanV0> {
147 containing_span(
148 self.css_module_values
149 .iter()
150 .map(|definition| definition.span),
151 offset,
152 )
153 }
154
155 pub(super) fn css_module_value_text(&self, source: &str, offset: usize) -> Option<String> {
156 self.css_module_values
157 .iter()
158 .filter(|definition| span_contains_offset(definition.span, offset))
159 .min_by_key(|definition| span_len(definition.span))
160 .and_then(|definition| definition.value_span)
161 .and_then(|span| source.get(span.start..span.end))
162 .map(str::trim)
163 .map(ToString::to_string)
164 }
165
166 pub(super) fn scss_forward_span_for_offset(&self, offset: usize) -> Option<ParserByteSpanV0> {
167 containing_span(self.scss_forward_rules.iter().copied(), offset)
168 }
169
170 pub(super) fn keyframes_span_for_offset(&self, offset: usize) -> Option<ParserByteSpanV0> {
171 containing_span(self.keyframes_rules.iter().copied(), offset)
172 }
173
174 pub(super) fn declaration_span_for_offset(&self, offset: usize) -> Option<ParserByteSpanV0> {
175 containing_span(
176 self.declarations
177 .iter()
178 .map(|declaration| declaration.byte_span),
179 offset,
180 )
181 }
182
183 pub(super) fn declaration_property_key_for_offset(
184 &self,
185 offset: usize,
186 ) -> Option<CanonicalPropertyKeyV0> {
187 self.declaration_for_offset(offset)
188 .map(|declaration| declaration.property_key.clone())
189 }
190
191 pub(super) fn declaration_value_text(&self, source: &str, offset: usize) -> Option<String> {
192 let declaration = self.declaration_for_offset(offset)?;
193 source
194 .get(declaration.value_span.start..declaration.value_span.end)
195 .map(str::trim)
196 .map(ToString::to_string)
197 }
198
199 pub(super) fn sass_parameter_list_contains(&self, offset: usize) -> bool {
200 self.sass_parameter_lists
201 .iter()
202 .any(|span| span_contains_offset(*span, offset))
203 }
204
205 fn declaration_for_offset(&self, offset: usize) -> Option<&ParserDeclarationSyntaxFactV0> {
206 self.declarations
207 .iter()
208 .filter(|declaration| span_contains_offset(declaration.byte_span, offset))
209 .min_by_key(|declaration| span_len(declaration.byte_span))
210 }
211}
212
213pub fn collect_parser_declaration_syntax_facts(
217 source: &str,
218 dialect: StyleDialect,
219) -> Vec<ParserDeclarationSyntaxFactV0> {
220 let parsed = parse(source, dialect);
221 ProductSyntaxIndexV0::declarations_from_parse(source, &parsed)
222}
223
224fn parameter_list_span(node: &SyntaxNode<SyntaxKind>) -> Option<ParserByteSpanV0> {
225 let mut depth = 0usize;
226 let mut start = None;
227 for token in node
228 .descendants_with_tokens()
229 .filter_map(|element| element.into_token())
230 {
231 let span = byte_span(token.text_range());
232 match token.kind() {
233 SyntaxKind::LeftParen => {
234 depth = depth.saturating_add(1);
235 if depth == 1 {
236 start = Some(span.end);
237 }
238 }
239 SyntaxKind::RightParen if depth == 1 => {
240 return start.map(|start| ParserByteSpanV0 {
241 start,
242 end: span.start,
243 });
244 }
245 SyntaxKind::RightParen => depth = depth.saturating_sub(1),
246 SyntaxKind::LeftBrace if depth == 0 => return None,
247 _ => {}
248 }
249 }
250 None
251}
252
253fn declaration_syntax(
254 source: &str,
255 node: &SyntaxNode<SyntaxKind>,
256 source_order: usize,
257 context_cache: &mut DeclarationContextCache,
258) -> Option<ParserDeclarationSyntaxFactV0> {
259 let (selector_contexts, condition_contexts) = declaration_contexts(source, node, context_cache);
260 declaration_syntax_with_context(
261 source,
262 node,
263 source_order,
264 selector_contexts,
265 condition_contexts,
266 )
267}
268
269fn declaration_syntax_with_context(
270 source: &str,
271 node: &SyntaxNode<SyntaxKind>,
272 source_order: usize,
273 selector_contexts: Vec<ParserDeclarationSelectorContextV0>,
274 condition_contexts: Vec<String>,
275) -> Option<ParserDeclarationSyntaxFactV0> {
276 let mut declaration_span = node_span(node);
277 let declaration_end = declaration_span.end;
278 let mut colon = None;
279 let mut value_end = None;
280 let mut important_start = None;
281 let mut property_name = String::new();
282 let mut property_is_custom = node.kind() == SyntaxKind::CustomPropertyDeclaration;
283 let mut value_text = String::new();
284 for token in node
285 .descendants_with_tokens()
286 .filter_map(|element| element.into_token())
287 {
288 let token_span = byte_span(token.text_range());
289 if token.parent().kind() == SyntaxKind::ImportantAnnotation {
290 important_start.get_or_insert(token_span.start);
291 }
292 if colon.is_none() && token.kind() == SyntaxKind::Colon {
293 colon = Some(token_span);
294 continue;
295 }
296 if colon.is_some()
297 && value_end.is_none()
298 && matches!(
299 token.kind(),
300 SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon
301 )
302 {
303 value_end = Some(token_span.start);
304 }
305 if token.kind() == SyntaxKind::Semicolon {
306 declaration_span.end = declaration_span.end.min(token_span.start);
307 }
308
309 let target = if colon.is_none() {
310 property_is_custom |= token.kind() == SyntaxKind::CustomPropertyName;
311 Some(&mut property_name)
312 } else if value_end.is_none() && important_start.is_none() {
313 Some(&mut value_text)
314 } else {
315 None
316 };
317 if let Some(target) = target {
318 append_normalized_declaration_token(source, token.kind(), token_span, target);
319 }
320 }
321 let colon = colon?;
322 let value_span = ParserByteSpanV0 {
323 start: colon.end,
324 end: value_end
325 .unwrap_or(declaration_end)
326 .min(important_start.unwrap_or(usize::MAX)),
327 };
328 let property = PropertyNameV0::new(
329 property_name,
330 if property_is_custom {
331 PropertyNameKindV0::Custom
332 } else {
333 PropertyNameKindV0::Standard
334 },
335 );
336 let property_name = property.authored_text();
337 let property_key = property.canonical_key();
338 let value_text = value_text.trim().to_string();
339 (!property_name.is_empty()).then_some(ParserDeclarationSyntaxFactV0 {
340 byte_span: declaration_span,
341 property_name,
342 property_key,
343 value_span,
344 value_text,
345 important: important_start.is_some(),
346 selector_contexts,
347 condition_contexts,
348 source_order,
349 })
350}
351
352fn collect_declarations_from_subtree(
353 source: &str,
354 node: &SyntaxNode<SyntaxKind>,
355 selector_contexts: &mut Vec<ParserDeclarationSelectorContextV0>,
356 condition_contexts: &mut Vec<String>,
357 declarations: &mut Vec<ParserDeclarationSyntaxFactV0>,
358) {
359 if matches!(
360 node.kind(),
361 SyntaxKind::Declaration | SyntaxKind::CustomPropertyDeclaration
362 ) {
363 if let Some(declaration) = declaration_syntax_with_context(
364 source,
365 node,
366 declarations.len(),
367 selector_contexts.clone(),
368 condition_contexts.clone(),
369 ) {
370 declarations.push(declaration);
371 }
372 return;
373 }
374
375 let selector_pushed = selector_context_for_node(source, node).is_some_and(|context| {
376 selector_contexts.push(context);
377 true
378 });
379 let condition_context = condition_context_for_node(source, node);
380 let condition_pushed = condition_context
381 .filter(|context| condition_contexts.last() != Some(context))
382 .is_some_and(|context| {
383 condition_contexts.push(context);
384 true
385 });
386
387 for child in node.children() {
388 collect_declarations_from_subtree(
389 source,
390 child,
391 selector_contexts,
392 condition_contexts,
393 declarations,
394 );
395 }
396
397 if condition_pushed {
398 condition_contexts.pop();
399 }
400 if selector_pushed {
401 selector_contexts.pop();
402 }
403}
404
405fn selector_context_for_node(
406 source: &str,
407 node: &SyntaxNode<SyntaxKind>,
408) -> Option<ParserDeclarationSelectorContextV0> {
409 let (reset_to_root, selector_members) = match node.kind() {
410 SyntaxKind::Rule | SyntaxKind::NestRule => (false, selector_members_for_rule(source, node)),
411 SyntaxKind::ScssAtRootRule => (true, at_root_selector_members(source, node)),
412 _ => return None,
413 };
414 (!selector_members.is_empty()).then_some(ParserDeclarationSelectorContextV0 {
415 reset_to_root,
416 selector_members,
417 })
418}
419
420fn condition_context_for_node(source: &str, node: &SyntaxNode<SyntaxKind>) -> Option<String> {
421 if !is_at_rule_node_kind(node.kind())
422 || matches!(
423 node.kind(),
424 SyntaxKind::LayerRule | SyntaxKind::ScssAtRootRule | SyntaxKind::NestRule
425 )
426 {
427 return None;
428 }
429 block_header_text(source, node)
430 .map(|header| header.split_whitespace().collect::<Vec<_>>().join(" "))
431 .filter(|header| !is_non_condition_wrapper_header(header))
432 .filter(|header| !header.is_empty())
433}
434
435fn declaration_contexts(
436 source: &str,
437 node: &SyntaxNode<SyntaxKind>,
438 cache: &mut DeclarationContextCache,
439) -> (Vec<ParserDeclarationSelectorContextV0>, Vec<String>) {
440 let mut ancestors = node.ancestors().skip(1).collect::<Vec<_>>();
441 ancestors.reverse();
442 let mut selector_contexts = Vec::new();
443 let mut condition_contexts = Vec::new();
444 for ancestor in ancestors {
445 let span = node_span(ancestor);
446 let key = (span.start, span.end);
447 if matches!(
448 ancestor.kind(),
449 SyntaxKind::Rule | SyntaxKind::NestRule | SyntaxKind::ScssAtRootRule
450 ) {
451 let context = cache
452 .selector_contexts
453 .entry(key)
454 .or_insert_with(|| match ancestor.kind() {
455 SyntaxKind::Rule | SyntaxKind::NestRule => {
456 let selector_members = selector_members_for_rule(source, ancestor);
457 (!selector_members.is_empty()).then_some(
458 ParserDeclarationSelectorContextV0 {
459 reset_to_root: false,
460 selector_members,
461 },
462 )
463 }
464 SyntaxKind::ScssAtRootRule => {
465 let selector_members = at_root_selector_members(source, ancestor);
466 (!selector_members.is_empty()).then_some(
467 ParserDeclarationSelectorContextV0 {
468 reset_to_root: true,
469 selector_members,
470 },
471 )
472 }
473 _ => None,
474 })
475 .clone();
476 if let Some(context) = context {
477 selector_contexts.push(context);
478 }
479 }
480 if is_at_rule_node_kind(ancestor.kind())
481 && !matches!(
482 ancestor.kind(),
483 SyntaxKind::LayerRule | SyntaxKind::ScssAtRootRule | SyntaxKind::NestRule
484 )
485 {
486 let context = cache
487 .condition_contexts
488 .entry(key)
489 .or_insert_with(|| {
490 block_header_text(source, ancestor)
491 .map(|header| header.split_whitespace().collect::<Vec<_>>().join(" "))
492 .filter(|header| !is_non_condition_wrapper_header(header))
493 .filter(|header| !header.is_empty())
494 })
495 .clone();
496 if let Some(context) = context
497 && condition_contexts.last() != Some(&context)
498 {
499 condition_contexts.push(context);
500 }
501 }
502 }
503 (selector_contexts, condition_contexts)
504}
505
506fn selector_members_for_rule(source: &str, node: &SyntaxNode<SyntaxKind>) -> Vec<String> {
507 let Some(selector_list) = node.children().find(|child| {
508 matches!(
509 child.kind(),
510 SyntaxKind::SelectorList
511 | SyntaxKind::RelativeSelectorList
512 | SyntaxKind::BogusSelectorList
513 )
514 }) else {
515 return Vec::new();
516 };
517 selector_list
518 .children()
519 .filter(|child| {
520 matches!(
521 child.kind(),
522 SyntaxKind::Selector | SyntaxKind::RelativeSelector | SyntaxKind::BogusSelector
523 )
524 })
525 .filter_map(|selector| source_text_for_node(source, selector))
526 .map(|selector| selector.trim().to_string())
527 .filter(|selector| !selector.is_empty())
528 .collect()
529}
530
531fn at_root_selector_members(source: &str, node: &SyntaxNode<SyntaxKind>) -> Vec<String> {
532 let Some(header) = block_header_text(source, node) else {
533 return Vec::new();
534 };
535 let Some(rest) = css_keyword(header.trim_start()).strip_prefix("@at-root") else {
536 return Vec::new();
537 };
538 if let Some(next) = rest.chars().next()
539 && !next.is_ascii_whitespace()
540 {
541 return Vec::new();
542 }
543 let selector = rest.trim();
544 if selector.is_empty() || selector.starts_with('(') {
545 Vec::new()
546 } else {
547 vec![selector.to_string()]
548 }
549}
550
551fn is_non_condition_wrapper_header(header: &str) -> bool {
552 ["@layer", "@at-root", "@nest"]
553 .into_iter()
554 .any(|keyword| at_rule_header_has_keyword(header, keyword))
555}
556
557fn at_rule_header_has_keyword(header: &str, keyword: &str) -> bool {
558 let Some(rest) = css_keyword(header.trim_start()).strip_prefix(keyword) else {
559 return false;
560 };
561 rest.is_empty() || rest.chars().next().is_some_and(char::is_whitespace)
562}
563
564fn append_normalized_declaration_token(
565 source: &str,
566 kind: SyntaxKind,
567 span: ParserByteSpanV0,
568 target: &mut String,
569) {
570 if matches!(
571 kind,
572 SyntaxKind::BlockComment | SyntaxKind::LineComment | SyntaxKind::ScssSilentComment
573 ) {
574 if !target.ends_with(char::is_whitespace) {
575 target.push(' ');
576 }
577 } else if let Some(token_text) = source.get(span.start..span.end) {
578 target.push_str(token_text);
579 }
580}
581
582fn block_header_text<'a>(source: &'a str, node: &SyntaxNode<SyntaxKind>) -> Option<&'a str> {
583 let open = node
584 .descendants_with_tokens()
585 .filter_map(|element| element.into_token())
586 .find(|token| token.kind() == SyntaxKind::LeftBrace)
587 .map(|token| byte_span(token.text_range()))?;
588 source.get(node_span(node).start..open.start)
589}
590
591fn source_text_for_node<'a>(source: &'a str, node: &SyntaxNode<SyntaxKind>) -> Option<&'a str> {
592 let span = node_span(node);
593 source.get(span.start..span.end)
594}
595
596fn value_span_after_colon(node: &SyntaxNode<SyntaxKind>) -> Option<ParserByteSpanV0> {
597 let mut colon_end = None;
598 let mut value_end = None;
599 for token in node
600 .descendants_with_tokens()
601 .filter_map(|element| element.into_token())
602 {
603 let span = byte_span(token.text_range());
604 if colon_end.is_none() && token.kind() == SyntaxKind::Colon {
605 colon_end = Some(span.end);
606 continue;
607 }
608 if colon_end.is_some()
609 && matches!(
610 token.kind(),
611 SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon
612 )
613 {
614 value_end = Some(span.start);
615 break;
616 }
617 }
618 let start = colon_end?;
619 let end = value_end.unwrap_or_else(|| node_span(node).end);
620 (start <= end).then_some(ParserByteSpanV0 { start, end })
621}
622
623fn containing_span(
624 spans: impl Iterator<Item = ParserByteSpanV0>,
625 offset: usize,
626) -> Option<ParserByteSpanV0> {
627 spans
628 .filter(|span| span_contains_offset(*span, offset))
629 .min_by_key(|span| span_len(*span))
630}
631
632fn span_contains_offset(span: ParserByteSpanV0, offset: usize) -> bool {
633 span.start <= offset && offset < span.end
634}
635
636fn span_len(span: ParserByteSpanV0) -> usize {
637 span.end.saturating_sub(span.start)
638}
639
640fn node_span(node: &SyntaxNode<SyntaxKind>) -> ParserByteSpanV0 {
641 byte_span(node.text_range())
642}
643
644fn byte_span(range: cstree::text::TextRange) -> ParserByteSpanV0 {
645 ParserByteSpanV0 {
646 start: u32::from(range.start()) as usize,
647 end: u32::from(range.end()) as usize,
648 }
649}