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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum LayerBindingResolutionV0 {
12 Resolved(Option<LayerOrdinal>),
14 TopologyIncomplete { unresolved_count: usize },
16}
17
18pub 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}