rewrite... TWO!!

This commit is contained in:
Toshit Chawda 2024-12-13 18:46:58 -08:00
parent 6d03da6d85
commit 6edad29dbb
No known key found for this signature in database
GPG key ID: 91480ED99E2B3D9D
9 changed files with 273 additions and 135 deletions

View file

@ -65,8 +65,6 @@ fn main() -> Result<()> {
eprintln!("{}", err.with_source_code(source.clone())); eprintln!("{}", err.with_source_code(source.clone()));
} }
println!("changes: {:#?}", res.changes);
println!( println!(
"rewritten:\n{}", "rewritten:\n{}",
String::from_utf8(res.js).context("failed to parse rewritten js")? String::from_utf8(res.js).context("failed to parse rewritten js")?
@ -160,6 +158,10 @@ function $wrap(val) {
return val; return val;
} }
const $gwrap = $wrap;
function $scramitize(val) { return val }
function assert(val) { function assert(val) {
if (!val) fail(); if (!val) fail();
} }
@ -172,7 +174,7 @@ function check(val) {
.unwrap(); .unwrap();
let rewritten = dorewrite(&content).unwrap(); let rewritten = dorewrite(&content).unwrap();
println!("{:?}", rewritten); println!("{}", std::str::from_utf8(&rewritten.js).unwrap());
context.eval(Source::from_bytes(&rewritten.js)).unwrap(); context.eval(Source::from_bytes(&rewritten.js)).unwrap();
println!("PASS"); println!("PASS");

View file

@ -0,0 +1,5 @@
check(window);
check(this);
check(globalThis);
check(location);
check(globalThis["win" + "dow"])

View file

@ -0,0 +1,3 @@
const { location: x } = globalThis;
check(x);

View file

@ -0,0 +1,6 @@
function f(g = globalThis, l = location) {
check(g);
check(l);
}
f();

View file

@ -0,0 +1,7 @@
function f(location, globalThis) {
assert(location === 1)
assert(globalThis === 2)
}
f(1, 2);

View file

@ -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();

View file

@ -0,0 +1,3 @@
for (location of ["https://google.com"]) {
//
}

View file

@ -7,7 +7,7 @@ use smallvec::{smallvec, SmallVec};
use crate::{cfg::Config, RewriterError}; use crate::{cfg::Config, RewriterError};
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum Rewrite { pub(crate) enum Rewrite {
/// `(cfg.wrapfn(ident))` | `cfg.wrapfn(ident)` /// `(cfg.wrapfn(ident))` | `cfg.wrapfn(ident)`
WrapFn { WrapFn {
span: Span, span: Span,
@ -52,7 +52,7 @@ pub enum Rewrite {
rhsspan: Span, rhsspan: Span,
op: AssignmentOperator, op: AssignmentOperator,
}, },
/// `ident,` -> `ident: cfg.wrapfn(ident)` /// `ident,` -> `ident: cfg.wrapfn(ident),`
ShorthandObj { ShorthandObj {
span: Span, span: Span,
name: CompactStr, name: CompactStr,
@ -73,93 +73,228 @@ pub enum Rewrite {
} }
impl Rewrite { impl Rewrite {
pub fn get_span(&self) -> &Span { fn into_inner(self) -> SmallVec<[JsChange; 4]> {
match self { match self {
Self::WrapFn { span, .. } => span, Self::WrapFn { wrapped, span } => {
Self::SetRealmFn { span, .. } => span, let start = Span::new(span.start, span.start);
Self::WrapThisFn { span } => span, let end = Span::new(span.end, span.end);
Self::ImportFn { span } => span, if wrapped {
Self::MetaFn { span } => span, 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::ScramErr { span, .. } => {
Self::Scramitize { span } => 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::Eval { inner, span } => smallvec![
Self::Assignment { entirespan, .. } => entirespan, JsChange::EvalRewriteFn {
Self::ShorthandObj { span, .. } => span, span: Span::new(span.start, inner.start)
Self::SourceTag { span, .. } => span, },
JsChange::ReplaceClosingParen {
Self::Replace { span, .. } => span, span: Span::new(inner.end, span.end),
Self::Delete { span } => span, }
],
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 }],
}
}
}
#[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>)
// 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<E>) -> JsChangeInner<'a> fn to_inner<'a, E>(&'a self, cfg: &'a Config<E>) -> JsChangeInner<'a>
where where
E: Fn(String) -> String, E: Fn(String) -> String,
E: Clone, E: Clone,
{ {
match self { match self {
Self::WrapFn { wrapped, span } => { Self::WrapFn { span, extra } => {
if *wrapped { if *extra {
JsChangeInner::Wrap { JsChangeInner::Insert {
before: smallvec!["(", cfg.wrapfn.as_str(), "("], loc: span.start,
inner: span, str: smallvec!["(", cfg.wrapfn.as_str(), "("],
after: smallvec!["))"],
} }
} else { } else {
JsChangeInner::Wrap { JsChangeInner::Insert {
before: smallvec![cfg.wrapfn.as_str(), "("], loc: span.start,
inner: span, str: smallvec![cfg.wrapfn.as_str(), "("],
after: smallvec![")"],
} }
} }
} }
Self::SetRealmFn { span } => JsChangeInner::Wrap { Self::SetRealmFn { span } => JsChangeInner::Insert {
before: smallvec![cfg.setrealmfn.as_str(), "({})."], loc: span.start,
inner: span, str: smallvec![cfg.setrealmfn.as_str(), "({})."],
after: smallvec![],
}, },
Self::WrapThisFn { span } => JsChangeInner::Wrap { Self::WrapThisFn { span } => JsChangeInner::Insert {
before: smallvec![cfg.wrapthisfn.as_str(), "("], loc: span.start,
inner: span, str: smallvec![cfg.wrapthisfn.as_str(), "("],
after: smallvec![")"],
}, },
Self::ImportFn { .. } => JsChangeInner::Replace(smallvec![ Self::ScramErrFn { span } => JsChangeInner::Insert {
"(", loc: span.start,
cfg.importfn.as_str(), str: smallvec!["$scramerr("],
"(\"",
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::ScramitizeFn { span } => JsChangeInner::Insert {
Self::Eval { inner, .. } => JsChangeInner::Wrap { loc: span.start,
before: smallvec!["eval(", cfg.rewritefn.as_str(), "("], str: smallvec!["$scramitize("],
inner,
after: smallvec![")"],
}, },
Self::Assignment { Self::EvalRewriteFn { .. } => JsChangeInner::Replace {
name, rhsspan, op, .. str: smallvec!["eval(", cfg.rewritefn.as_str(), "("],
} => JsChangeInner::Wrap { },
before: smallvec![ 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(", "((t)=>$scramjet$tryset(",
name.as_str(), name.as_str(),
",\"", ",\"",
@ -167,41 +302,39 @@ impl Rewrite {
"\",t)||(", "\",t)||(",
name.as_str(), name.as_str(),
op.as_str(), op.as_str(),
"t))(" "t)("
], ],
inner: rhsspan,
after: smallvec![")"],
}, },
// maps to insert Self::ReplaceClosingParen { span } => JsChangeInner::Replace {
Self::ShorthandObj { name, .. } => JsChangeInner::Replace(smallvec![ str: smallvec![")"],
name.as_str(), },
":", Self::ClosingParen { span, semi } => JsChangeInner::Insert {
cfg.wrapfn.as_str(), loc: span.start,
"(", str: if *semi {
name.as_str(), smallvec![");"]
")" } else {
]), smallvec![")"]
// maps to insert },
Self::SourceTag { tagname, .. } => JsChangeInner::Replace(smallvec![ },
"/*scramtag ", Self::DoubleClosingParen { span } => JsChangeInner::Insert {
tagname.as_str(), loc: span.start,
" ", str: smallvec!["))"],
cfg.sourcetag.as_str(), },
"*/" Self::Replace { span, text } => JsChangeInner::Replace {
]), str: smallvec![text.as_str()],
Self::Replace { text, .. } => JsChangeInner::Replace(smallvec![text.as_str()]), },
Self::Delete { .. } => JsChangeInner::Replace(smallvec![]), Self::Delete { span } => JsChangeInner::Replace { str: smallvec![""] },
} }
} }
} }
impl PartialOrd for Rewrite { impl PartialOrd for JsChange {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl Ord for Rewrite { impl Ord for JsChange {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.get_span().start.cmp(&other.get_span().start) self.get_span().start.cmp(&other.get_span().start)
} }
@ -210,17 +343,8 @@ impl Ord for Rewrite {
type Changes<'a> = SmallVec<[&'a str; 8]>; type Changes<'a> = SmallVec<[&'a str; 8]>;
enum JsChangeInner<'a> { enum JsChangeInner<'a> {
Wrap { Insert { loc: u32, str: Changes<'a> },
/// Changes to add before span Replace { str: Changes<'a> },
before: Changes<'a>,
/// Span to add in between
inner: &'a Span,
/// Changes to add after span
after: Changes<'a>,
},
Replace {
str: Changes<'a>,
},
} }
pub(crate) struct JsChangeResult { pub(crate) struct JsChangeResult {
@ -229,7 +353,7 @@ pub(crate) struct JsChangeResult {
} }
pub(crate) struct JsChanges { pub(crate) struct JsChanges {
pub inner: Vec<Rewrite>, pub inner: Vec<JsChange>,
} }
impl JsChanges { impl JsChanges {
@ -238,7 +362,7 @@ impl JsChanges {
} }
pub fn add(&mut self, change: Rewrite) { pub fn add(&mut self, change: Rewrite) {
self.inner.push(change); self.inner.extend(change.into_inner().into_iter());
} }
pub fn perform<E>(&mut self, js: &str, cfg: &Config<E>) -> Result<JsChangeResult, RewriterError> pub fn perform<E>(&mut self, js: &str, cfg: &Config<E>) -> Result<JsChangeResult, RewriterError>
@ -267,30 +391,13 @@ impl JsChanges {
let inner = change.to_inner(cfg); let inner = change.to_inner(cfg);
match inner { match inner {
JsChangeInner::Wrap { JsChangeInner::Insert { loc, str } => {
before, for str in str {
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 {
buffer.extend_from_slice(str.as_bytes()); buffer.extend_from_slice(str.as_bytes());
} }
} }
JsChangeInner::Replace(list) => { JsChangeInner::Replace { str } => {
for str in list { for str in str {
buffer.extend_from_slice(str.as_bytes()); buffer.extend_from_slice(str.as_bytes());
} }
} }

View file

@ -1,7 +1,7 @@
use std::ops::Range; use std::ops::Range;
use cfg::Config; use cfg::Config;
use changes::{Rewrite, JsChangeResult, JsChanges}; use changes::{JsChangeResult, JsChanges, Rewrite};
use oxc::{ use oxc::{
allocator::Allocator, allocator::Allocator,
ast::Visit, ast::Visit,
@ -18,8 +18,8 @@ mod visitor;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum RewriterError { pub enum RewriterError {
#[error("oxc panicked in parser")] #[error("oxc panicked in parser: {0:?}")]
OxcPanicked, OxcPanicked(Vec<OxcDiagnostic>),
#[error("out of bounds while applying range: {0:?})")] #[error("out of bounds while applying range: {0:?})")]
Oob(Range<usize>), Oob(Range<usize>),
} }
@ -29,7 +29,6 @@ pub struct RewriteResult {
pub js: Vec<u8>, pub js: Vec<u8>,
pub sourcemap: Vec<u8>, pub sourcemap: Vec<u8>,
pub errors: Vec<OxcDiagnostic>, pub errors: Vec<OxcDiagnostic>,
pub changes: Vec<Rewrite>,
} }
pub fn rewrite<E>(js: &str, config: Config<E>) -> Result<RewriteResult, RewriterError> pub fn rewrite<E>(js: &str, config: Config<E>) -> Result<RewriteResult, RewriterError>
@ -48,7 +47,7 @@ where
.parse(); .parse();
if ret.panicked { if ret.panicked {
return Err(RewriterError::OxcPanicked); return Err(RewriterError::OxcPanicked(ret.errors));
} }
let mut visitor = Visitor { let mut visitor = Visitor {
@ -62,12 +61,10 @@ where
} = visitor; } = visitor;
let JsChangeResult { js, sourcemap } = jschanges.perform(js, &config)?; let JsChangeResult { js, sourcemap } = jschanges.perform(js, &config)?;
let JsChanges { inner: changes } = jschanges;
Ok(RewriteResult { Ok(RewriteResult {
js, js,
sourcemap, sourcemap,
errors: ret.errors, errors: ret.errors,
changes,
}) })
} }