refactor(es/minifier): Remove unused crates (#3395)

This commit is contained in:
Donny/강동윤 2022-01-28 20:17:08 +09:00 committed by GitHub
parent 839d0ac480
commit 74b433080b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 8 additions and 2131 deletions

View File

@ -107,7 +107,6 @@ jobs:
- swc_ecma_codegen
- swc_ecma_codegen_macros
- swc_ecma_dep_graph
- swc_ecma_diff
- swc_ecma_ext_transforms
- swc_ecma_lints
- swc_ecma_loader

73
Cargo.lock generated
View File

@ -620,35 +620,6 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "dbg-swc"
version = "0.10.0"
dependencies = [
"anyhow",
"pretty_assertions 1.0.0",
"rayon",
"structopt",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_codegen",
"swc_ecma_dep_graph",
"swc_ecma_diff",
"swc_ecma_loader",
"swc_ecma_minifier",
"swc_ecma_parser",
"swc_ecma_transforms_base",
"swc_ecma_transforms_react",
"swc_ecma_transforms_typescript",
"swc_ecma_utils",
"swc_ecma_visit",
"swc_node_comments",
"swc_timer",
"tempfile",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "debug_unreachable"
version = "0.1.1"
@ -1849,18 +1820,6 @@ dependencies = [
"output_vt100",
]
[[package]]
name = "pretty_assertions"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc"
dependencies = [
"ansi_term",
"ctor",
"diff",
"output_vt100",
]
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
@ -2937,7 +2896,7 @@ dependencies = [
name = "swc_ecma_dep_graph"
version = "0.58.0"
dependencies = [
"pretty_assertions 0.7.2",
"pretty_assertions",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
@ -2946,22 +2905,6 @@ dependencies = [
"testing",
]
[[package]]
name = "swc_ecma_diff"
version = "0.6.0"
dependencies = [
"ansi_term",
"auto_impl",
"num-bigint",
"string_cache",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_codegen",
"swc_ecma_parser",
"testing",
]
[[package]]
name = "swc_ecma_ext_transforms"
version = "0.50.0"
@ -3021,7 +2964,7 @@ dependencies = [
"backtrace",
"indexmap",
"once_cell",
"pretty_assertions 0.7.2",
"pretty_assertions",
"rayon",
"regex",
"retain_mut",
@ -3052,7 +2995,7 @@ dependencies = [
"enum_kind",
"lexical",
"num-bigint",
"pretty_assertions 0.7.2",
"pretty_assertions",
"serde",
"serde_json",
"smallvec",
@ -3078,7 +3021,7 @@ dependencies = [
"dashmap",
"indexmap",
"once_cell",
"pretty_assertions 0.7.2",
"pretty_assertions",
"semver 1.0.4",
"serde",
"serde_json",
@ -3100,7 +3043,7 @@ dependencies = [
name = "swc_ecma_transforms"
version = "0.114.3"
dependencies = [
"pretty_assertions 0.7.2",
"pretty_assertions",
"sourcemap",
"swc_atoms",
"swc_common",
@ -3408,7 +3351,7 @@ dependencies = [
"ahash",
"anyhow",
"copyless",
"pretty_assertions 0.7.2",
"pretty_assertions",
"rayon",
"serde",
"serde_json",
@ -3473,7 +3416,7 @@ dependencies = [
"dashmap",
"is-macro",
"once_cell",
"pretty_assertions 0.7.2",
"pretty_assertions",
"regex",
"serde",
"serde_json",
@ -3661,7 +3604,7 @@ dependencies = [
"ansi_term",
"difference",
"once_cell",
"pretty_assertions 0.7.2",
"pretty_assertions",
"regex",
"serde_json",
"swc_common",

View File

@ -1,12 +1,10 @@
[workspace]
members = [
"crates/dbg-swc",
"crates/jsdoc",
"crates/node",
"crates/swc_cli",
"crates/swc_css",
"crates/swc_ecmascript",
"crates/swc_ecma_diff",
"crates/swc_ecma_lints",
"crates/swc_estree_compat",
"crates/swc_plugin",

View File

@ -1,37 +0,0 @@
[package]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
description = "Debug tools for swc"
documentation = "https://rustdoc.swc.rs/dbg-swc/"
edition = "2021"
include = ["Cargo.toml", "src/**/*.rs"]
license = "Apache-2.0"
name = "dbg-swc"
repository = "https://github.com/swc-project/swc.git"
version = "0.10.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1"
pretty_assertions = "1.0.0"
rayon = "1"
structopt = "0.3.25"
swc_atoms = {version = "0.2.9", path = "../swc_atoms/"}
swc_common = {version = "0.17.0", path = "../swc_common/", features = ["concurrent"]}
swc_ecma_ast = {version = "0.65.0", path = "../swc_ecma_ast/"}
swc_ecma_codegen = {version = "0.89.0", path = "../swc_ecma_codegen"}
swc_ecma_dep_graph = {version = "0.58.0", path = "../swc_ecma_dep_graph/"}
swc_ecma_diff = {version = "0.6.0", path = "../swc_ecma_diff/"}
swc_ecma_loader = {version = "0.28.0", path = "../swc_ecma_loader/", features = ["lru", "node", "tsc"]}
swc_ecma_minifier = {version = "0.71.0", path = "../swc_ecma_minifier/"}
swc_ecma_parser = {version = "0.87.0", path = "../swc_ecma_parser/"}
swc_ecma_transforms_base = {version = "0.57.0", path = "../swc_ecma_transforms_base/"}
swc_ecma_transforms_react = {version = "0.77.0", path = "../swc_ecma_transforms_react/"}
swc_ecma_transforms_typescript = {version = "0.79.0", path = "../swc_ecma_transforms_typescript/"}
swc_ecma_utils = {version = "0.64.0", path = "../swc_ecma_utils/"}
swc_ecma_visit = {version = "0.51.0", path = "../swc_ecma_visit/"}
swc_node_comments = {version = "0.4.0", path = "../swc_node_comments/"}
swc_timer = {version = "0.4.0", path = "../swc_timer/"}
tempfile = "3.2.0"
tracing = "0.1.29"
tracing-subscriber = {version = "0.3.5", features = ["env-filter"]}

View File

@ -1,13 +0,0 @@
# dbg-swc
## `dbg-swc reduce-min`
```
dbg-swc reduce-min --build '' --test 'npm test.spec' --working-dir ~/src/ionic-framework/packages/react/ ~/src/ionic-framework/packages/react/src/index.ts
```
(TODO) e.g. next.js app
```sh
dbg-swc reduce-min --build 'rm -rf .next && npx next build' --test 'dbg-swc grab-console http://localhost:3000 --script test.js --start "npm start"' ~/your/next/app/pages/index.js ~/your/next/app/pages/index.js
```

View File

@ -1,4 +0,0 @@
#!/usr/bin/env bash
set -eu
cargo install --locked --debug --path .

View File

@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -eu
cargo run -- $@

View File

@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -eu
./scripts/install.sh
dbg-swc reduce-min --build 'rm -rf .next && npx next build' --test 'dbg-swc grab-console http://localhost:3000 --script test.js --start "npm start"' $@

View File

@ -1,71 +0,0 @@
use self::{minified::DiffMinifiedCommand, reduce_min::ReduceMinCommand};
use anyhow::Result;
use std::{env, io::stderr, str::FromStr, sync::Arc, time::Instant};
use structopt::StructOpt;
use swc_common::{
errors::{Handler, HANDLER},
SourceMap, GLOBALS,
};
use tracing_subscriber::EnvFilter;
mod minified;
mod reduce_min;
mod util;
#[derive(Debug, StructOpt)]
enum Cmd {
DiffMin(DiffMinifiedCommand),
ReduceMin(ReduceMinCommand),
}
struct Track {
start: Instant,
}
impl Drop for Track {
fn drop(&mut self) {
eprintln!("Done in {:?}", self.start.elapsed());
}
}
fn main() -> Result<()> {
let globals = swc_common::Globals::default();
let cm = Arc::new(SourceMap::default());
let handler = Handler::with_emitter_writer(Box::new(stderr()), Some(cm.clone()));
let _logger = {
let log_env = env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string());
let logger = tracing_subscriber::FmtSubscriber::builder()
.without_time()
.with_target(false)
.with_ansi(true)
.with_env_filter(EnvFilter::from_str(&log_env).unwrap())
.with_test_writer()
.pretty()
.finish();
tracing::subscriber::set_global_default(logger)
};
let _track = Track {
start: Instant::now(),
};
let cmd = Cmd::from_args();
GLOBALS.set(&globals, || {
HANDLER.set(&handler, || {
match cmd {
Cmd::DiffMin(cmd) => {
cmd.run(cm.clone())?;
}
Cmd::ReduceMin(cmd) => {
cmd.run(cm.clone())?;
}
}
Ok(())
})
})
}

View File

@ -1,192 +0,0 @@
use crate::util::{parse, print_js};
use anyhow::{bail, Context, Result};
use std::{
io::Write,
path::PathBuf,
process::{Command, Stdio},
sync::Arc,
};
use structopt::StructOpt;
use swc_common::{util::take::Take, FileName, Mark, SourceMap};
use swc_ecma_ast::*;
use swc_ecma_diff::Diff;
use swc_ecma_minifier::option::{ExtraOptions, MinifyOptions};
use swc_ecma_transforms_base::{fixer::fixer, resolver::resolver_with_mark};
use swc_ecma_visit::{VisitMut, VisitMutWith};
use tempfile::NamedTempFile;
/// Diff the output of swc minifier and terser.
#[derive(Debug, StructOpt)]
pub(crate) struct DiffMinifiedCommand {
input: PathBuf,
#[structopt(long)]
no_mangle: bool,
}
impl DiffMinifiedCommand {
pub fn run(self, cm: Arc<SourceMap>) -> Result<()> {
let terser_output = self
.get_output_from_terser()
.context("failed to get output from terser")?;
let fm = cm
.load_file(&self.input)
.context("failed to load input file")?;
let top_level_mark = Mark::fresh(Mark::root());
let mut swc_module = {
let mut m = parse(&fm).context("failed to parse input file using swc")?;
m.visit_mut_with(&mut resolver_with_mark(top_level_mark));
m.visit_mut_with(&mut fixer(None));
m.visit_mut_with(&mut Normalizer::default());
m = swc_ecma_minifier::optimize(
m,
cm.clone(),
None,
None,
&MinifyOptions {
compress: Some(Default::default()),
mangle: if self.no_mangle {
None
} else {
Some(Default::default())
},
..Default::default()
},
&ExtraOptions { top_level_mark },
);
m.visit_mut_with(&mut fixer(None));
m.visit_mut_with(&mut Normalizer::default());
m.visit_mut_with(&mut BeforeDiffNormalizer::default());
m
};
let terser_fm = cm.new_source_file(FileName::Anon, terser_output);
let mut terser_module = parse(&terser_fm)?;
{
terser_module.visit_mut_with(&mut resolver_with_mark(top_level_mark));
terser_module.visit_mut_with(&mut fixer(None));
terser_module.visit_mut_with(&mut Normalizer::default());
terser_module.visit_mut_with(&mut BeforeDiffNormalizer::default());
}
{
// Diff
let config = swc_ecma_diff::Config { ignore_span: true };
let mut ctx = swc_ecma_diff::Ctx::new(config);
let diff_res = swc_module.diff(&mut terser_module, &mut ctx);
eprintln!("Diff: \n{}", diff_res);
}
swc_module.visit_mut_with(&mut Normalizer::default());
terser_module.visit_mut_with(&mut Normalizer::default());
let swc_output = print_js(cm.clone(), &swc_module, None).context("failed to print js")?;
let terser_output = print_js(cm, &terser_module, None).context("failed to print js")?;
if swc_output == terser_output {
return Ok(());
}
let mut swc_file = NamedTempFile::new()?;
writeln!(swc_file, "{}", swc_output)?;
let mut terser_file = NamedTempFile::new()?;
writeln!(terser_file, "{}", terser_output)?;
Command::new("code")
.arg("--wait")
.arg("--diff")
.arg(&swc_file.path())
.arg(&terser_file.path())
.status()?;
Ok(())
}
/// Invoke `terser`
fn get_output_from_terser(&self) -> Result<String> {
let mut c = Command::new("terser");
c.arg("--compress");
if !self.no_mangle {
c.arg("--mangle");
}
let output = c
.arg("--")
.arg(&self.input)
.stderr(Stdio::inherit())
.output()
.context("failed to run terser")?;
if !output.status.success() {
bail!("terser failed with status code {}", output.status);
}
let src = String::from_utf8(output.stdout).context("failed to parse terser output")?;
Ok(src)
}
}
#[derive(Debug, Default)]
struct Normalizer {}
impl VisitMut for Normalizer {
fn visit_mut_new_expr(&mut self, e: &mut NewExpr) {
e.visit_mut_children_with(self);
if let Some(args) = &e.args {
if args.is_empty() {
e.args = None;
}
}
}
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
s.visit_mut_children_with(self);
if let Stmt::Decl(Decl::Var(v)) = s {
if v.decls.is_empty() {
s.take();
}
}
}
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
stmts.visit_mut_children_with(self);
stmts.retain(|s| !matches!(s, Stmt::Empty(..)));
}
}
#[derive(Debug, Default)]
struct BeforeDiffNormalizer {}
impl VisitMut for BeforeDiffNormalizer {
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
s.visit_mut_children_with(self);
if let Stmt::Block(bs) = s {
if bs.stmts.len() == 1 {
*s = bs.stmts[0].take();
}
}
}
fn visit_mut_str(&mut self, s: &mut Str) {
s.visit_mut_children_with(self);
s.has_escape = false;
}
}

View File

@ -1,131 +0,0 @@
use crate::util::parse;
use anyhow::{Context, Result};
use rayon::prelude::*;
use std::{
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use swc_common::{
collections::AHashMap, comments::NoopComments, errors::HANDLER, FileName, SourceFile,
SourceMap, GLOBALS,
};
use swc_ecma_loader::{resolve::Resolve, resolvers::node::NodeModulesResolver, TargetEnv};
use tracing::info;
pub fn collect_deps(cm: Arc<SourceMap>, working_dir: &Path, entry: &Path) -> Result<Vec<PathBuf>> {
let collector = DependencyCollector {
cm,
working_dir: working_dir.to_path_buf(),
cache: Default::default(),
resolver: Box::new(NodeModulesResolver::new(
TargetEnv::Node,
Default::default(),
false,
)),
};
collector.load_recursively(Arc::new(FileName::Real(entry.to_path_buf())))?;
let files = collector.cache.into_inner()?;
Ok(files
.into_iter()
.map(|(_, file)| file.fm.name.clone())
.filter_map(|f| match f {
FileName::Real(v) => Some(v),
_ => None,
})
.collect())
}
struct DependencyCollector {
cm: Arc<SourceMap>,
#[allow(unused)]
working_dir: PathBuf,
cache: Mutex<AHashMap<Arc<FileName>, Arc<ModuleData>>>,
resolver: Box<dyn Resolve>,
}
impl DependencyCollector {
fn load_recursively(&self, name: Arc<FileName>) -> Result<()> {
if self.cache.lock().unwrap().contains_key(&name) {
return Ok(());
}
info!("Loading {}", name);
let fm = match &*name {
FileName::Real(path) => self.cm.load_file(path)?,
FileName::Custom(..) => return Ok(()),
_ => {
todo!("load({:?})", name)
}
};
self.cache
.lock()
.unwrap()
.insert(name.clone(), Arc::new(ModuleData { fm: fm.clone() }));
if let FileName::Real(name) = &*name {
if let Some(ext) = name.extension() {
if ext == "json" {
return Ok(());
}
}
}
let module = parse(&fm)?;
let deps = swc_ecma_dep_graph::analyze_dependencies(&module, &NoopComments);
let deps = deps
.into_iter()
.filter(|dep| &*dep.specifier != "next")
.collect::<Vec<_>>();
let _res = GLOBALS.with(|globals| {
HANDLER.with(|handler| {
deps.into_par_iter()
.map(|dep| {
GLOBALS.set(globals, || {
HANDLER.set(handler, || {
let res = self
.resolver
.resolve(&fm.name, &dep.specifier)
.with_context(|| {
format!(
"failed to resolve `{}` from `{}`",
dep.specifier, fm.name
)
})?;
Ok((res, dep))
})
})
})
.map(|res| {
GLOBALS.set(globals, || {
HANDLER.set(handler, || {
res.and_then(|(name, dep)| {
let name = Arc::new(name);
self.load_recursively(name.clone()).with_context(|| {
format!("failed to load `{}` (`{}`)", name, dep.specifier)
})
})
})
})
})
.collect::<Result<_>>()
})
})?;
Ok(())
}
}
struct ModuleData {
fm: Arc<SourceFile>,
}

View File

@ -1,241 +0,0 @@
use self::{deps::collect_deps, types::ignore_typescript};
use crate::util::{parse, print_js};
use anyhow::{bail, Context, Result};
use std::{
path::PathBuf,
process::{Command, Stdio},
sync::Arc,
};
use structopt::StructOpt;
use swc_common::{comments::NoopComments, Mark, SourceMap};
use swc_ecma_minifier::option::{ExtraOptions, MinifyOptions};
use swc_ecma_transforms_base::{
fixer::fixer,
helpers::{inject_helpers, Helpers, HELPERS},
hygiene::hygiene,
resolver::resolver_with_mark,
};
use swc_ecma_transforms_typescript::strip_with_jsx;
use swc_ecma_visit::{FoldWith, VisitMutWith};
use swc_node_comments::SwcComments;
use swc_timer::timer;
use tracing::{info, span, Level};
mod deps;
mod types;
/// This tool repeat replacing one file with a minified form at a time.
#[derive(Debug, StructOpt)]
pub(crate) struct ReduceMinCommand {
entry: PathBuf,
#[structopt(long)]
working_dir: PathBuf,
/// This command is invoked using `bash`.
#[structopt(long = "build")]
build_command: String,
/// This command is invoked using `bash`.
#[structopt(long = "test")]
test_command: String,
}
impl ReduceMinCommand {
pub(crate) fn run(self, cm: Arc<SourceMap>) -> Result<()> {
let all_files = {
let _timer = timer!("collect list of files to patch");
collect_deps(cm.clone(), &self.working_dir, &self.entry)?
};
let mut runner = Runner {
cm,
comments: Default::default(),
working_dir: self.working_dir,
build_command: self.build_command,
test_command: self.test_command,
expected: Default::default(),
};
{
let _span = span!(Level::ERROR, "initial run, without minification").entered();
runner.expected = runner.check().context("initial check failed")?;
}
runner.run(all_files)
}
}
struct Runner {
cm: Arc<SourceMap>,
comments: SwcComments,
working_dir: PathBuf,
build_command: String,
test_command: String,
expected: String,
}
/// Restores original content on drop
struct Patch {
path: PathBuf,
orig: Arc<String>,
}
impl Drop for Patch {
fn drop(&mut self) {
let _ = std::fs::write(&self.path, self.orig.as_bytes());
}
}
impl Runner {
fn patch_file(&mut self, path: PathBuf) -> Result<(Patch, String)> {
(|| -> Result<_> {
let fm = self.cm.load_file(&path).context("failed to load file")?;
let top_level_mark = Mark::fresh(Mark::root());
let mut m = parse(&fm).context("failed to parse input file using swc")?;
let helpers = Helpers::new(false);
m = HELPERS.set(&helpers, || {
let mut m = m;
m.visit_mut_with(&mut resolver_with_mark(top_level_mark));
m.visit_mut_with(&mut strip_with_jsx(
self.cm.clone(),
swc_ecma_transforms_typescript::Config {
..Default::default()
},
NoopComments,
top_level_mark,
));
m = m.fold_with(&mut swc_ecma_transforms_react::react(
self.cm.clone(),
None::<NoopComments>,
Default::default(),
top_level_mark,
));
m = swc_ecma_minifier::optimize(
m,
self.cm.clone(),
None,
None,
&MinifyOptions {
compress: Some(Default::default()),
mangle: None,
..Default::default()
},
&ExtraOptions { top_level_mark },
);
m.visit_mut_with(&mut inject_helpers());
m.visit_mut_with(&mut hygiene());
m.visit_mut_with(&mut fixer(None));
if let Some(ext) = path.extension() {
if ext == "tsx" || ext == "ts" {
m.visit_mut_with(&mut ignore_typescript(self.comments.clone()));
}
}
m
});
let mut patched = print_js(self.cm.clone(), &m, Some(&self.comments))?;
// Ignore helpers injected by swc
macro_rules! ignore {
($s:expr) => {{
patched = patched.replace($s, concat!("//@ts-ignore\n", $s));
}};
}
// TODO: Find better way
ignore!("function _");
ignore!("_extends = ");
ignore!("return _extends.apply(this, arguments)");
std::fs::write(&path, patched.as_bytes()).context("failed to write patched content")?;
let patch = Patch {
path: path.clone(),
orig: fm.src.clone(),
};
Ok((patch, patched))
})()
.with_context(|| format!("failed to patch file: {}", path.display()))
}
fn run(mut self, files: Vec<PathBuf>) -> Result<()> {
// Patch one file at a time.
for file in files {
if let Some(ext) = file.extension() {
if ext == "json" {
info!("Skipping json file");
continue;
}
}
let _span = span!(Level::ERROR, "patch", file = &*file.display().to_string()).entered();
let (_patch, patched) = self.patch_file(file.clone())?;
let actual = self.check().with_context(|| {
format!(
"test failed for `{}`\nPatched:\n{}",
file.display(),
patched
)
})?;
if actual != self.expected {
bail!("expected: {}, actual: {}", self.expected, actual);
}
}
Ok(())
}
/// Build, test, and grab the console output.
fn check(&mut self) -> Result<String> {
{
info!("Building app");
let mut cmd = Command::new("bash");
let status = cmd
.current_dir(&self.working_dir)
.arg("-c")
.arg(&self.build_command)
.status()
.context("failed to run build command")?;
if !status.success() {
bail!("build failed");
}
}
info!("Testing app");
let mut cmd = Command::new("bash");
let output = cmd
.current_dir(&self.working_dir)
.arg("-c")
.arg(&self.test_command)
.stderr(Stdio::inherit())
.output()
.context("failed to get test output")?;
if !output.status.success() {
bail!("test failed");
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
}

View File

@ -1,88 +0,0 @@
use swc_common::{
comments::{Comment, CommentKind, Comments},
Spanned, DUMMY_SP,
};
use swc_ecma_ast::*;
use swc_ecma_visit::{VisitMut, VisitMutWith};
use swc_node_comments::SwcComments;
/// Add `@ts-ignore` to all statements.
pub fn ignore_typescript(comments: SwcComments) -> impl VisitMut {
AddTypes {
comments,
should_not_annotate_type: Default::default(),
}
}
struct AddTypes {
comments: SwcComments,
should_not_annotate_type: bool,
}
impl VisitMut for AddTypes {
///
/// - `(this.foo)` => `(this as any).foo`
fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) {
e.visit_mut_children_with(self);
if let Expr::This(..) = &*e.obj {
e.obj = Expr::Paren(ParenExpr {
span: DUMMY_SP,
expr: Box::new(Expr::TsAs(TsAsExpr {
span: DUMMY_SP,
expr: e.obj.clone(),
type_ann: any_type(),
})),
})
.into();
}
}
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
s.visit_mut_children_with(self);
self.comments.add_leading(
s.span().lo,
Comment {
kind: CommentKind::Line,
span: DUMMY_SP,
text: "@ts-ignore".into(),
},
);
}
fn visit_mut_var_decl_or_expr(&mut self, n: &mut VarDeclOrExpr) {
let old = self.should_not_annotate_type;
self.should_not_annotate_type = true;
n.visit_mut_children_with(self);
self.should_not_annotate_type = old;
}
fn visit_mut_var_decl_or_pat(&mut self, n: &mut VarDeclOrPat) {
let old = self.should_not_annotate_type;
self.should_not_annotate_type = true;
n.visit_mut_children_with(self);
self.should_not_annotate_type = old;
}
fn visit_mut_var_declarator(&mut self, v: &mut VarDeclarator) {
v.visit_mut_children_with(self);
if !self.should_not_annotate_type {
if let Pat::Ident(id) = &mut v.name {
id.type_ann = Some(TsTypeAnn {
span: DUMMY_SP,
type_ann: any_type(),
});
}
}
}
}
fn any_type() -> Box<TsType> {
Box::new(TsType::TsKeywordType(TsKeywordType {
span: DUMMY_SP,
kind: TsKeywordTypeKind::TsAnyKeyword,
}))
}

View File

@ -1,70 +0,0 @@
use anyhow::{anyhow, Result};
use std::sync::Arc;
use swc_common::{comments::Comments, input::SourceFileInput, FileName, SourceFile, SourceMap};
use swc_ecma_ast::{EsVersion, Module};
use swc_ecma_parser::{lexer::Lexer, EsConfig, Parser, Syntax, TsConfig};
use swc_ecma_utils::HANDLER;
pub(crate) fn parse(fm: &SourceFile) -> Result<Module> {
let syntax = Syntax::Es(EsConfig {
jsx: true,
..Default::default()
});
let syntax = match &fm.name {
FileName::Real(p) => match p.extension() {
Some(ext) => {
if ext == "tsx" {
Syntax::Typescript(TsConfig {
tsx: true,
decorators: true,
..Default::default()
})
} else if ext == "ts" {
Syntax::Typescript(TsConfig {
decorators: true,
..Default::default()
})
} else {
syntax
}
}
None => syntax,
},
_ => syntax,
};
let lexer = Lexer::new(syntax, EsVersion::latest(), SourceFileInput::from(fm), None);
let mut parser = Parser::new_from(lexer);
parser.parse_module().map_err(|err| {
HANDLER.with(|handler| {
err.into_diagnostic(handler).emit();
});
anyhow!("failed to parse module")
})
}
pub(crate) fn print_js(
cm: Arc<SourceMap>,
m: &Module,
comments: Option<&dyn Comments>,
) -> Result<String> {
let mut buf = vec![];
{
let wr = swc_ecma_codegen::text_writer::JsWriter::new(cm.clone(), "\n", &mut buf, None);
let mut emitter = swc_ecma_codegen::Emitter {
cfg: swc_ecma_codegen::Config { minify: false },
cm,
comments,
wr,
};
emitter.emit_module(m)?;
}
Ok(String::from_utf8(buf)?)
}

View File

@ -1,28 +0,0 @@
[package]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
description = "Diffing tools for ECMAScript"
documentation = "https://rustdoc.swc.rs/swc_ecma_diff/"
edition = "2021"
include = ["Cargo.toml", "src/**/*.rs"]
license = "Apache-2.0"
name = "swc_ecma_diff"
repository = "https://github.com/swc-project/swc.git"
version = "0.6.0"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
ansi_term = "0.12.1"
auto_impl = "0.5.0"
num-bigint = "0.2"
string_cache = "0.8"
swc_atoms = {version = "0.2.9", path = "../swc_atoms"}
swc_common = {version = "0.17.0", path = "../swc_common"}
swc_ecma_ast = {version = "0.65.0", path = "../swc_ecma_ast"}
[dev-dependencies]
swc_ecma_codegen = {version = "0.89.0", path = "../swc_ecma_codegen"}
swc_ecma_parser = {version = "0.87.0", path = "../swc_ecma_parser"}
testing = {version = "0.18.0", path = "../testing"}

View File

@ -1,72 +0,0 @@
use crate::{Config, Diff, DiffResult};
use swc_atoms::JsWord;
/// The context for [Diff]. This contains config and path.
///
/// This is very inefficient, but it's fine as this will be used only for
/// debugging tools. I don't want to bother with lifetimes.
#[derive(Debug, Clone)]
pub struct Ctx {
pub(crate) path: Vec<PathComponent>,
pub(crate) config: Config,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PathComponent {
StructProp { struct_name: JsWord, key: JsWord },
VecElem { l: usize, r: usize },
}
impl Ctx {
pub fn new(config: Config) -> Self {
Ctx {
path: Default::default(),
config,
}
}
pub(crate) fn diff_struct<F>(&mut self, struct_name: &str, f: F) -> DiffResult
where
F: FnOnce(&mut StructDiffCtx),
{
let mut helper = StructDiffCtx {
parent: self,
results: Default::default(),
struct_name: struct_name.into(),
};
f(&mut helper);
if helper.results.is_empty() {
return DiffResult::Identical;
}
DiffResult::Multiple(helper.results)
}
}
pub(crate) struct StructDiffCtx<'a> {
parent: &'a mut Ctx,
results: Vec<DiffResult>,
struct_name: JsWord,
}
impl StructDiffCtx<'_> {
pub fn field<T>(&mut self, field_name: &str, l: &mut T, r: &mut T)
where
T: Diff,
{
let mut ctx = self.parent.clone();
ctx.path.push(PathComponent::StructProp {
struct_name: self.struct_name.clone(),
key: field_name.into(),
});
let diff = l.diff(r, &mut ctx);
if matches!(diff, DiffResult::Identical) {
return;
}
self.results.push(diff);
}
}

View File

@ -1,166 +0,0 @@
use crate::{Ctx, Diff, DiffResult, Node};
use swc_common::EqIgnoreSpan;
use swc_ecma_ast::*;
diff_enum!(
Expr,
[
This,
Array,
Object,
Fn,
Unary,
Update,
Bin,
Assign,
Member,
SuperProp,
Cond,
Call,
New,
Seq,
Ident,
Lit,
Tpl,
TaggedTpl,
Arrow,
Class,
Yield,
MetaProp,
Await,
Paren,
JSXMember,
JSXNamespacedName,
JSXEmpty,
JSXElement,
JSXFragment,
TsTypeAssertion,
TsConstAssertion,
TsNonNull,
TsAs,
PrivateName,
OptChain,
Invalid
]
);
diff_enum!(PropOrSpread, [Prop, Spread]);
diff_enum!(BlockStmtOrExpr, [BlockStmt, Expr]);
diff_enum!(SuperProp, [Computed, Ident]);
diff_enum!(Callee, [Expr, Super, Import]);
diff_enum!(Lit, [Str, Bool, Null, BigInt, Num, Regex, JSXText]);
diff_enum!(JSXAttrOrSpread, [JSXAttr, SpreadElement]);
diff_enum!(Prop, [Shorthand, KeyValue, Assign, Getter, Setter, Method]);
diff_string_enum!(MetaPropKind);
diff_struct!(ThisExpr, [span]);
diff_struct!(ArrayLit, [span, elems]);
diff_struct!(ExprOrSpread, [spread, expr]);
diff_struct!(ObjectLit, [span, props]);
diff_struct!(FnExpr, [ident, function]);
diff_struct!(UnaryExpr, [span, op, arg]);
diff_struct!(Decorator, [span, expr]);
diff_struct!(UpdateExpr, [span, prefix, op, arg]);
diff_struct!(BinExpr, [span, op, left, right]);
diff_struct!(AssignExpr, [span, op, left, right]);
diff_struct!(MemberExpr, [span, obj, prop]);
diff_struct!(SuperPropExpr, [span, obj, prop]);
diff_enum!(MemberProp, [Ident, PrivateName, Computed]);
diff_struct!(CondExpr, [span, test, cons, alt]);
diff_struct!(CallExpr, [span, callee, args, type_args]);
diff_struct!(NewExpr, [span, callee, args, type_args]);
diff_struct!(SeqExpr, [span, exprs]);
diff_struct!(TaggedTpl, [span, tag, tpl, type_params]);
diff_struct!(TplElement, [span, cooked, raw, tail]);
diff_struct!(Str, [span, value, has_escape, kind]);
diff_struct!(
ArrowExpr,
[
span,
params,
body,
is_async,
is_generator,
type_params,
return_type
]
);
diff_struct!(ClassExpr, [ident, class]);
diff_struct!(YieldExpr, [span, arg, delegate]);
diff_struct!(MetaPropExpr, [span, kind]);
diff_struct!(AwaitExpr, [span, arg]);
diff_struct!(ParenExpr, [span, expr]);
diff_struct!(JSXMemberExpr, [obj, prop]);
diff_struct!(JSXNamespacedName, [ns, name]);
diff_enum!(JSXObject, [JSXMemberExpr, Ident]);
diff_struct!(JSXEmptyExpr, [span]);
diff_struct!(JSXElement, [span, opening, children, closing]);
diff_struct!(
JSXOpeningElement,
[span, name, attrs, self_closing, type_args]
);
diff_enum!(
JSXElementChild,
[
JSXElement,
JSXText,
JSXSpreadChild,
JSXExprContainer,
JSXFragment
]
);
diff_struct!(JSXAttr, [span, name, value]);
diff_enum!(JSXAttrName, [Ident, JSXNamespacedName]);
diff_enum!(
JSXAttrValue,
[Lit, JSXExprContainer, JSXElement, JSXFragment]
);
diff_struct!(JSXClosingElement, [span, name]);
diff_struct!(JSXFragment, [span, opening, children, closing]);
diff_enum!(JSXElementName, [Ident, JSXMemberExpr, JSXNamespacedName]);
diff_struct!(JSXOpeningFragment, [span]);
diff_struct!(JSXClosingFragment, [span]);
diff_struct!(JSXSpreadChild, [span, expr]);
diff_struct!(JSXExprContainer, [span, expr]);
diff_enum!(JSXExpr, [JSXEmptyExpr, Expr]);
diff_struct!(PrivateName, [span, id]);
diff_struct!(OptChainExpr, [span, question_dot_token, expr]);
diff_struct!(SpreadElement, [dot3_token, expr]);
diff_struct!(Super, [span]);
diff_struct!(Import, [span]);
diff_struct!(Bool, [span, value]);
diff_struct!(Null, [span]);
diff_struct!(Number, [span, value]);
diff_struct!(BigInt, [span, value]);
diff_struct!(Regex, [span, exp, flags]);
diff_struct!(JSXText, [span, value, raw]);
diff_struct!(AssignProp, [key, value]);
diff_struct!(KeyValueProp, [key, value]);
diff_struct!(GetterProp, [span, key, type_ann, body]);
diff_struct!(SetterProp, [span, key, param, body]);
diff_struct!(MethodProp, [key, function]);
diff_enum!(PropName, [Ident, Str, BigInt, Num, Computed]);
diff_struct!(ComputedPropName, [span, expr]);
diff_struct!(BindingIdent, [id, type_ann]);
/// Ignored
impl Diff for StrKind {
fn diff(&mut self, _: &mut Self, _: &mut Ctx) -> DiffResult {
DiffResult::Identical
}
}
impl Diff for Tpl {
fn diff(&mut self, other: &mut Self, ctx: &mut Ctx) -> DiffResult {
if self.eq_ignore_span(&*other) {
return crate::DiffResult::Identical;
}
DiffResult::Different(crate::Difference {
path: ctx.path.clone(),
left: Node(format!("{:?}", self)),
right: Node(format!("{:?}", other)),
})
}
}

View File

@ -1,5 +0,0 @@
use swc_ecma_ast::*;
// TODO: Allow renaming of identifiers.
diff_struct!(Ident, [span, sym, optional]);

View File

@ -1,15 +0,0 @@
//! Implementation of [crate::Diff] for types in [swc_ecma_ast].
use swc_ecma_ast::*;
mod expr;
mod ident;
mod ops;
mod pat;
mod stmt;
mod typescript;
diff_struct!(Module, [span, body, shebang]);
diff_struct!(Invalid, [span]);
diff_enum!(ModuleItem, [Stmt, ModuleDecl]);

View File

@ -1,9 +0,0 @@
use swc_ecma_ast::*;
diff_string_enum!(AssignOp);
diff_string_enum!(UnaryOp);
diff_string_enum!(UpdateOp);
diff_string_enum!(VarDeclKind);
diff_string_enum!(BinaryOp);
trivial!(MethodKind);
trivial!(TsTypeOperatorOp);

View File

@ -1,12 +0,0 @@
use swc_ecma_ast::*;
diff_enum!(Pat, [Ident, Rest, Array, Object, Invalid, Assign, Expr]);
diff_enum!(PatOrExpr, [Pat, Expr]);
diff_struct!(ArrayPat, [span, elems, optional, type_ann]);
diff_struct!(ObjectPat, [span, props, optional, type_ann]);
diff_struct!(RestPat, [span, dot3_token, arg, type_ann]);
diff_struct!(AssignPat, [span, left, right, type_ann]);
diff_enum!(ObjectPatProp, [KeyValue, Assign, Rest]);
diff_struct!(KeyValuePatProp, [key, value]);
diff_struct!(AssignPatProp, [span, key, value]);

View File

@ -1,188 +0,0 @@
use swc_ecma_ast::*;
diff_enum!(
Stmt,
[
Block, Empty, Debugger, With, Return, Labeled, Break, Continue, If, Switch, Throw, Try,
While, DoWhile, For, ForIn, ForOf, Decl, Expr
]
);
diff_struct!(BlockStmt, [span, stmts]);
diff_struct!(EmptyStmt, [span]);
diff_struct!(DebuggerStmt, [span]);
diff_struct!(WithStmt, [span, obj, body]);
diff_struct!(ReturnStmt, [span, arg]);
diff_struct!(ThrowStmt, [span, arg]);
diff_struct!(LabeledStmt, [span, label, body]);
diff_struct!(BreakStmt, [span, label]);
diff_struct!(ContinueStmt, [span, label]);
diff_struct!(IfStmt, [span, test, cons, alt]);
diff_struct!(SwitchStmt, [span, discriminant, cases]);
diff_struct!(SwitchCase, [span, test, cons]);
diff_struct!(TryStmt, [span, block, handler, finalizer]);
diff_struct!(WhileStmt, [span, test, body]);
diff_struct!(DoWhileStmt, [span, test, body]);
diff_struct!(ForStmt, [span, init, test, update, body]);
diff_struct!(ForInStmt, [span, left, right, body]);
diff_struct!(ForOfStmt, [span, left, right, body, await_token]);
diff_struct!(ExprStmt, [span, expr]);
diff_struct!(CatchClause, [span, param, body]);
diff_enum!(VarDeclOrPat, [VarDecl, Pat]);
diff_enum!(VarDeclOrExpr, [VarDecl, Expr]);
diff_enum!(
Decl,
[Class, Fn, TsInterface, TsTypeAlias, TsEnum, TsModule, Var]
);
diff_struct!(VarDecl, [span, kind, decls, declare]);
diff_struct!(VarDeclarator, [span, name, init, definite]);
diff_struct!(ClassDecl, [ident, class, declare]);
diff_struct!(FnDecl, [ident, function, declare]);
diff_struct!(
Function,
[
params,
decorators,
span,
body,
is_generator,
is_async,
type_params,
return_type
]
);
diff_struct!(Param, [span, decorators, pat]);
diff_struct!(
Class,
[
span,
decorators,
body,
super_class,
is_abstract,
type_params,
super_type_params,
implements
]
);
diff_enum!(
ClassMember,
[
Constructor,
Method,
PrivateMethod,
ClassProp,
PrivateProp,
TsIndexSignature,
Empty,
StaticBlock
]
);
diff_struct!(
Constructor,
[span, params, body, key, accessibility, is_optional]
);
diff_enum!(ParamOrTsParamProp, [Param, TsParamProp]);
diff_enum!(
ModuleDecl,
[
Import,
ExportDecl,
ExportNamed,
ExportDefaultDecl,
ExportDefaultExpr,
ExportAll,
TsImportEquals,
TsExportAssignment,
TsNamespaceExport
]
);
diff_struct!(StaticBlock, [span, body]);
diff_struct!(
ClassMethod,
[
span,
key,
function,
kind,
is_static,
accessibility,
is_abstract,
is_optional,
is_override
]
);
diff_struct!(
PrivateMethod,
[
span,
key,
function,
kind,
is_static,
accessibility,
is_abstract,
is_optional,
is_override
]
);
diff_struct!(
ClassProp,
[
span,
key,
value,
type_ann,
is_static,
is_abstract,
is_optional,
is_override,
readonly,
accessibility,
decorators,
declare,
definite
]
);
diff_struct!(
PrivateProp,
[
span,
key,
value,
type_ann,
is_static,
is_abstract,
is_optional,
is_override,
readonly,
computed,
accessibility,
decorators,
definite
]
);
diff_struct!(ImportDecl, [span, specifiers, src, type_only, asserts]);
diff_struct!(ExportDecl, [span, decl]);
diff_struct!(ExportDefaultDecl, [span, decl]);
diff_struct!(ExportDefaultExpr, [span, expr]);
diff_struct!(NamedExport, [span, specifiers, src, type_only, asserts]);
diff_struct!(ExportAll, [span, src, asserts]);
diff_enum!(ImportSpecifier, [Named, Default, Namespace]);
diff_enum!(ExportSpecifier, [Named, Default, Namespace]);
diff_enum!(DefaultDecl, [Class, Fn, TsInterfaceDecl]);
diff_enum!(ModuleExportName, [Ident, Str]);
diff_struct!(ImportNamedSpecifier, [span, local, imported, is_type_only]);
diff_struct!(ImportDefaultSpecifier, [span, local]);
diff_struct!(ImportStarAsSpecifier, [span, local]);
diff_struct!(ExportNamedSpecifier, [span, orig, exported, is_type_only]);
diff_struct!(ExportDefaultSpecifier, [exported]);
diff_struct!(ExportNamespaceSpecifier, [span, name]);

View File

@ -1,178 +0,0 @@
use swc_ecma_ast::*;
diff_struct!(TsTypeAnn, [span, type_ann]);
diff_struct!(TsTypeParamDecl, [span, params]);
diff_struct!(TsTypeParam, [span, name, constraint, default]);
diff_enum!(
TsType,
[
TsKeywordType,
TsThisType,
TsFnOrConstructorType,
TsTypeRef,
TsTypeQuery,
TsTypeLit,
TsArrayType,
TsTupleType,
TsOptionalType,
TsRestType,
TsUnionOrIntersectionType,
TsConditionalType,
TsInferType,
TsParenthesizedType,
TsTypeOperator,
TsIndexedAccessType,
TsMappedType,
TsLitType,
TsTypePredicate,
TsImportType
]
);
diff_enum!(TsEntityName, [TsQualifiedName, Ident]);
diff_enum!(
TsTypeElement,
[
TsCallSignatureDecl,
TsConstructSignatureDecl,
TsPropertySignature,
TsGetterSignature,
TsSetterSignature,
TsMethodSignature,
TsIndexSignature
]
);
diff_struct!(TsTypeParamInstantiation, [span, params]);
diff_struct!(TsExprWithTypeArgs, [span, expr, type_args]);
diff_struct!(TsAsExpr, [span, expr, type_ann]);
diff_struct!(TsTypeAssertion, [span, expr, type_ann]);
diff_struct!(TsConstAssertion, [span, expr]);
diff_struct!(TsNonNullExpr, [span, expr]);
diff_struct!(
TsInterfaceDecl,
[span, id, declare, type_params, extends, body]
);
diff_struct!(TsInterfaceBody, [span, body]);
diff_struct!(TsTypeAliasDecl, [span, declare, id, type_params, type_ann]);
diff_struct!(TsEnumDecl, [span, declare, id, members, is_const]);
diff_struct!(TsEnumMember, [span, id, init]);
diff_enum!(TsEnumMemberId, [Str, Ident]);
diff_struct!(TsModuleDecl, [span, id, body, declare, global]);
diff_enum!(TsModuleName, [Ident, Str]);
diff_enum!(TsNamespaceBody, [TsModuleBlock, TsNamespaceDecl]);
trivial!(Accessibility);
diff_struct!(
TsIndexSignature,
[params, type_ann, readonly, is_static, span]
);
diff_enum!(TsFnParam, [Ident, Array, Object, Rest]);
diff_struct!(
TsParamProp,
[
span,
decorators,
accessibility,
is_override,
readonly,
param
]
);
diff_enum!(TsParamPropParam, [Ident, Assign]);
diff_struct!(
TsImportEqualsDecl,
[span, declare, is_export, is_type_only, id, module_ref]
);
diff_enum!(TsModuleRef, [TsEntityName, TsExternalModuleRef]);
diff_struct!(TsExportAssignment, [span, expr]);
diff_struct!(TsNamespaceExportDecl, [id, span]);
diff_struct!(TsKeywordType, [span, kind]);
trivial!(TsKeywordTypeKind);
diff_struct!(TsThisType, [span]);
diff_enum!(TsUnionOrIntersectionType, [TsUnionType, TsIntersectionType]);
diff_enum!(TsFnOrConstructorType, [TsFnType, TsConstructorType]);
diff_struct!(TsTypeRef, [span, type_name, type_params]);
diff_struct!(TsTypeQuery, [span, expr_name]);
diff_enum!(TsTypeQueryExpr, [TsEntityName, Import]);
diff_struct!(TsTypeLit, [span, members]);
diff_struct!(TsTupleType, [span, elem_types]);
diff_struct!(TsTupleElement, [span, label, ty]);
diff_struct!(TsArrayType, [span, elem_type]);
diff_struct!(TsOptionalType, [span, type_ann]);
diff_struct!(TsRestType, [span, type_ann]);
diff_struct!(
TsConditionalType,
[span, check_type, extends_type, true_type, false_type]
);
diff_struct!(TsInferType, [span, type_param]);
diff_struct!(TsParenthesizedType, [span, type_ann]);
diff_struct!(TsTypeOperator, [span, op, type_ann]);
diff_struct!(TsIndexedAccessType, [span, obj_type, index_type, readonly]);
diff_struct!(
TsMappedType,
[span, readonly, type_param, type_ann, name_type, optional]
);
diff_struct!(TsLitType, [span, lit]);
trivial!(TruePlusMinus);
diff_enum!(TsLit, [Number, Str, Bool, BigInt, Tpl]);
diff_struct!(TsTypePredicate, [span, param_name, asserts, type_ann]);
diff_enum!(TsThisTypeOrIdent, [TsThisType, Ident]);
diff_struct!(TsImportType, [span, arg, type_args, qualifier]);
diff_struct!(TsQualifiedName, [left, right]);
diff_struct!(TsUnionType, [span, types]);
diff_struct!(TsIntersectionType, [span, types]);
diff_struct!(TsFnType, [span, params, type_params, type_ann]);
diff_struct!(
TsConstructorType,
[span, params, type_params, type_ann, is_abstract]
);
diff_struct!(TsCallSignatureDecl, [span, params, type_ann, type_params]);
diff_struct!(
TsConstructSignatureDecl,
[span, params, type_ann, type_params]
);
diff_struct!(
TsPropertySignature,
[
span,
key,
type_ann,
optional,
readonly,
computed,
init,
params,
type_params
]
);
diff_struct!(
TsMethodSignature,
[
span,
key,
type_ann,
params,
type_params,
readonly,
computed,
optional
]
);
diff_struct!(
TsGetterSignature,
[span, readonly, key, computed, optional, type_ann]
);
diff_struct!(
TsSetterSignature,
[span, readonly, key, computed, optional, param]
);
diff_struct!(TsNamespaceDecl, [span, declare, global, id, body]);
diff_struct!(TsModuleBlock, [span, body]);
diff_struct!(TsExternalModuleRef, [span, expr]);
diff_struct!(TsTplLitType, [span, types, quasis]);

View File

@ -1,321 +0,0 @@
pub use self::ctx::{Ctx, PathComponent};
use std::fmt::{self, Debug, Display, Formatter};
use swc_common::Span;
#[macro_use]
mod macros;
mod ctx;
mod js_ast;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Config {
/// If this value is true, the differences of [Span]s are ignored.
///
/// Defaults to false.
pub ignore_span: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Node(String);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Difference {
pub path: Vec<PathComponent>,
pub left: Node,
pub right: Node,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DiffResult {
/// Two nodes are identical.
Identical,
Different(Difference),
Multiple(Vec<DiffResult>),
}
impl From<Difference> for DiffResult {
fn from(v: Difference) -> Self {
DiffResult::Different(v)
}
}
impl Display for Difference {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Path: ")?;
for c in &self.path {
match c {
PathComponent::StructProp { struct_name, key } => {
write!(
f,
"({struct_name}.{key})",
key = key,
struct_name = struct_name
)?;
}
PathComponent::VecElem { l, r } => {
write!(f, "[{} <-> {}]", l, r)?;
}
}
}
writeln!(f)?;
writeln!(f, "Left: {}", self.left.0)?;
writeln!(f, "Right: {}", self.right.0)?;
Ok(())
}
}
impl Display for DiffResult {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
DiffResult::Identical => {}
DiffResult::Different(d) => {
writeln!(f, "{}", d)?;
}
DiffResult::Multiple(d) => {
if d.len() == 1 {
for d in d {
write!(f, "{}", d)?;
}
} else {
for d in d {
writeln!(f, "{}", d)?;
}
}
}
}
Ok(())
}
}
#[auto_impl::auto_impl(Box)]
pub trait Diff: Debug {
/// This may remove common node from `self` and `other`.
fn diff(&mut self, other: &mut Self, ctx: &mut Ctx) -> DiffResult;
}
impl Diff for Span {
fn diff(&mut self, other: &mut Self, ctx: &mut Ctx) -> DiffResult {
if *self == *other {
return DiffResult::Identical;
}
if ctx.config.ignore_span {
return DiffResult::Identical;
}
DiffResult::Different(Difference {
path: ctx.path.clone(),
left: Node(format!("{:?}", self)),
right: Node(format!("{:?}", other)),
})
}
}
impl<T> Diff for Vec<T>
where
T: Diff,
{
fn diff(&mut self, other: &mut Self, ctx: &mut Ctx) -> DiffResult {
let mut results = Vec::new();
if self.len() != other.len() {
results.push(DiffResult::Different(Difference {
path: ctx.path.clone(),
left: Node(format!("len = {}", self.len())),
right: Node(format!("len = {}", other.len())),
}));
}
let mut should_retry = false;
let should_try_shifting = ctx.path.iter().all(|v| match v {
PathComponent::VecElem { l, r } => *l == *r,
_ => true,
});
// If we are visiting this vector for the first time, we can shift indexes to
// reduce output files.
//
// e.g.
//
// A:
//
// console.log('Foo')
// console.log('Bar')
// console.log('Baz')
//
//
// B:
//
// console.log('Bar')
// console.log('Baz')
//
//
// We can remove two statements.
if should_try_shifting {
let mut l_removed = vec![];
let mut r_removed = vec![];
for (l_idx, l) in self.iter_mut().enumerate() {
for (r_idx, r) in other.iter_mut().enumerate() {
if r_removed.contains(&r_idx) {
continue;
}
let mut ctx = ctx.clone();
ctx.path.push(PathComponent::VecElem { l: l_idx, r: r_idx });
let diff = l.diff(r, &mut ctx);
if matches!(diff, DiffResult::Identical) {
l_removed.push(l_idx);
r_removed.push(r_idx);
should_retry = true;
break;
}
if l_idx == r_idx {
results.push(diff);
}
}
}
if !l_removed.is_empty() {
let new_l = self
.drain(..)
.enumerate()
.filter(|(i, _)| !l_removed.contains(i))
.map(|(_, v)| v)
.collect::<Vec<_>>();
*self = new_l;
}
if !r_removed.is_empty() {
let new_r = other
.drain(..)
.enumerate()
.filter(|(i, _)| !r_removed.contains(i))
.map(|(_, v)| v)
.collect::<Vec<_>>();
*other = new_r;
}
} else {
let mut l = self.iter_mut();
let mut r = other.iter_mut();
let mut idx = 0;
let mut removed = vec![];
while let (Some(l), Some(r)) = (l.next(), r.next()) {
let cur_idx = idx;
idx += 1;
let mut ctx = ctx.clone();
ctx.path.push(PathComponent::VecElem {
l: cur_idx,
r: cur_idx,
});
let diff = l.diff(r, &mut ctx);
if matches!(diff, DiffResult::Identical) {
removed.push(cur_idx);
continue;
}
results.push(diff);
}
if !removed.is_empty() {
should_retry = true;
let new_l = self
.drain(..)
.enumerate()
.filter(|(i, _)| !removed.contains(i))
.map(|(_, v)| v)
.collect::<Vec<_>>();
let new_r = other
.drain(..)
.enumerate()
.filter(|(i, _)| !removed.contains(i))
.map(|(_, v)| v)
.collect::<Vec<_>>();
*self = new_l;
*other = new_r;
}
}
if should_retry {
return self.diff(other, ctx);
}
// TODO: Dump extra nodes
if results.is_empty() {
return DiffResult::Identical;
}
DiffResult::Multiple(results)
}
}
impl<T> Diff for Option<T>
where
T: Diff,
{
fn diff(&mut self, other: &mut Self, ctx: &mut Ctx) -> DiffResult {
let result = match (&mut *self, &mut *other) {
(Some(l), Some(r)) => l.diff(r, ctx),
(None, None) => DiffResult::Identical,
(None, Some(r)) => DiffResult::Different(Difference {
path: ctx.path.clone(),
left: Node("None".into()),
right: Node(format!("{:?}", r)),
}),
(Some(l), None) => DiffResult::Different(Difference {
path: ctx.path.clone(),
left: Node(format!("{:?}", l)),
right: Node("None".into()),
}),
};
if matches!(result, DiffResult::Identical) {
// Remove common node.
*self = None;
*other = None;
return result;
}
result
}
}
trivial!(bool, (), char);
trivial!(usize, u8, u16, u32, u64, u128);
trivial!(isize, i8, i16, i32, i64, i128);
trivial!(f32, f64);
trivial!(String, str);
trivial!(num_bigint::BigInt);
impl<S> Diff for string_cache::Atom<S>
where
S: string_cache::StaticAtomSet,
{
fn diff(&mut self, other: &mut Self, ctx: &mut Ctx) -> DiffResult {
if *self == *other {
return DiffResult::Identical;
}
DiffResult::Different(Difference {
path: ctx.path.clone(),
left: Node(format!("{:?}", self)),
right: Node(format!("{:?}", other)),
})
}
}

View File

@ -1,108 +0,0 @@
macro_rules! trivial {
($T:ty) => {
impl crate::Diff for $T {
fn diff(&mut self, other: &mut Self, ctx: &mut crate::Ctx) -> crate::DiffResult {
if *self == *other {
return crate::DiffResult::Identical;
}
crate::DiffResult::Different(crate::Difference {
path: ctx.path.clone(),
left: crate::Node(format!("{:?}", self)),
right: crate::Node(format!("{:?}", other)),
})
}
}
};
(
$T:ty, $($tt:tt)*
) => {
trivial!($T);
trivial!($($tt)*);
};
}
macro_rules! diff_struct {
(
$T:ident,
[
$($field:ident),*
]
) => {
impl crate::Diff for $T {
fn diff(&mut self, other: &mut Self, ctx: &mut crate::Ctx) -> crate::DiffResult {
// Ensure that we diff all fields.
#[allow(unused)]
fn _assert_all_fields(_node: &$T){
let $T {
$($field,)*
} = _node;
}
use swc_common::EqIgnoreSpan;
let result = ctx.diff_struct(stringify!($T), |ctx| {
$(
ctx.field(stringify!($field), &mut self.$field, &mut other.$field);
)*
});
if ctx.config.ignore_span{
if self.eq_ignore_span(&*other) {
return crate::DiffResult::Identical;
}
} else {
if *self == *other {
return crate::DiffResult::Identical;
}
}
result
}
}
};
}
macro_rules! diff_enum {
(
$T:ident,
[
$($Variant:ident),*
]
) => {
impl crate::Diff for $T {
fn diff(&mut self, other: &mut Self, ctx: &mut crate::Ctx) -> crate::DiffResult {
// Ensure that we handle all variants.
fn _assert_all_variants(_node: &$T){
match _node {
$(
$T::$Variant(..) => {},
)*
}
}
match (&mut *self, &mut * other) {
$(
(
$T::$Variant(l),
$T::$Variant(r),
) => crate::Diff::diff(l,r,ctx),
)*
_ => crate::DiffResult::Different(crate::Difference {
path: ctx.path.clone(),
left: crate::Node(format!("{:?}", self)),
right: crate::Node(format!("{:?}", other)),
}),
}
}
}
};
}
macro_rules! diff_string_enum {
($T:ty) => {
trivial!($T);
};
}

View File

@ -1,4 +0,0 @@
import foo from 'foo;'
console.log(foo);
console.log(foo);

View File

@ -1 +0,0 @@
console.log(foo);

View File

@ -1,4 +0,0 @@
Path: (Module.body)
Left: len = 1
Right: len = 0

View File

@ -1,3 +0,0 @@
import foo from 'foo;'
console.log(foo);

View File

@ -1,3 +0,0 @@
console.log('Foo')
console.log('Bar')
console.log('Baz')

View File

@ -1 +0,0 @@
console.log('Foo');

View File

@ -1,4 +0,0 @@
Path: (Module.body)
Left: len = 1
Right: len = 0

View File

@ -1,2 +0,0 @@
console.log('Bar')
console.log('Baz')

View File

@ -1,81 +0,0 @@
use std::path::{Path, PathBuf};
use swc_common::{input::SourceFileInput, sync::Lrc, SourceMap};
use swc_ecma_ast::Module;
use swc_ecma_codegen::{
text_writer::{JsWriter, WriteJs},
Emitter,
};
use swc_ecma_diff::{Config, Ctx, Diff};
use swc_ecma_parser::{lexer::Lexer, EsConfig, Parser, Syntax};
use testing::NormalizedOutput;
fn parse(cm: Lrc<SourceMap>, path: &Path) -> Module {
let fm = cm.load_file(path).unwrap();
let lexer = Lexer::new(
Syntax::Es(EsConfig {
jsx: true,
..Default::default()
}),
Default::default(),
SourceFileInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
parser.parse_module().unwrap()
}
#[testing::fixture("tests/diff/**/l.js")]
fn diff(l: PathBuf) {
let r = l.with_file_name("r.js");
let spec = l.with_file_name("output.swc-diff");
let (diff_str, l_str, r_str) = testing::run_test(false, |cm, _handler| {
let mut l = parse(cm.clone(), &l);
let mut r = parse(cm.clone(), &r);
let mut ctx = Ctx::new(Config { ignore_span: true });
let res = l.diff(&mut r, &mut ctx);
let l = print(cm.clone(), &[l]);
let r = print(cm, &[r]);
Ok((format!("{}", res), l, r))
})
.unwrap();
NormalizedOutput::from(l_str)
.compare_to_file(&l.with_file_name("l.output.js"))
.unwrap();
NormalizedOutput::from(r_str)
.compare_to_file(&r.with_file_name("r.output.js"))
.unwrap();
NormalizedOutput::from(diff_str)
.compare_to_file(&spec)
.unwrap();
}
fn print<N: swc_ecma_codegen::Node>(cm: Lrc<SourceMap>, nodes: &[N]) -> String {
let mut buf = vec![];
{
let wr: Box<dyn WriteJs> = Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None));
let mut emitter = Emitter {
cfg: swc_ecma_codegen::Config { minify: false },
cm,
comments: None,
wr,
};
for n in nodes {
n.emit_with(&mut emitter).unwrap();
}
}
String::from_utf8(buf).unwrap()
}