omena_parser/facts/
emission_selectors.rs1use cstree::text::TextRange;
4use omena_syntax::SyntaxKind;
5
6use crate::ParseResult;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9#[non_exhaustive]
10pub enum ParsedEmissionSelectorFactKindV0 {
11 Element,
12 Attribute,
13 Universal,
14 PseudoClass,
15 PseudoElement,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[non_exhaustive]
20pub struct ParsedEmissionSelectorFactV0 {
21 pub kind: ParsedEmissionSelectorFactKindV0,
22 pub name: String,
23 pub range: TextRange,
24}
25
26#[derive(Debug, Clone, Default, PartialEq, Eq)]
27#[non_exhaustive]
28pub struct ParsedEmissionSelectorFactsV0 {
29 pub selectors: Vec<ParsedEmissionSelectorFactV0>,
30}
31
32pub fn collect_emission_selector_facts_from_cst(
33 text: &str,
34 parsed: &ParseResult,
35) -> ParsedEmissionSelectorFactsV0 {
36 let mut selectors = parsed
37 .syntax()
38 .descendants()
39 .filter_map(|node| {
40 let kind = match node.kind() {
41 SyntaxKind::TypeSelector => ParsedEmissionSelectorFactKindV0::Element,
42 SyntaxKind::AttributeSelector => ParsedEmissionSelectorFactKindV0::Attribute,
43 SyntaxKind::UniversalSelector => ParsedEmissionSelectorFactKindV0::Universal,
44 SyntaxKind::PseudoClassSelector => ParsedEmissionSelectorFactKindV0::PseudoClass,
45 SyntaxKind::PseudoElementSelector => {
46 ParsedEmissionSelectorFactKindV0::PseudoElement
47 }
48 _ => return None,
49 };
50 let range = node.text_range();
51 let start = u32::from(range.start()) as usize;
52 let end = u32::from(range.end()) as usize;
53 Some(ParsedEmissionSelectorFactV0 {
54 kind,
55 name: text.get(start..end).unwrap_or_default().to_string(),
56 range,
57 })
58 })
59 .collect::<Vec<_>>();
60 selectors.sort_by_key(|selector| {
61 (
62 u32::from(selector.range.start()),
63 u32::from(selector.range.end()),
64 selector.kind,
65 selector.name.clone(),
66 )
67 });
68 ParsedEmissionSelectorFactsV0 { selectors }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::{ParsedEmissionSelectorFactKindV0, collect_emission_selector_facts_from_cst};
74 use crate::{
75 StyleDialect, collect_style_fact_collection, parse, with_omena_parser_parse_instrumentation,
76 };
77
78 #[test]
79 fn collects_structural_selectors_absent_from_the_named_selector_surface() {
80 let source = "div, [data-state='open'], svg|*, :root, ::before { color: red; }";
81 let parsed = parse(source, StyleDialect::Css);
82 let facts = collect_emission_selector_facts_from_cst(source, &parsed);
83
84 assert_eq!(
85 facts
86 .selectors
87 .iter()
88 .map(|selector| (selector.kind, selector.name.as_str()))
89 .collect::<Vec<_>>(),
90 vec![
91 (ParsedEmissionSelectorFactKindV0::Element, "div"),
92 (
93 ParsedEmissionSelectorFactKindV0::Attribute,
94 "[data-state='open']"
95 ),
96 (ParsedEmissionSelectorFactKindV0::Universal, "svg|*"),
97 (ParsedEmissionSelectorFactKindV0::PseudoClass, ":root"),
98 (ParsedEmissionSelectorFactKindV0::PseudoElement, "::before"),
99 ]
100 );
101 }
102
103 #[test]
104 fn combined_fact_collection_parses_once() {
105 let (collection, snapshot) = with_omena_parser_parse_instrumentation(|| {
106 collect_style_fact_collection("div { color: red; }", StyleDialect::Css)
107 });
108
109 assert_eq!(snapshot.parse_invocation_count, 1);
110 assert!(collection.facts.selectors.is_empty());
111 assert_eq!(collection.emission_selectors.selectors.len(), 1);
112 }
113}