diff --git a/rewriter/native/src/main.rs b/rewriter/native/src/main.rs index bf24e1b..ed7bfa3 100644 --- a/rewriter/native/src/main.rs +++ b/rewriter/native/src/main.rs @@ -65,8 +65,6 @@ fn main() -> Result<()> { eprintln!("{}", err.with_source_code(source.clone())); } - println!("changes: {:#?}", res.changes); - println!( "rewritten:\n{}", String::from_utf8(res.js).context("failed to parse rewritten js")? @@ -160,6 +158,10 @@ function $wrap(val) { return val; } +const $gwrap = $wrap; + +function $scramitize(val) { return val } + function assert(val) { if (!val) fail(); } @@ -172,7 +174,7 @@ function check(val) { .unwrap(); let rewritten = dorewrite(&content).unwrap(); - println!("{:?}", rewritten); + println!("{}", std::str::from_utf8(&rewritten.js).unwrap()); context.eval(Source::from_bytes(&rewritten.js)).unwrap(); println!("PASS"); diff --git a/rewriter/native/tests/0-sanity.js b/rewriter/native/tests/0-sanity.js new file mode 100644 index 0000000..c4bb536 --- /dev/null +++ b/rewriter/native/tests/0-sanity.js @@ -0,0 +1,5 @@ +check(window); +check(this); +check(globalThis); +check(location); +check(globalThis["win" + "dow"]) diff --git a/rewriter/native/tests/1-destructure.js b/rewriter/native/tests/1-destructure.js new file mode 100644 index 0000000..7c236b6 --- /dev/null +++ b/rewriter/native/tests/1-destructure.js @@ -0,0 +1,3 @@ +const { location: x } = globalThis; + +check(x); diff --git a/rewriter/native/tests/2-function-default.js b/rewriter/native/tests/2-function-default.js new file mode 100644 index 0000000..7ad7532 --- /dev/null +++ b/rewriter/native/tests/2-function-default.js @@ -0,0 +1,6 @@ +function f(g = globalThis, l = location) { + check(g); + check(l); +} + +f(); diff --git a/rewriter/native/tests/3-shadow-globals.js b/rewriter/native/tests/3-shadow-globals.js new file mode 100644 index 0000000..e1fd8c0 --- /dev/null +++ b/rewriter/native/tests/3-shadow-globals.js @@ -0,0 +1,7 @@ + +function f(location, globalThis) { + assert(location === 1) + assert(globalThis === 2) +} + +f(1, 2); diff --git a/rewriter/native/tests/eval-overridden-globals.js.disabled b/rewriter/native/tests/eval-overridden-globals.js.disabled new file mode 100644 index 0000000..700890e --- /dev/null +++ b/rewriter/native/tests/eval-overridden-globals.js.disabled @@ -0,0 +1,8 @@ + +let reached = false +let eval = (t) => t === "location = 1" && (reached = true) + +// testing to make sure this doesn't get rewritten +eval("location = 1") + +if (!reached) fail(); diff --git a/rewriter/native/tests/for-assignment.js.disabled b/rewriter/native/tests/for-assignment.js.disabled new file mode 100644 index 0000000..78e1e21 --- /dev/null +++ b/rewriter/native/tests/for-assignment.js.disabled @@ -0,0 +1,3 @@ +for (location of ["https://google.com"]) { + // +} diff --git a/rewriter/rewriter/src/changes.rs b/rewriter/rewriter/src/changes.rs index 4176a6d..a62878b 100644 --- a/rewriter/rewriter/src/changes.rs +++ b/rewriter/rewriter/src/changes.rs @@ -7,7 +7,7 @@ use smallvec::{smallvec, SmallVec}; use crate::{cfg::Config, RewriterError}; #[derive(Debug, PartialEq, Eq, Clone)] -pub enum Rewrite { +pub(crate) enum Rewrite { /// `(cfg.wrapfn(ident))` | `cfg.wrapfn(ident)` WrapFn { span: Span, @@ -52,7 +52,7 @@ pub enum Rewrite { rhsspan: Span, op: AssignmentOperator, }, - /// `ident,` -> `ident: cfg.wrapfn(ident)` + /// `ident,` -> `ident: cfg.wrapfn(ident),` ShorthandObj { span: Span, name: CompactStr, @@ -73,93 +73,228 @@ pub enum Rewrite { } impl Rewrite { - pub fn get_span(&self) -> &Span { + fn into_inner(self) -> SmallVec<[JsChange; 4]> { match self { - Self::WrapFn { span, .. } => span, - Self::SetRealmFn { span, .. } => span, - Self::WrapThisFn { span } => span, - Self::ImportFn { span } => span, - Self::MetaFn { span } => span, + Self::WrapFn { wrapped, span } => { + let start = Span::new(span.start, span.start); + let end = Span::new(span.end, span.end); + if wrapped { + smallvec![ + JsChange::WrapFn { + span: start, + extra: true + }, + JsChange::DoubleClosingParen { span: end } + ] + } else { + smallvec![ + JsChange::WrapFn { + span: start, + extra: false + }, + JsChange::ClosingParen { + span: end, + semi: false, + } + ] + } + } + Self::SetRealmFn { span } => smallvec![JsChange::SetRealmFn { span }], + Self::WrapThisFn { span } => smallvec![ + JsChange::WrapThisFn { + span: Span::new(span.start, span.start) + }, + JsChange::ClosingParen { + span: Span::new(span.end, span.end), + semi: false, + } + ], + Self::ImportFn { span } => smallvec![JsChange::ImportFn { span }], + Self::MetaFn { span } => smallvec![JsChange::MetaFn { span }], - Self::ScramErr { span, .. } => span, - Self::Scramitize { span } => span, + Self::ScramErr { span, .. } => { + smallvec![ + JsChange::ScramErrFn { + span: Span::new(span.start, span.start) + }, + JsChange::ClosingParen { + span: Span::new(span.end, span.end), + semi: true + } + ] + } + Self::Scramitize { span } => { + smallvec![ + JsChange::ScramitizeFn { + span: Span::new(span.start, span.start) + }, + JsChange::ClosingParen { + span: Span::new(span.end, span.end), + semi: false, + } + ] + } - Self::Eval { span, .. } => span, - Self::Assignment { entirespan, .. } => entirespan, - Self::ShorthandObj { span, .. } => span, - Self::SourceTag { span, .. } => span, + Self::Eval { inner, span } => smallvec![ + JsChange::EvalRewriteFn { + span: Span::new(span.start, inner.start) + }, + JsChange::ReplaceClosingParen { + span: Span::new(inner.end, span.end), + } + ], + Self::Assignment { + name, + rhsspan, + op, + entirespan, + } => smallvec![ + JsChange::AssignmentLeft { + name, + op, + span: Span::new(entirespan.start, rhsspan.start) + }, + JsChange::ReplaceClosingParen { + span: Span::new(rhsspan.end, entirespan.end) + } + ], + // maps to insert + Self::ShorthandObj { name, span } => smallvec![JsChange::ShorthandObj { + ident: name, + span: Span::new(span.end, span.end) + }], + // maps to insert + Self::SourceTag { tagname, span } => smallvec![JsChange::SourceTag { span, tagname }], + Self::Replace { text, span } => smallvec![JsChange::Replace { span, text }], + Self::Delete { span } => smallvec![JsChange::Delete { span }], + } + } +} - Self::Replace { span, .. } => span, - Self::Delete { span } => span, +#[derive(Debug, PartialEq, Eq, Clone)] +enum JsChange { + /// insert `${cfg.wrapfn}(` + WrapFn { span: Span, extra: bool }, + /// insert `${cfg.setrealmfn}({}).` + SetRealmFn { span: Span }, + /// insert `${cfg.wrapthis}(` + WrapThisFn { span: Span }, + /// insert `$scramerr(` + ScramErrFn { span: Span }, + /// insert `$scramitize(` + ScramitizeFn { span: Span }, + /// insert `eval(${cfg.rewritefn}(` + EvalRewriteFn { span: Span }, + /// insert `: ${cfg.wrapfn}(ident)` + ShorthandObj { span: Span, ident: CompactStr }, + /// insert scramtag + SourceTag { span: Span, tagname: String }, + + /// replace span with `(${cfg.importfn}("${cfg.base}")` + ImportFn { span: Span }, + /// replace span with `${cfg.metafn}("${cfg.base}")` + MetaFn { span: Span }, + /// replace span with `((t)=>$scramjet$tryset(${name},"${op}",t)||(${name}${op}t))(` + AssignmentLeft { + span: Span, + name: CompactStr, + op: AssignmentOperator, + }, + + /// replace span with `)` + ReplaceClosingParen { span: Span }, + /// insert `)` + ClosingParen { span: Span, semi: bool }, + /// insert `))` + DoubleClosingParen { span: Span }, + + /// replace span with text + Replace { span: Span, text: String }, + /// replace span with "" + Delete { span: Span }, +} + +impl JsChange { + fn get_span(&self) -> &Span { + match self { + Self::WrapFn { span, .. } + | Self::SetRealmFn { span } + | Self::WrapThisFn { span } + | Self::ScramErrFn { span } + | Self::ScramitizeFn { span } + | Self::EvalRewriteFn { span } + | Self::ShorthandObj { span, .. } + | Self::SourceTag { span, .. } + | Self::ImportFn { span } + | Self::MetaFn { span } + | Self::AssignmentLeft { span, .. } + | Self::ReplaceClosingParen { span } + | Self::ClosingParen { span, .. } + | Self::DoubleClosingParen { span } + | Self::Replace { span, .. } + | Self::Delete { span } => span, } } - // returns (bunch of stuff to add before, option) - // bunch of stuff to add after should only be some if it's not a replace op fn to_inner<'a, E>(&'a self, cfg: &'a Config) -> JsChangeInner<'a> where E: Fn(String) -> String, E: Clone, { match self { - Self::WrapFn { wrapped, span } => { - if *wrapped { - JsChangeInner::Wrap { - before: smallvec!["(", cfg.wrapfn.as_str(), "("], - inner: span, - after: smallvec!["))"], + Self::WrapFn { span, extra } => { + if *extra { + JsChangeInner::Insert { + loc: span.start, + str: smallvec!["(", cfg.wrapfn.as_str(), "("], } } else { - JsChangeInner::Wrap { - before: smallvec![cfg.wrapfn.as_str(), "("], - inner: span, - after: smallvec![")"], + JsChangeInner::Insert { + loc: span.start, + str: smallvec![cfg.wrapfn.as_str(), "("], } } } - Self::SetRealmFn { span } => JsChangeInner::Wrap { - before: smallvec![cfg.setrealmfn.as_str(), "({})."], - inner: span, - after: smallvec![], + Self::SetRealmFn { span } => JsChangeInner::Insert { + loc: span.start, + str: smallvec![cfg.setrealmfn.as_str(), "({})."], }, - Self::WrapThisFn { span } => JsChangeInner::Wrap { - before: smallvec![cfg.wrapthisfn.as_str(), "("], - inner: span, - after: smallvec![")"], + Self::WrapThisFn { span } => JsChangeInner::Insert { + loc: span.start, + str: smallvec![cfg.wrapthisfn.as_str(), "("], }, - Self::ImportFn { .. } => JsChangeInner::Replace(smallvec![ - "(", - cfg.importfn.as_str(), - "(\"", - cfg.base.as_str(), - "\"))" - ]), - Self::MetaFn { .. } => JsChangeInner::Replace(smallvec![ - cfg.metafn.as_str(), - "(\"", - cfg.base.as_str(), - "\")" - ]), - - // maps to insert - Self::ScramErr { name, .. } => { - JsChangeInner::Replace(smallvec!["$scramerr(", name.as_str(), ");"]) - } - Self::Scramitize { span } => JsChangeInner::Wrap { - before: smallvec!["$scramitize("], - inner: span, - after: smallvec![")"], + Self::ScramErrFn { span } => JsChangeInner::Insert { + loc: span.start, + str: smallvec!["$scramerr("], }, - - Self::Eval { inner, .. } => JsChangeInner::Wrap { - before: smallvec!["eval(", cfg.rewritefn.as_str(), "("], - inner, - after: smallvec![")"], + Self::ScramitizeFn { span } => JsChangeInner::Insert { + loc: span.start, + str: smallvec!["$scramitize("], }, - Self::Assignment { - name, rhsspan, op, .. - } => JsChangeInner::Wrap { - before: smallvec![ + Self::EvalRewriteFn { .. } => JsChangeInner::Replace { + str: smallvec!["eval(", cfg.rewritefn.as_str(), "("], + }, + Self::ShorthandObj { span, ident } => JsChangeInner::Insert { + loc: span.start, + str: smallvec![":", cfg.wrapfn.as_str(), "(", ident.as_str(), ")"], + }, + Self::SourceTag { span, tagname } => JsChangeInner::Insert { + loc: span.start, + str: smallvec![ + "/*scramtag ", + cfg.sourcetag.as_str(), + tagname.as_str(), + "*/" + ], + }, + Self::ImportFn { span } => JsChangeInner::Replace { + str: smallvec!["(", cfg.importfn.as_str(), "(\"", cfg.base.as_str(), "\")"], + }, + Self::MetaFn { span } => JsChangeInner::Replace { + str: smallvec![cfg.metafn.as_str(), "(\"", cfg.base.as_str()], + }, + Self::AssignmentLeft { span, name, op } => JsChangeInner::Replace { + str: smallvec![ "((t)=>$scramjet$tryset(", name.as_str(), ",\"", @@ -167,41 +302,39 @@ impl Rewrite { "\",t)||(", name.as_str(), op.as_str(), - "t))(" + "t)(" ], - inner: rhsspan, - after: smallvec![")"], }, - // maps to insert - Self::ShorthandObj { name, .. } => JsChangeInner::Replace(smallvec![ - name.as_str(), - ":", - cfg.wrapfn.as_str(), - "(", - name.as_str(), - ")" - ]), - // maps to insert - Self::SourceTag { tagname, .. } => JsChangeInner::Replace(smallvec![ - "/*scramtag ", - tagname.as_str(), - " ", - cfg.sourcetag.as_str(), - "*/" - ]), - Self::Replace { text, .. } => JsChangeInner::Replace(smallvec![text.as_str()]), - Self::Delete { .. } => JsChangeInner::Replace(smallvec![]), + Self::ReplaceClosingParen { span } => JsChangeInner::Replace { + str: smallvec![")"], + }, + Self::ClosingParen { span, semi } => JsChangeInner::Insert { + loc: span.start, + str: if *semi { + smallvec![");"] + } else { + smallvec![")"] + }, + }, + Self::DoubleClosingParen { span } => JsChangeInner::Insert { + loc: span.start, + str: smallvec!["))"], + }, + Self::Replace { span, text } => JsChangeInner::Replace { + str: smallvec![text.as_str()], + }, + Self::Delete { span } => JsChangeInner::Replace { str: smallvec![""] }, } } } -impl PartialOrd for Rewrite { +impl PartialOrd for JsChange { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for Rewrite { +impl Ord for JsChange { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.get_span().start.cmp(&other.get_span().start) } @@ -210,17 +343,8 @@ impl Ord for Rewrite { type Changes<'a> = SmallVec<[&'a str; 8]>; enum JsChangeInner<'a> { - Wrap { - /// Changes to add before span - before: Changes<'a>, - /// Span to add in between - inner: &'a Span, - /// Changes to add after span - after: Changes<'a>, - }, - Replace { - str: Changes<'a>, - }, + Insert { loc: u32, str: Changes<'a> }, + Replace { str: Changes<'a> }, } pub(crate) struct JsChangeResult { @@ -229,7 +353,7 @@ pub(crate) struct JsChangeResult { } pub(crate) struct JsChanges { - pub inner: Vec, + pub inner: Vec, } impl JsChanges { @@ -238,7 +362,7 @@ impl JsChanges { } pub fn add(&mut self, change: Rewrite) { - self.inner.push(change); + self.inner.extend(change.into_inner().into_iter()); } pub fn perform(&mut self, js: &str, cfg: &Config) -> Result @@ -267,30 +391,13 @@ impl JsChanges { let inner = change.to_inner(cfg); match inner { - JsChangeInner::Wrap { - before, - inner: wrapspan, - after, - } => { - // wrap op - for str in before { - buffer.extend_from_slice(str.as_bytes()); - } - - let wrapstart = wrapspan.start as usize; - let wrapend = wrapspan.end as usize; - buffer.extend_from_slice( - js.get(wrapstart..wrapend) - .ok_or_else(|| RewriterError::Oob(wrapstart..wrapend))? - .as_bytes(), - ); - - for str in after { + JsChangeInner::Insert { loc, str } => { + for str in str { buffer.extend_from_slice(str.as_bytes()); } } - JsChangeInner::Replace(list) => { - for str in list { + JsChangeInner::Replace { str } => { + for str in str { buffer.extend_from_slice(str.as_bytes()); } } diff --git a/rewriter/rewriter/src/lib.rs b/rewriter/rewriter/src/lib.rs index e65a807..675a968 100644 --- a/rewriter/rewriter/src/lib.rs +++ b/rewriter/rewriter/src/lib.rs @@ -1,7 +1,7 @@ use std::ops::Range; use cfg::Config; -use changes::{Rewrite, JsChangeResult, JsChanges}; +use changes::{JsChangeResult, JsChanges, Rewrite}; use oxc::{ allocator::Allocator, ast::Visit, @@ -18,8 +18,8 @@ mod visitor; #[derive(Error, Debug)] pub enum RewriterError { - #[error("oxc panicked in parser")] - OxcPanicked, + #[error("oxc panicked in parser: {0:?}")] + OxcPanicked(Vec), #[error("out of bounds while applying range: {0:?})")] Oob(Range), } @@ -29,7 +29,6 @@ pub struct RewriteResult { pub js: Vec, pub sourcemap: Vec, pub errors: Vec, - pub changes: Vec, } pub fn rewrite(js: &str, config: Config) -> Result @@ -48,7 +47,7 @@ where .parse(); if ret.panicked { - return Err(RewriterError::OxcPanicked); + return Err(RewriterError::OxcPanicked(ret.errors)); } let mut visitor = Visitor { @@ -62,12 +61,10 @@ where } = visitor; let JsChangeResult { js, sourcemap } = jschanges.perform(js, &config)?; - let JsChanges { inner: changes } = jschanges; Ok(RewriteResult { js, sourcemap, errors: ret.errors, - changes, }) }