use oxc_css_parser::pos::Span;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Element {
pub(crate) combinator: String,
pub(crate) value: String,
}
impl Element {
fn new(combinator: impl Into<String>, value: impl Into<String>) -> Self {
Self {
combinator: combinator.into(),
value: value.into(),
}
}
fn is_parent(&self) -> bool {
self.value == "&"
}
}
pub(crate) type Sel = Vec<Element>;
#[derive(Clone, Debug)]
pub(crate) struct Need {
pub(crate) target: Sel,
pub(crate) all: bool,
}
fn value_eq(a: &Element, b: &Element) -> bool {
a.value == b.value || norm(&a.value) == norm(&b.value)
}
fn norm(value: &str) -> String {
if let Some(canonical) = anb_canonical(value) {
return canonical;
}
let mut out = String::with_capacity(value.len());
let attr = value.starts_with('[');
for ch in value.chars() {
if ch.is_whitespace() {
continue;
}
if attr && (ch == '"' || ch == '\'') {
continue;
}
out.push(ch);
}
out
}
fn anb_canonical(value: &str) -> Option<String> {
const NTH: [&str; 4] = [
":nth-child(",
":nth-of-type(",
":nth-last-child(",
":nth-last-of-type(",
];
let lower = value.to_ascii_lowercase();
NTH.iter()
.any(|p| lower.starts_with(p))
.then(|| value.to_string())
}
pub(crate) fn parse(text: &str) -> Sel {
let bytes = text.as_bytes();
let mut out: Sel = Vec::new();
let mut combinator = String::new();
let mut i = 0;
while i < bytes.len() {
let c = bytes[i] as char;
if c.is_ascii_whitespace() {
if combinator.is_empty() {
combinator.push(' ');
}
i += 1;
continue;
}
if matches!(c, '>' | '+' | '~' | '|') {
combinator = c.to_string();
i += 1;
continue;
}
let start = i;
if c == '[' {
i = skip_balanced(bytes, i, b'[', b']');
} else if c == ':' {
i += 1;
if bytes.get(i) == Some(&b':') {
i += 1;
}
i = skip_ident(bytes, i);
if bytes.get(i) == Some(&b'(') {
i = skip_balanced(bytes, i, b'(', b')');
}
} else if c == '&' {
i += 1;
} else {
if matches!(c, '.' | '#' | '*' | '%') {
i += 1;
}
i = skip_ident(bytes, i);
while bytes.get(i) == Some(&b'|') && bytes.get(i + 1) != Some(&b'|') {
i = skip_ident(bytes, i + 1);
}
if i == start {
i += 1;
}
}
let value = text[start..i].to_string();
out.push(Element {
combinator: std::mem::take(&mut combinator),
value,
});
}
out
}
fn skip_ident(bytes: &[u8], mut i: usize) -> usize {
while i < bytes.len() {
let c = bytes[i];
if c == b'\\' {
i += 2;
} else if c.is_ascii_alphanumeric() || matches!(c, b'_' | b'-') || c >= 0x80 {
i += 1;
} else {
break;
}
}
i.min(bytes.len())
}
fn skip_balanced(bytes: &[u8], start: usize, open: u8, close: u8) -> usize {
let mut depth = 0usize;
let mut quote = 0u8;
let mut i = start;
while i < bytes.len() {
let c = bytes[i];
if quote != 0 {
if c == b'\\' {
i += 2;
continue;
}
if c == quote {
quote = 0;
}
} else if c == b'"' || c == b'\'' {
quote = c;
} else if c == open {
depth += 1;
} else if c == close {
depth -= 1;
if depth == 0 {
return i + 1;
}
}
i += 1;
}
bytes.len()
}
pub(crate) fn render(sel: &Sel) -> String {
let mut out = String::new();
for (i, e) in sel.iter().enumerate() {
match e.combinator.as_str() {
" " => {
if i > 0 {
out.push(' ');
}
}
"" | "|" => out.push_str(&e.combinator),
other => {
out.push(' ');
out.push_str(other);
out.push(' ');
}
}
out.push_str(&e.value);
}
out
}
pub(crate) fn combine(parents: &[Sel], child: &Sel) -> Vec<Sel> {
if !child.iter().any(Element::is_parent) {
if parents.is_empty() {
return vec![child.clone()];
}
return parents
.iter()
.map(|p| {
let mut sel = p.clone();
let mut tail = child.clone();
if let Some(first) = tail.first_mut() {
if first.combinator.is_empty() {
first.combinator.push(' ');
}
}
sel.extend(tail);
sel
})
.collect();
}
let mut acc: Vec<Sel> = vec![Vec::new()];
for el in child {
if !el.is_parent() {
for sel in &mut acc {
sel.push(el.clone());
}
continue;
}
let mut next: Vec<Sel> = Vec::new();
for sel in &acc {
if parents.is_empty() {
if sel.is_empty() {
next.push(sel.clone());
continue;
}
let mut s = sel.clone();
s.push(Element::new(&el.combinator, ""));
next.push(s);
continue;
}
for parent in parents {
let mut s = sel.clone();
let parent_first = parent.first();
let combinator = match parent_first {
Some(p) if el.combinator.is_empty() || el.combinator == " " => {
if p.combinator.is_empty() || p.combinator == " " {
el.combinator.clone()
} else {
p.combinator.clone()
}
}
_ => el.combinator.clone(),
};
if let Some(p) = parent_first {
s.push(Element::new(combinator, &p.value));
}
s.extend(parent.iter().skip(1).cloned());
next.push(s);
}
}
acc = next;
}
acc.retain(|s| !s.is_empty());
acc
}
struct Hit {
start: usize,
end: usize,
}
fn find_match(needle: &Sel, hay: &Sel, all: bool) -> Vec<Hit> {
if needle.is_empty() {
return Vec::new();
}
let mut pending: Vec<(usize, usize)> = Vec::new();
let mut hits = Vec::new();
for (idx, element) in hay.iter().enumerate() {
if all || idx == 0 {
pending.push((idx, 0));
}
let target_combinator = if idx == 0 && element.combinator.is_empty() {
" "
} else {
element.combinator.as_str()
};
let mut i = 0;
while i < pending.len() {
let (start, matched) = pending[i];
let ok = value_eq(&needle[matched], element)
&& (matched == 0 || needle[matched].combinator == target_combinator);
if !ok {
pending.remove(i);
continue;
}
let next = matched + 1;
if next < needle.len() {
pending[i].1 = next;
i += 1;
continue;
}
if !all && idx + 1 < hay.len() {
pending.remove(i);
continue;
}
pending.clear();
hits.push(Hit {
start,
end: idx + 1,
});
break;
}
}
hits
}
fn extend_selector(hits: &[Hit], hay: &Sel, replacement: &Sel) -> Sel {
let Some(first_value) = replacement.first() else {
return hay.clone();
};
let mut out: Sel = Vec::with_capacity(hay.len());
let mut cursor = 0;
for hit in hits {
out.extend(hay[cursor..hit.start].iter().cloned());
out.push(Element::new(&hay[hit.start].combinator, &first_value.value));
out.extend(replacement.iter().skip(1).cloned());
cursor = hit.end;
}
out.extend(hay[cursor..].iter().cloned());
out
}
struct Scope {
parent: Option<usize>,
extends: Vec<usize>,
}
struct Extend {
target: Sel,
all: bool,
self_sel: Sel,
self_span: Span,
first: bool,
visible: bool,
ancestors: Vec<usize>,
ruleset: usize,
}
struct Rule {
sels: Vec<Sel>,
spans: Vec<Span>,
visible: Vec<bool>,
skip: Vec<bool>,
skip_all: bool,
scope: usize,
}
pub(crate) struct Engine {
scopes: Vec<Scope>,
extends: Vec<Extend>,
rules: Vec<Rule>,
applied: Vec<Vec<usize>>,
}
impl Default for Engine {
fn default() -> Self {
Self::new()
}
}
impl Engine {
pub(crate) fn new() -> Self {
Self {
scopes: vec![Scope {
parent: None,
extends: Vec::new(),
}],
extends: Vec::new(),
rules: Vec::new(),
applied: Vec::new(),
}
}
pub(crate) fn push_scope(&mut self, parent: Option<usize>) -> usize {
self.scopes.push(Scope {
parent,
extends: Vec::new(),
});
self.scopes.len() - 1
}
pub(crate) fn add_rule(
&mut self,
sels: Vec<Sel>,
spans: Vec<Span>,
scope: usize,
hidden: bool,
) -> usize {
let skip = vec![false; sels.len()];
let visible = vec![!hidden; sels.len()];
self.rules.push(Rule {
sels,
spans,
visible,
skip,
skip_all: false,
scope,
});
self.rules.len() - 1
}
pub(crate) fn add_need(
&mut self,
need: &Need,
self_sel: Sel,
self_span: Span,
ruleset: usize,
first: bool,
visible: bool,
) {
let id = self.extends.len();
self.extends.push(Extend {
target: need.target.clone(),
all: need.all,
self_sel,
self_span,
first,
visible,
ancestors: vec![id],
ruleset,
});
let scope = self.rules[ruleset].scope;
self.scopes[scope].extends.push(id);
}
pub(crate) fn mark_skip_all(&mut self, ruleset: usize) {
self.rules[ruleset].skip_all = true;
}
pub(crate) fn mark_skip(&mut self, ruleset: usize, path: usize) {
if let Some(skip) = self.rules[ruleset].skip.get_mut(path) {
*skip = true;
}
}
pub(crate) fn visible_selectors(&self, ruleset: usize) -> Vec<(Sel, Span)> {
let rule = &self.rules[ruleset];
rule.sels
.iter()
.zip(rule.spans.iter())
.zip(rule.visible.iter())
.filter(|(_, visible)| **visible)
.map(|((sel, span), _)| (sel.clone(), *span))
.collect()
}
pub(crate) fn run(&mut self) {
let scope_count = self.scopes.len();
self.applied = vec![Vec::new(); scope_count];
for scope in 0..scope_count {
let own = self.scopes[scope].extends.clone();
let mut list = match self.scopes[scope].parent {
Some(parent) => {
let mut list = own.clone();
list.extend(self.applied[parent].iter().cloned());
list
}
None => own.clone(),
};
let chained = self.chain(&list, &own, 0);
list.extend(chained);
self.applied[scope] = list;
}
for index in 0..self.rules.len() {
let scope = self.rules[index].scope;
let extends = self.applied[scope].clone();
if self.rules[index].skip_all {
continue;
}
let mut added: Vec<(Sel, Span, bool)> = Vec::new();
let paths = self.rules[index].sels.len();
for &extend in &extends {
for path in 0..paths {
if self.rules[index].skip[path] {
continue;
}
let hay = &self.rules[index].sels[path];
let (target, all) = (&self.extends[extend].target, self.extends[extend].all);
let hits = find_match(target, hay, all);
if hits.is_empty() {
continue;
}
added.push((
extend_selector(&hits, hay, &self.extends[extend].self_sel),
self.extends[extend].self_span,
self.extends[extend].visible,
));
}
}
self.rules[index]
.sels
.extend(added.iter().map(|(s, _, _)| s.clone()));
self.rules[index]
.spans
.extend(added.iter().map(|(_, s, _)| *s));
self.rules[index]
.visible
.extend(added.iter().map(|(_, _, v)| *v));
}
for rule in &mut self.rules {
rule.skip.resize(rule.sels.len(), false);
rule.visible.resize(rule.sels.len(), true);
rule.spans
.resize(rule.sels.len(), Span { start: 0, end: 0 });
}
}
fn chain(&mut self, list: &[usize], targets: &[usize], depth: usize) -> Vec<usize> {
if depth > 100 {
return Vec::new();
}
let mut added: Vec<usize> = Vec::new();
for &source in list {
for &target in targets {
if self.extends[source]
.ancestors
.contains(&self.extends[target].ancestors[0])
{
continue;
}
let (needle, all) = (
self.extends[source].target.clone(),
self.extends[source].all,
);
let target_self = self.extends[target].self_sel.clone();
let hits = find_match(&needle, &target_self, all);
if hits.is_empty() {
continue;
}
let replacement = self.extends[source].self_sel.clone();
let new_sel = extend_selector(&hits, &target_self, &replacement);
let new_target = self.extends[target].target.clone();
let new_all = self.extends[target].all;
let ruleset = self.extends[target].ruleset;
let first = self.extends[target].first;
let self_span = self.extends[target].self_span;
let id = self.extends.len();
let mut ancestors = vec![id];
ancestors.extend(self.extends[target].ancestors.iter().cloned());
ancestors.extend(self.extends[source].ancestors.iter().cloned());
let visible = self.extends[target].visible;
let path_visible = self.extends[source].visible;
self.extends.push(Extend {
target: new_target,
all: new_all,
self_sel: new_sel.clone(),
self_span,
first,
visible,
ancestors,
ruleset,
});
if first {
self.rules[ruleset].sels.push(new_sel);
self.rules[ruleset].spans.push(self_span);
self.rules[ruleset].visible.push(path_visible);
self.rules[ruleset].skip.push(true);
}
added.push(id);
}
}
if added.is_empty() {
return added;
}
let more = self.chain(&added, targets, depth + 1);
added.extend(more);
added
}
}