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