1use bumpalo::{Bump, collections::Vec};
2use css_lexer::{AtomSet, Cursor, DynAtomSet, Lexer, ToSpan};
3use css_parse::{
4 CursorOverlaySet, CursorToSourceCursorSink, NodeMetadata, NodeWithMetadata, OverlayKind, OverlaySegment, Parse,
5 Parser, SourceCursor, SourceOffset, Span, ToCursors,
6};
7use std::{cell::RefCell, marker::PhantomData};
8
9#[derive(Debug)]
10pub enum TransformEdit<'a> {
11 Replace { target: Span, cursors: Vec<'a, SourceCursor<'a>> },
12 InsertBefore { anchor: SourceOffset, cursors: Vec<'a, SourceCursor<'a>> },
13 InsertAfter { anchor: SourceOffset, cursors: Vec<'a, SourceCursor<'a>> },
14 Delete { target: Span },
15}
16
17#[derive(Debug)]
18pub enum CommitError {
19 OverlappingEdit { previous: Span, new: Span },
20 InvalidEdit { span: Span },
21}
22
23struct PendingSegment<'a> {
24 span: Span,
25 intent: OverlayKind,
26 order: usize,
27 cursors: Vec<'a, SourceCursor<'a>>,
28}
29
30pub trait TransformerFeatures<M, N>: Sized + Default + Copy {
31 fn transforms<'a, 'ctx>(self, transformer: &'ctx Transformer<'a, M, N, Self>, node: &N)
32 where
33 M: NodeMetadata,
34 N: NodeWithMetadata<M>;
35}
36
37pub struct Transformer<'a, M: NodeMetadata, N: NodeWithMetadata<M>, F: TransformerFeatures<M, N>> {
38 bump: &'a Bump,
39 atoms: &'static dyn DynAtomSet,
40 pub(crate) features: F,
41 changed: RefCell<bool>,
42 overlays: RefCell<CursorOverlaySet<'a>>,
43 edits: RefCell<Vec<'a, TransformEdit<'a>>>,
44 pub(crate) source_text: &'a str,
45 _phantom: PhantomData<(M, N)>,
46}
47
48impl<'a, M: NodeMetadata, N: NodeWithMetadata<M>, F: TransformerFeatures<M, N>> Transformer<'a, M, N, F> {
49 pub fn new_in(bump: &'a Bump, features: F, atoms: &'static dyn DynAtomSet, source_text: &'a str) -> Self {
50 Self {
51 bump,
52 features,
53 atoms,
54 changed: RefCell::new(false),
55 overlays: RefCell::new(CursorOverlaySet::new(bump)),
56 edits: RefCell::new(Vec::new_in(bump)),
57 source_text,
58 _phantom: PhantomData,
59 }
60 }
61
62 pub fn transform(&mut self, node: &mut N) {
63 self.reset();
64 self.features.transforms(self, node);
65 if let Err(err) = self.commit_overlays() {
66 panic!("Transform overlays commit failed: {:?}", err);
67 }
68 }
69
70 pub fn has_changed(&self) -> bool {
71 *self.changed.borrow()
72 }
73
74 pub fn bump(&self) -> &'a Bump {
75 self.bump
76 }
77
78 pub fn to_source_cursor(&self, cursor: Cursor) -> SourceCursor<'a> {
79 SourceCursor::from(cursor, cursor.str_slice(self.source_text))
80 }
81
82 pub fn to_source_cursors(&self, parsed: &impl ToCursors) -> Vec<'a, SourceCursor<'a>> {
83 let mut cursors = Vec::new_in(self.bump());
84 let mut sink = CursorToSourceCursorSink::new(self.source_text, &mut cursors);
85 parsed.to_cursors(&mut sink);
86 cursors
87 }
88
89 pub fn to_atom<A: AtomSet + PartialEq>(&self, c: Cursor) -> A {
90 let bits = c.atom_bits();
91 if bits == 0 {
92 let source_cursor = self.to_source_cursor(c);
93 return A::from_str(&source_cursor.parse(self.bump));
94 }
95 A::from_bits(bits)
96 }
97
98 pub fn overlays(&self) -> std::cell::Ref<'_, CursorOverlaySet<'a>> {
99 self.overlays.borrow()
100 }
101
102 pub fn parse_value<T>(&self, source: &'a str) -> Vec<'a, SourceCursor<'a>>
103 where
104 T: Parse<'a> + ToCursors,
105 {
106 let lexer = Lexer::new(self.atoms, source);
107 let mut parser = Parser::new(self.bump, source, lexer);
108 let parsed = parser.parse_entirely::<T>();
109 debug_assert!(
110 parsed.output.is_some(),
111 "Transformer::parse_value failed to parse {:?}: {:?}",
112 source,
113 parsed.errors
114 );
115 let mut cursors = Vec::new_in(self.bump());
116 let mut sink = CursorToSourceCursorSink::new(source, &mut cursors);
117 parsed.to_cursors(&mut sink);
118 cursors
119 }
120
121 pub fn reset(&self) {
122 *self.changed.borrow_mut() = false;
123 self.overlays.borrow_mut().clear();
124 self.edits.borrow_mut().clear();
125 }
126
127 pub fn has_replacement(&self, span: impl ToSpan) -> bool {
128 self.overlays.borrow().has_overlay(span.to_span())
129 }
130
131 pub fn clear_pending_edits(&self, span: Span) -> bool {
132 let mut edits = self.edits.borrow_mut();
133 let len_before = edits.len();
134 edits.retain(|edit| match edit {
135 TransformEdit::Replace { target, .. } | TransformEdit::Delete { target } => target != &span,
136 _ => true,
137 });
138 len_before != edits.len()
139 }
140
141 pub fn replace(&self, span: impl ToSpan, cursors: Vec<'a, SourceCursor<'a>>) {
142 let span = span.to_span();
143 debug_assert!(span.start() <= span.end(), "Transformer::replace received invalid span: {:?}", span);
144 *self.changed.borrow_mut() = true;
145 self.edits.borrow_mut().push(TransformEdit::Replace { target: span, cursors });
146 }
147
148 pub fn delete(&self, span: impl ToSpan) {
149 let span = span.to_span();
150 debug_assert!(span.start() <= span.end(), "Transformer::delete received invalid span: {:?}", span);
151 *self.changed.borrow_mut() = true;
152 self.edits.borrow_mut().push(TransformEdit::Delete { target: span });
153 }
154
155 pub fn insert_before(&self, anchor: SourceOffset, cursors: Vec<'a, SourceCursor<'a>>) {
156 *self.changed.borrow_mut() = true;
157 self.edits.borrow_mut().push(TransformEdit::InsertBefore { anchor, cursors });
158 }
159
160 pub fn insert_after(&self, anchor: SourceOffset, cursors: Vec<'a, SourceCursor<'a>>) {
161 *self.changed.borrow_mut() = true;
162 self.edits.borrow_mut().push(TransformEdit::InsertAfter { anchor, cursors });
163 }
164
165 pub fn replace_parsed<T>(&self, span: impl ToSpan, css: &str)
166 where
167 T: Parse<'a> + ToCursors,
168 {
169 let owned = self.bump.alloc_str(css);
170 self.replace(span, self.parse_value::<T>(owned));
171 }
172
173 pub fn commit_overlays(&self) -> Result<(), CommitError> {
174 let mut edits = self.edits.borrow_mut();
175 if edits.is_empty() {
176 return Ok(());
177 }
178
179 let mut pending_segments: Vec<'a, PendingSegment<'a>> = Vec::with_capacity_in(edits.len(), self.bump);
180
181 for (order, edit) in edits.drain(..).enumerate() {
182 match edit {
183 TransformEdit::Replace { target, cursors } => {
184 if target.start() > target.end() {
185 return Err(CommitError::InvalidEdit { span: target });
186 }
187 pending_segments.push(PendingSegment {
188 span: target,
189 intent: OverlayKind::Replace,
190 order,
191 cursors,
192 });
193 }
194 TransformEdit::InsertBefore { anchor, cursors } => {
195 let span = Span::new(anchor, anchor);
196 pending_segments.push(PendingSegment { span, intent: OverlayKind::InsertBefore, order, cursors });
197 }
198 TransformEdit::InsertAfter { anchor, cursors } => {
199 let span = Span::new(anchor, anchor);
200 pending_segments.push(PendingSegment { span, intent: OverlayKind::InsertAfter, order, cursors });
201 }
202 TransformEdit::Delete { target } => {
203 pending_segments.push(PendingSegment {
204 span: target,
205 intent: OverlayKind::Replace,
206 order,
207 cursors: Vec::with_capacity_in(0, self.bump()),
208 });
209 }
210 }
211 }
212
213 pending_segments.sort_by(|a, b| {
214 a.span
215 .start()
216 .cmp(&b.span.start())
217 .then_with(|| a.span.end().cmp(&b.span.end()))
218 .then_with(|| a.intent.cmp(&b.intent))
219 .then_with(|| a.order.cmp(&b.order))
220 });
221
222 let mut last_non_zero: Option<Span> = None;
223 for segment in &pending_segments {
224 if segment.span.start() > segment.span.end() {
225 return Err(CommitError::InvalidEdit { span: segment.span });
226 }
227 if segment.span.start() == segment.span.end() {
228 continue;
229 }
230 if let Some(prev) = last_non_zero
231 && segment.span.start() < prev.end()
232 {
233 return Err(CommitError::OverlappingEdit { previous: prev, new: segment.span });
234 }
235 last_non_zero = Some(segment.span);
236 }
237
238 let mut overlays = self.overlays.borrow_mut();
239 overlays.clear();
240 for segment in pending_segments {
241 let overlay_segment = OverlaySegment::new(segment.span, segment.cursors, segment.intent);
242 overlays.push_segment(overlay_segment);
243 }
244
245 Ok(())
246 }
247}
248
249#[macro_export]
250macro_rules! transformer {
251 ($(#[$meta:meta])* $vis:vis enum $feature: ident [ $metadata: ident, $($node:tt)+ ] { $( $(#[$varmeta:meta])* $variant: ident$(,)?)+ } ) => {
252 use $crate::Transform;
253
254 $(#[$meta])*
255 #[bitmask(u16)]
256 pub enum $feature {
257 $(
258 $(#[$varmeta])*
259 $variant,
260 )+
261 }
262
263 impl<N> $crate::TransformerFeatures<$metadata, N> for $feature
264 where
265 N: $($node)+ + ::css_parse::NodeWithMetadata<$metadata>
266 {
267 fn transforms<'a, 'ctx>(self, transformer: &'ctx $crate::Transformer<'a, $metadata, N, Self>, node: &N) {
268 $(
269 if $variant::may_change(transformer.features, node) {
270 let mut transform = $variant::new(transformer);
271 node.accept(&mut transform);
272 }
273 )+
274 }
275 }
276 };
277 }
278
279#[cfg(test)]
280mod tests {
281 use crate::CssMinifierFeature;
282
283 use super::*;
284 use bumpalo::Bump;
285 use css_ast::{CssAtomSet, CssMetadata};
286 use css_parse::{ComponentValues, SourceOffset, Span};
287
288 #[test]
289 fn commit_overlays_rejects_overlapping_edits() {
290 let bump = Bump::default();
291 let context: Transformer<CssMetadata, ComponentValues, CssMinifierFeature> =
292 Transformer::new_in(&bump, CssMinifierFeature::all_bits(), &CssAtomSet::ATOMS, "");
293 let first = context.parse_value::<ComponentValues>("a");
294 let second = context.parse_value::<ComponentValues>("b");
295
296 context.replace(Span::new(SourceOffset(0), SourceOffset(2)), first);
297 context.replace(Span::new(SourceOffset(1), SourceOffset(3)), second);
298
299 let err = context.commit_overlays().expect_err("expected overlapping edits to fail");
300 match err {
301 CommitError::OverlappingEdit { previous, new } => {
302 assert_eq!(previous, Span::new(SourceOffset(0), SourceOffset(2)));
303 assert_eq!(new, Span::new(SourceOffset(1), SourceOffset(3)));
304 }
305 other => panic!("unexpected commit error: {other:?}"),
306 }
307 }
308
309 #[test]
310 fn commit_overlays_preserves_insert_order() {
311 let bump = Bump::default();
312 let context: Transformer<CssMetadata, ComponentValues, CssMinifierFeature> =
313 Transformer::new_in(&bump, CssMinifierFeature::all_bits(), &CssAtomSet::ATOMS, "");
314 let anchor = SourceOffset(5);
315
316 context.insert_before(anchor, context.parse_value::<ComponentValues>("A"));
317 context.insert_before(anchor, context.parse_value::<ComponentValues>("B"));
318 context.insert_after(anchor, context.parse_value::<ComponentValues>("C"));
319 context.insert_after(anchor, context.parse_value::<ComponentValues>("D"));
320
321 context.commit_overlays().expect("commit should succeed");
322 let overlays = context.overlays();
323 let segments = overlays.segments();
324
325 assert_eq!(segments.len(), 4);
326 assert_eq!(segments[0].cursors()[0].source(), "A");
327 assert_eq!(segments[1].cursors()[0].source(), "B");
328 assert_eq!(segments[2].cursors()[0].source(), "C");
329 assert_eq!(segments[3].cursors()[0].source(), "D");
330 }
331}