1use std::ops::Range;
8
9use crate::{
10 SyntaxKind, SyntaxNode,
11 ident::{CanonicalClassKeyV0, CanonicalIdKeyV0, CanonicalTypeSelectorKeyV0, ClassNameV0},
12};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum CanonicalSelectorCombinatorV0 {
16 Descendant,
17 Child,
18 NextSibling,
19 SubsequentSibling,
20 Column,
21 Other,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct CanonicalSelectorSpecificityWitnessV0 {
26 pub ids: u32,
27 pub classes: u32,
28 pub types: u32,
29 pub exact: bool,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct NestingTokenV0 {
34 byte_range: Range<usize>,
35}
36
37impl NestingTokenV0 {
38 pub fn byte_range(&self) -> Range<usize> {
39 self.byte_range.clone()
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct CanonicalCompoundSelectorV0 {
45 byte_range: Range<usize>,
46 required_tag: Option<CanonicalTypeSelectorKeyV0>,
47 required_id: Option<CanonicalIdKeyV0>,
48 required_classes: Vec<CanonicalClassKeyV0>,
49 nesting_tokens: Vec<NestingTokenV0>,
50}
51
52impl CanonicalCompoundSelectorV0 {
53 pub fn byte_range(&self) -> Range<usize> {
54 self.byte_range.clone()
55 }
56
57 pub fn required_tag(&self) -> Option<&CanonicalTypeSelectorKeyV0> {
58 self.required_tag.as_ref()
59 }
60
61 pub fn required_id(&self) -> Option<&CanonicalIdKeyV0> {
62 self.required_id.as_ref()
63 }
64
65 pub fn required_classes(&self) -> &[CanonicalClassKeyV0] {
66 self.required_classes.as_slice()
67 }
68
69 pub fn nesting_tokens(&self) -> &[NestingTokenV0] {
70 self.nesting_tokens.as_slice()
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct CanonicalSelectorBranchV0 {
76 authored: String,
77 byte_range: Range<usize>,
78 compounds: Vec<CanonicalCompoundSelectorV0>,
79 combinators: Vec<CanonicalSelectorCombinatorV0>,
80 nesting_tokens: Vec<NestingTokenV0>,
81 specificity: CanonicalSelectorSpecificityWitnessV0,
82}
83
84impl CanonicalSelectorBranchV0 {
85 pub fn authored(&self) -> &str {
86 self.authored.as_str()
87 }
88
89 pub fn byte_range(&self) -> Range<usize> {
90 self.byte_range.clone()
91 }
92
93 pub fn compounds(&self) -> &[CanonicalCompoundSelectorV0] {
94 self.compounds.as_slice()
95 }
96
97 pub fn combinators(&self) -> &[CanonicalSelectorCombinatorV0] {
98 self.combinators.as_slice()
99 }
100
101 pub fn nesting_tokens(&self) -> &[NestingTokenV0] {
102 self.nesting_tokens.as_slice()
103 }
104
105 pub fn specificity(&self) -> CanonicalSelectorSpecificityWitnessV0 {
106 self.specificity
107 }
108
109 fn substitute_nesting(&self, parent: &str) -> String {
110 if self.nesting_tokens.is_empty() {
111 return format!("{parent} {}", self.authored.trim());
112 }
113 let branch_start = self.byte_range.start;
114 let mut output = String::with_capacity(self.authored.len().saturating_add(parent.len()));
115 let mut cursor = 0usize;
116 for token in &self.nesting_tokens {
117 let start = token.byte_range.start.saturating_sub(branch_start);
118 let end = token.byte_range.end.saturating_sub(branch_start);
119 if start < cursor || end > self.authored.len() {
120 continue;
121 }
122 output.push_str(&self.authored[cursor..start]);
123 output.push_str(parent);
124 cursor = end;
125 }
126 output.push_str(&self.authored[cursor..]);
127 output
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct CanonicalSelectorAst {
137 authored: String,
138 source_byte_range: Range<usize>,
139 branches: Vec<CanonicalSelectorBranchV0>,
140}
141
142impl CanonicalSelectorAst {
143 pub fn from_cst(selector_node: &SyntaxNode) -> Option<Self> {
144 if !matches!(
145 selector_node.kind(),
146 SyntaxKind::SelectorList
147 | SyntaxKind::RelativeSelectorList
148 | SyntaxKind::BogusSelectorList
149 | SyntaxKind::Selector
150 | SyntaxKind::RelativeSelector
151 | SyntaxKind::BogusSelector
152 ) {
153 return None;
154 }
155 let (authority_start, authority_prefix) = leading_selector_trivia(selector_node);
156 let authority_end = byte_end(selector_node);
157 let authored = format!("{authority_prefix}{}", syntax_node_text(selector_node)?);
158 let branch_nodes = if matches!(
159 selector_node.kind(),
160 SyntaxKind::Selector | SyntaxKind::RelativeSelector | SyntaxKind::BogusSelector
161 ) {
162 vec![selector_node.clone()]
163 } else {
164 selector_node
165 .children()
166 .filter(|child| {
167 matches!(
168 child.kind(),
169 SyntaxKind::Selector
170 | SyntaxKind::RelativeSelector
171 | SyntaxKind::BogusSelector
172 )
173 })
174 .cloned()
175 .collect::<Vec<_>>()
176 };
177 let branches = branch_nodes
178 .iter()
179 .enumerate()
180 .filter_map(|(index, branch)| {
181 build_branch(branch, authority_start, authored.as_str(), index == 0)
182 })
183 .collect::<Vec<_>>();
184 (!branches.is_empty()).then_some(Self {
185 authored,
186 source_byte_range: authority_start..authority_end,
187 branches,
188 })
189 }
190
191 pub fn authored(&self) -> &str {
192 self.authored.as_str()
193 }
194
195 pub fn branches(&self) -> &[CanonicalSelectorBranchV0] {
196 self.branches.as_slice()
197 }
198
199 pub fn nesting_token_count(&self) -> usize {
200 self.branches
201 .iter()
202 .map(|branch| branch.nesting_tokens.len())
203 .sum()
204 }
205
206 pub fn canonical_class_keys(&self) -> impl Iterator<Item = &CanonicalClassKeyV0> {
207 self.branches
208 .iter()
209 .flat_map(|branch| branch.compounds.iter())
210 .flat_map(|compound| compound.required_classes.iter())
211 }
212
213 pub fn canonical_class_key_for_source_span(
216 &self,
217 semantic_name: &str,
218 source_byte_range: Range<usize>,
219 nesting_parent_name: Option<&str>,
220 ) -> Option<CanonicalClassKeyV0> {
221 if self.source_byte_range.start > source_byte_range.start
222 || source_byte_range.start >= source_byte_range.end
223 || source_byte_range.end > self.source_byte_range.end
224 {
225 return None;
226 }
227 let relative_source_range = source_byte_range.start - self.source_byte_range.start
228 ..source_byte_range.end - self.source_byte_range.start;
229 let key = ClassNameV0::new(semantic_name).canonical_key();
230 let matching_branches = self
231 .branches
232 .iter()
233 .filter(|branch| {
234 branch.byte_range.start <= relative_source_range.start
235 && relative_source_range.end <= branch.byte_range.end
236 })
237 .collect::<Vec<_>>();
238 let directly_issued = matching_branches.iter().any(|branch| {
239 branch.compounds.iter().any(|compound| {
240 compound.byte_range.start <= relative_source_range.start
241 && relative_source_range.end <= compound.byte_range.end
242 && compound.required_classes.contains(&key)
243 })
244 });
245 let expected = format!(".{semantic_name}");
246 let nesting_issued = matching_branches.iter().any(|branch| {
247 if branch.nesting_tokens.is_empty() {
248 return false;
249 }
250 let issued_for_parent = |parent_name: &str| {
251 let parent = format!(".{parent_name}");
252 branch.substitute_nesting(parent.as_str()).trim() == expected
253 };
254 nesting_parent_name.is_some_and(issued_for_parent)
255 });
256 (directly_issued || nesting_issued).then_some(key)
257 }
258
259 pub fn expand_with_parent(&self, parent: &Self) -> Option<String> {
260 let mut expanded = Vec::new();
261 for parent_branch in &parent.branches {
262 let parent_text = parent_branch.authored.trim();
263 if parent_text.is_empty() {
264 continue;
265 }
266 for nested_branch in &self.branches {
267 expanded.push(nested_branch.substitute_nesting(parent_text));
268 }
269 }
270 (!expanded.is_empty()).then(|| expanded.join(", "))
271 }
272}
273
274fn build_branch(
275 branch: &SyntaxNode,
276 authority_start: usize,
277 authority_text: &str,
278 include_authority_prefix: bool,
279) -> Option<CanonicalSelectorBranchV0> {
280 let raw_branch_range = relative_range(branch, authority_start)?;
281 let authored_start = if include_authority_prefix {
282 0
283 } else {
284 raw_branch_range.start
285 };
286 let raw_authored = authority_text.get(authored_start..raw_branch_range.end)?;
287 let leading_trivia_bytes = raw_authored
288 .len()
289 .saturating_sub(raw_authored.trim_start().len());
290 let trailing_trivia_bytes = raw_authored
291 .len()
292 .saturating_sub(raw_authored.trim_end().len());
293 let branch_range = authored_start.saturating_add(leading_trivia_bytes)
294 ..raw_branch_range.end.saturating_sub(trailing_trivia_bytes);
295 let authored = authority_text.get(branch_range.clone())?.to_string();
296 let complex = branch
297 .children()
298 .find(|child| child.kind() == SyntaxKind::ComplexSelector)
299 .unwrap_or(branch);
300 let compounds = complex
301 .children()
302 .filter(|child| child.kind() == SyntaxKind::CompoundSelector)
303 .filter_map(|compound| build_compound(compound, authority_start))
304 .collect::<Vec<_>>();
305 let combinators = complex
306 .children()
307 .filter(|child| child.kind() == SyntaxKind::Combinator)
308 .map(|node| classify_combinator(syntax_node_text(node).as_deref().unwrap_or_default()))
309 .collect::<Vec<_>>();
310 let nesting_tokens = branch
311 .descendants()
312 .filter(|node| node.kind() == SyntaxKind::NestingSelectorNode)
313 .filter_map(|node| {
314 relative_range(node, authority_start).map(|byte_range| NestingTokenV0 { byte_range })
315 })
316 .collect::<Vec<_>>();
317 let specificity = specificity_witness(branch);
318 Some(CanonicalSelectorBranchV0 {
319 authored,
320 byte_range: branch_range,
321 compounds,
322 combinators,
323 nesting_tokens,
324 specificity,
325 })
326}
327
328fn build_compound(
329 compound: &SyntaxNode,
330 authority_start: usize,
331) -> Option<CanonicalCompoundSelectorV0> {
332 let mut required_tag = None;
333 let mut required_id = None;
334 let mut required_classes = Vec::new();
335 let mut nesting_tokens = Vec::new();
336 for node in compound.children() {
337 match node.kind() {
338 SyntaxKind::ClassSelector => {
339 if let Some(text) = syntax_node_text(node)
340 && let Some(raw) = text.strip_prefix('.')
341 {
342 required_classes.push(ClassNameV0::new(raw).canonical_key());
343 }
344 }
345 SyntaxKind::IdSelector => {
346 if let Some(text) = syntax_node_text(node)
347 && let Some(raw) = text.strip_prefix('#')
348 {
349 required_id = Some(CanonicalIdKeyV0::from_authored(raw));
350 }
351 }
352 SyntaxKind::TypeSelector => {
353 if let Some(text) = syntax_node_text(node) {
354 let raw = text.rsplit('|').next().unwrap_or(text.as_str()).trim();
355 if raw != "*" && !raw.is_empty() {
356 required_tag = Some(CanonicalTypeSelectorKeyV0::from_authored(raw));
357 }
358 }
359 }
360 SyntaxKind::NestingSelectorNode => {
361 if let Some(byte_range) = relative_range(node, authority_start) {
362 nesting_tokens.push(NestingTokenV0 { byte_range });
363 }
364 }
365 _ => {}
366 }
367 }
368 Some(CanonicalCompoundSelectorV0 {
369 byte_range: relative_range(compound, authority_start)?,
370 required_tag,
371 required_id,
372 required_classes,
373 nesting_tokens,
374 })
375}
376
377fn specificity_witness(branch: &SyntaxNode) -> CanonicalSelectorSpecificityWitnessV0 {
378 let mut witness = CanonicalSelectorSpecificityWitnessV0 {
379 ids: 0,
380 classes: 0,
381 types: 0,
382 exact: true,
383 };
384 for node in branch.descendants() {
385 match node.kind() {
386 SyntaxKind::IdSelector => witness.ids = witness.ids.saturating_add(1),
387 SyntaxKind::ClassSelector
388 | SyntaxKind::AttributeSelector
389 | SyntaxKind::PseudoClassSelector => {
390 witness.classes = witness.classes.saturating_add(1)
391 }
392 SyntaxKind::TypeSelector | SyntaxKind::PseudoElementSelector => {
393 witness.types = witness.types.saturating_add(1)
394 }
395 SyntaxKind::PseudoSelectorArgument
396 | SyntaxKind::NthSelectorArgument
397 | SyntaxKind::BogusSelector
398 | SyntaxKind::BogusCompoundSelector => witness.exact = false,
399 _ => {}
400 }
401 }
402 witness
403}
404
405fn classify_combinator(text: &str) -> CanonicalSelectorCombinatorV0 {
406 match text.trim() {
407 "" => CanonicalSelectorCombinatorV0::Descendant,
408 ">" => CanonicalSelectorCombinatorV0::Child,
409 "+" => CanonicalSelectorCombinatorV0::NextSibling,
410 "~" => CanonicalSelectorCombinatorV0::SubsequentSibling,
411 "||" => CanonicalSelectorCombinatorV0::Column,
412 _ => CanonicalSelectorCombinatorV0::Other,
413 }
414}
415
416fn relative_range(node: &SyntaxNode, authority_start: usize) -> Option<Range<usize>> {
417 let start = byte_start(node).checked_sub(authority_start)?;
418 let end = byte_end(node).checked_sub(authority_start)?;
419 (start <= end).then_some(start..end)
420}
421
422fn byte_start(node: &SyntaxNode) -> usize {
423 u32::from(node.text_range().start()) as usize
424}
425
426fn byte_end(node: &SyntaxNode) -> usize {
427 u32::from(node.text_range().end()) as usize
428}
429
430fn leading_selector_trivia(selector_node: &SyntaxNode) -> (usize, String) {
431 let mut start = byte_start(selector_node);
432 let mut authored_parts = Vec::<String>::new();
433 let trivia_anchor = selector_node
434 .ancestors()
435 .find(|node| node.kind() == SyntaxKind::Rule)
436 .unwrap_or(selector_node);
437 let mut previous = trivia_anchor.prev_sibling_or_token();
438 while let Some(element) = previous {
439 let Some(token) = element.as_token() else {
440 break;
441 };
442 if !matches!(
443 token.kind(),
444 SyntaxKind::Whitespace | SyntaxKind::LineComment | SyntaxKind::BlockComment
445 ) {
446 break;
447 }
448 let Some(text) = token
449 .try_resolved()
450 .map(|resolved| resolved.text().to_string())
451 else {
452 break;
453 };
454 start = u32::from(token.text_range().start()) as usize;
455 authored_parts.push(text);
456 previous = element.prev_sibling_or_token();
457 }
458 authored_parts.reverse();
459 (start, authored_parts.concat())
460}
461
462fn syntax_node_text(node: &SyntaxNode) -> Option<String> {
463 node.try_resolved()
464 .map(|resolved| resolved.text().to_string())
465}