Skip to main content

omena_semantic/
layer_tree.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use omena_cascade::LayerOrdinal;
4use omena_parser::ParsedCst;
5use omena_syntax::{SyntaxKind, SyntaxNode, css_keyword};
6
7use crate::{ParserByteSpanV0, StyleLayerBlockBindingV0, StyleLayerIndexV0, StyleLayerOrderNodeV0};
8
9/// Result of resolving a source span against the canonical layer topology.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum LayerBindingResolutionV0 {
12    /// `Some` identifies a layered declaration; `None` is unlayered.
13    Resolved(Option<LayerOrdinal>),
14    /// Existing bindings cannot prove a complete layer order.
15    TopologyIncomplete { unresolved_count: usize },
16}
17
18/// Resolves the innermost layer block containing a source span.
19pub fn layer_ordinal_for_byte_span(
20    layer_index: &StyleLayerIndexV0,
21    span_start: usize,
22    span_end: usize,
23) -> LayerBindingResolutionV0 {
24    if !layer_index.topology_complete {
25        return LayerBindingResolutionV0::TopologyIncomplete {
26            unresolved_count: layer_index.unresolved_topology_count,
27        };
28    }
29
30    let ordinal = layer_index
31        .block_bindings
32        .iter()
33        .filter(|binding| {
34            binding.byte_span.start <= span_start && span_end <= binding.byte_span.end
35        })
36        .max_by_key(|binding| binding.nesting_depth)
37        .map(|binding| i32::try_from(binding.cascade_rank).unwrap_or(i32::MAX - 1))
38        .and_then(LayerOrdinal::new);
39    LayerBindingResolutionV0::Resolved(ordinal)
40}
41
42pub(crate) struct LayerOrderFactsV0 {
43    pub(crate) order_nodes: Vec<StyleLayerOrderNodeV0>,
44    pub(crate) block_bindings: Vec<StyleLayerBlockBindingV0>,
45    pub(crate) unresolved_topology_count: usize,
46    pub(crate) topology_complete: bool,
47}
48
49#[derive(Clone)]
50struct LayerBlockDraftV0 {
51    context_id: String,
52    node: SyntaxNode,
53    local_path: Option<String>,
54    canonical_name: Option<String>,
55}
56
57#[derive(Clone)]
58struct LayerNodeDraftV0 {
59    canonical_name: String,
60    local_name: String,
61    parent_name: Option<String>,
62    first_source_order: usize,
63    implicit_prefix: bool,
64}
65
66pub(crate) fn summarize_layer_order_from_cst(_source: &str, cst: &ParsedCst) -> LayerOrderFactsV0 {
67    let mut all_context_order = 0usize;
68    let mut blocks = Vec::<LayerBlockDraftV0>::new();
69    for node in cst.root().descendants().filter(|node| {
70        matches!(
71            node.kind(),
72            SyntaxKind::LayerRule | SyntaxKind::ContainerRule | SyntaxKind::ScopeRule
73        ) && node_has_block(node)
74    }) {
75        if node.kind() == SyntaxKind::LayerRule {
76            let names = layer_names(node);
77            blocks.push(LayerBlockDraftV0 {
78                context_id: format!("layer:{all_context_order}"),
79                node: node.clone(),
80                local_path: (names.len() == 1).then(|| names[0].clone()),
81                canonical_name: None,
82            });
83        }
84        all_context_order = all_context_order.saturating_add(1);
85    }
86
87    blocks.sort_by_key(|block| {
88        let range = block.node.text_range();
89        (
90            u32::from(range.start()) as usize,
91            usize::MAX.saturating_sub(u32::from(range.end()) as usize),
92        )
93    });
94
95    let mut unresolved_topology_count = 0usize;
96    for index in 0..blocks.len() {
97        let parent = nearest_enclosing_block(index, blocks.as_slice());
98        let parent_name = parent.and_then(|parent| blocks[parent].canonical_name.as_deref());
99        let Some(local_path) = blocks[index].local_path.as_deref() else {
100            unresolved_topology_count = unresolved_topology_count.saturating_add(1);
101            continue;
102        };
103        if parent.is_some() && parent_name.is_none() {
104            unresolved_topology_count = unresolved_topology_count.saturating_add(1);
105            continue;
106        }
107        blocks[index].canonical_name = canonical_layer_path(parent_name, local_path);
108        if blocks[index].canonical_name.is_none() {
109            unresolved_topology_count = unresolved_topology_count.saturating_add(1);
110        }
111    }
112
113    let mut events = cst
114        .root()
115        .descendants()
116        .filter(|node| node.kind() == SyntaxKind::LayerRule)
117        .collect::<Vec<_>>();
118    events.sort_by_key(|node| u32::from(node.text_range().start()) as usize);
119
120    let mut nodes = BTreeMap::<String, LayerNodeDraftV0>::new();
121    let mut source_order = 0usize;
122    for event in events {
123        let parent = nearest_enclosing_block_for_node(event, blocks.as_slice());
124        if parent.is_some_and(|block| block.canonical_name.is_none()) {
125            unresolved_topology_count = unresolved_topology_count.saturating_add(1);
126            continue;
127        }
128        let parent_name = parent.and_then(|block| block.canonical_name.clone());
129        let names = layer_names(event);
130        let has_block = node_has_block(event);
131        if names.is_empty() {
132            if !has_block {
133                unresolved_topology_count = unresolved_topology_count.saturating_add(1);
134            }
135            continue;
136        }
137        if has_block && names.len() != 1 {
138            continue;
139        }
140        for name in names {
141            let Some(canonical_name) = canonical_layer_path(parent_name.as_deref(), name.as_str())
142            else {
143                unresolved_topology_count = unresolved_topology_count.saturating_add(1);
144                continue;
145            };
146            register_layer_path(&mut nodes, canonical_name.as_str(), source_order);
147            source_order = source_order.saturating_add(1);
148        }
149    }
150
151    let ranks = cascade_ranks(nodes.values());
152    let mut order_nodes = nodes
153        .into_values()
154        .map(|node| StyleLayerOrderNodeV0 {
155            cascade_rank: ranks
156                .get(node.canonical_name.as_str())
157                .copied()
158                .unwrap_or(0),
159            nesting_depth: node.canonical_name.split('.').count().saturating_sub(1),
160            canonical_name: node.canonical_name,
161            local_name: node.local_name,
162            parent_name: node.parent_name,
163            first_source_order: node.first_source_order,
164            implicit_prefix: node.implicit_prefix,
165        })
166        .collect::<Vec<_>>();
167    order_nodes.sort_by_key(|node| node.cascade_rank);
168
169    let mut block_bindings = blocks
170        .iter()
171        .filter_map(|block| {
172            let canonical_name = block.canonical_name.as_ref()?;
173            let range = block.node.text_range();
174            Some(StyleLayerBlockBindingV0 {
175                context_id: block.context_id.clone(),
176                canonical_name: canonical_name.clone(),
177                cascade_rank: ranks.get(canonical_name.as_str()).copied().unwrap_or(0),
178                nesting_depth: canonical_name.split('.').count().saturating_sub(1),
179                byte_span: ParserByteSpanV0 {
180                    start: u32::from(range.start()) as usize,
181                    end: u32::from(range.end()) as usize,
182                },
183            })
184        })
185        .collect::<Vec<_>>();
186    block_bindings.sort_by_key(|binding| (binding.byte_span.start, binding.byte_span.end));
187
188    LayerOrderFactsV0 {
189        topology_complete: unresolved_topology_count == 0,
190        order_nodes,
191        block_bindings,
192        unresolved_topology_count,
193    }
194}
195
196fn nearest_enclosing_block(index: usize, blocks: &[LayerBlockDraftV0]) -> Option<usize> {
197    let range = blocks[index].node.text_range();
198    blocks
199        .iter()
200        .enumerate()
201        .filter(|(candidate_index, candidate)| {
202            *candidate_index != index
203                && candidate.node.text_range().start() < range.start()
204                && range.end() < candidate.node.text_range().end()
205        })
206        .min_by_key(|(_, candidate)| {
207            u32::from(candidate.node.text_range().end())
208                .saturating_sub(u32::from(candidate.node.text_range().start()))
209        })
210        .map(|(candidate_index, _)| candidate_index)
211}
212
213fn nearest_enclosing_block_for_node<'a>(
214    node: &SyntaxNode,
215    blocks: &'a [LayerBlockDraftV0],
216) -> Option<&'a LayerBlockDraftV0> {
217    let range = node.text_range();
218    blocks
219        .iter()
220        .filter(|block| {
221            block.node.text_range() != range
222                && block.node.text_range().start() < range.start()
223                && range.end() < block.node.text_range().end()
224        })
225        .min_by_key(|block| {
226            u32::from(block.node.text_range().end())
227                .saturating_sub(u32::from(block.node.text_range().start()))
228        })
229}
230
231fn canonical_layer_path(parent: Option<&str>, local_path: &str) -> Option<String> {
232    let local_path = local_path.trim();
233    if !plain_layer_path(local_path) {
234        return None;
235    }
236    Some(match parent {
237        Some(parent) => format!("{parent}.{local_path}"),
238        None => local_path.to_string(),
239    })
240}
241
242fn plain_layer_path(path: &str) -> bool {
243    path.split('.').all(|segment| {
244        !segment.is_empty()
245            && segment
246                .bytes()
247                .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))
248    })
249}
250
251fn register_layer_path(
252    nodes: &mut BTreeMap<String, LayerNodeDraftV0>,
253    canonical_name: &str,
254    source_order: usize,
255) {
256    let segments = canonical_name.split('.').collect::<Vec<_>>();
257    for length in 1..=segments.len() {
258        let name = segments[..length].join(".");
259        let parent_name = (length > 1).then(|| segments[..length - 1].join("."));
260        let implicit_prefix = length != segments.len();
261        nodes.entry(name.clone()).or_insert(LayerNodeDraftV0 {
262            canonical_name: name,
263            local_name: segments[length - 1].to_string(),
264            parent_name,
265            first_source_order: source_order,
266            implicit_prefix,
267        });
268    }
269}
270
271fn cascade_ranks<'a>(nodes: impl Iterator<Item = &'a LayerNodeDraftV0>) -> BTreeMap<String, usize> {
272    let nodes = nodes
273        .map(|node| (node.canonical_name.clone(), node.clone()))
274        .collect::<BTreeMap<_, _>>();
275    let mut children = BTreeMap::<Option<String>, Vec<String>>::new();
276    for node in nodes.values() {
277        children
278            .entry(node.parent_name.clone())
279            .or_default()
280            .push(node.canonical_name.clone());
281    }
282    for names in children.values_mut() {
283        names.sort_by_key(|name| {
284            nodes
285                .get(name)
286                .map(|node| (node.first_source_order, node.canonical_name.clone()))
287                .unwrap_or((usize::MAX, name.clone()))
288        });
289    }
290
291    let mut ordered = Vec::new();
292    append_postorder(None, &children, &mut ordered, &mut BTreeSet::new());
293    ordered
294        .into_iter()
295        .enumerate()
296        .map(|(rank, name)| (name, rank))
297        .collect()
298}
299
300fn append_postorder(
301    parent: Option<&str>,
302    children: &BTreeMap<Option<String>, Vec<String>>,
303    ordered: &mut Vec<String>,
304    visited: &mut BTreeSet<String>,
305) {
306    let key = parent.map(ToString::to_string);
307    let Some(names) = children.get(&key) else {
308        return;
309    };
310    for name in names {
311        if !visited.insert(name.clone()) {
312            continue;
313        }
314        append_postorder(Some(name.as_str()), children, ordered, visited);
315        ordered.push(name.clone());
316    }
317}
318
319fn layer_names(node: &SyntaxNode) -> Vec<String> {
320    let text = syntax_node_text(node);
321    let Some(rest) = css_keyword(text.trim_start()).strip_prefix("@layer") else {
322        return Vec::new();
323    };
324    rest.split(['{', ';', '\n'])
325        .next()
326        .unwrap_or_default()
327        .split(',')
328        .filter_map(|name| {
329            let name = name.trim();
330            (!name.is_empty()).then(|| name.to_string())
331        })
332        .collect()
333}
334
335fn node_has_block(node: &SyntaxNode) -> bool {
336    node.descendants_with_tokens()
337        .filter_map(|element| element.into_token())
338        .any(|token| matches!(token.kind(), SyntaxKind::LeftBrace | SyntaxKind::SassIndent))
339}
340
341fn syntax_node_text(node: &SyntaxNode) -> String {
342    node.try_resolved()
343        .map(|resolved| resolved.text().to_string())
344        .unwrap_or_default()
345}