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