use std::cell::RefCell; use oxc::{ allocator::{Allocator, Vec}, ast_visit::Visit, diagnostics::OxcDiagnostic, parser::{ParseOptions, Parser}, span::SourceType, }; use thiserror::Error; pub mod cfg; mod changes; mod rewrite; mod visitor; use cfg::{Config, Flags, UrlRewriter}; use changes::JsChanges; use visitor::Visitor; #[derive(Error, Debug)] pub enum RewriterError { #[error("transformer error: {0}")] Transformer(#[from] transform::TransformError), #[error("url rewriter error: {0}")] Url(Box), #[error("formatting error: {0}")] Formatting(#[from] std::fmt::Error), #[error("oxc panicked in parser: {0}")] OxcPanicked(String), #[error("Already rewriting")] AlreadyRewriting, #[error("Not rewriting")] NotRewriting, #[error("Changes left over")] Leftover, } #[derive(Debug)] pub struct RewriteResult<'alloc> { pub js: Vec<'alloc, u8>, pub sourcemap: Vec<'alloc, u8>, pub errors: std::vec::Vec, pub flags: Flags, } pub struct Rewriter { cfg: Config, url: E, changes: RefCell>>, } impl Rewriter { fn take_changes<'alloc: 'data, 'data>( &'data self, alloc: &'alloc Allocator, ) -> Result, RewriterError> { let mut slot = self .changes .try_borrow_mut() .map_err(|_| RewriterError::AlreadyRewriting)?; slot.take() .ok_or(RewriterError::AlreadyRewriting) .and_then(|x| { let mut x = unsafe { std::mem::transmute::, JsChanges<'alloc, 'data>>(x) }; x.set_alloc(alloc)?; Ok(x) }) } fn put_changes<'alloc: 'data, 'data>( &'data self, mut changes: JsChanges<'alloc, 'data>, ) -> Result<(), RewriterError> { if !changes.empty() { return Err(RewriterError::Leftover); } let mut slot = self .changes .try_borrow_mut() .map_err(|_| RewriterError::AlreadyRewriting)?; if slot.is_some() { Err(RewriterError::NotRewriting) } else { changes.take_alloc()?; let changes = unsafe { std::mem::transmute::, JsChanges<'static, 'static>>( changes, ) }; slot.replace(changes); Ok(()) } } pub fn new(cfg: Config, url_rewriter: E) -> Self { Self { cfg, url: url_rewriter, changes: RefCell::new(Some(JsChanges::new())), } } pub fn rewrite<'alloc: 'data, 'data>( &'data self, alloc: &'alloc Allocator, js: &'data str, flags: Flags, ) -> Result, RewriterError> { let source_type = SourceType::unambiguous() .with_javascript(true) .with_module(flags.is_module) .with_standard(true); let parsed = Parser::new(alloc, js, source_type) .with_options(ParseOptions { allow_v8_intrinsics: true, allow_return_outside_function: true, ..Default::default() }) .parse(); if parsed.panicked { use std::fmt::Write; let mut errors = String::new(); for error in parsed.errors { writeln!(errors, "{error}")?; } return Err(RewriterError::OxcPanicked(errors)); } let jschanges = self.take_changes(alloc)?; let mut visitor = Visitor { alloc, jschanges, error: None, config: &self.cfg, rewriter: &self.url, flags, }; visitor.visit_program(&parsed.program); if let Some(error) = visitor.error { return Err(RewriterError::Url(error)); } let mut jschanges = visitor.jschanges; let changed = jschanges.perform(js, &self.cfg, &visitor.flags)?; self.put_changes(jschanges)?; let js: Vec<'alloc, u8> = changed.source; let sourcemap: Vec<'alloc, u8> = changed.map; Ok(RewriteResult { js, sourcemap, errors: parsed.errors, flags: visitor.flags, }) } }