Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(es/testing): Support babel-like fixture testing officially #8190

Merged
merged 59 commits into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
242b86c
migrate.sh
kdy1 Oct 29, 2023
66e2db3
deprecate
kdy1 Oct 29, 2023
731c35f
ls
kdy1 Oct 29, 2023
613ffa5
ast grep
kdy1 Oct 29, 2023
f698c09
Add babel_like
kdy1 Oct 29, 2023
9deab4f
Add impl
kdy1 Oct 29, 2023
307ac1d
builder
kdy1 Oct 29, 2023
8723fde
`BabelPassBuilder`
kdy1 Oct 29, 2023
a197fa0
comments
kdy1 Oct 29, 2023
e478b76
more
kdy1 Oct 29, 2023
008042c
parse options
kdy1 Oct 29, 2023
a1ee714
`parse_options`
kdy1 Oct 29, 2023
c7472ce
syntax
kdy1 Oct 29, 2023
11ca37a
fixup
kdy1 Oct 29, 2023
fbc59f3
doc
kdy1 Oct 29, 2023
879e81a
more fields
kdy1 Oct 29, 2023
139f811
`add_factory`
kdy1 Oct 29, 2023
7ed5c89
execute & fixture
kdy1 Oct 29, 2023
d6c1c92
TestOutput
kdy1 Oct 29, 2023
94b98ef
Build pass
kdy1 Oct 29, 2023
6dd6d76
panic
kdy1 Oct 29, 2023
cb82157
more work
kdy1 Oct 29, 2023
fbef36e
sourcemap
kdy1 Oct 29, 2023
312d9ab
parse
kdy1 Oct 29, 2023
963a69e
apply transform
kdy1 Oct 29, 2023
fa80594
apply transform
kdy1 Oct 29, 2023
e33616e
assert
kdy1 Oct 29, 2023
caaf8b4
invoke
kdy1 Oct 29, 2023
9e118e5
more work
kdy1 Oct 29, 2023
94d7ef3
babel_like
kdy1 Oct 29, 2023
e819d46
allow_error
kdy1 Oct 29, 2023
1575a01
Signature
kdy1 Oct 29, 2023
983eede
Migrate decorators test
kdy1 Oct 29, 2023
903679a
more doc
kdy1 Oct 29, 2023
7ab6de5
ast grep cannot do this
kdy1 Oct 29, 2023
8b98f5a
Migrate one
kdy1 Oct 29, 2023
cd3f2e6
lint
kdy1 Oct 29, 2023
ce9e822
Rename
kdy1 Oct 29, 2023
2a222f5
I found a better way
kdy1 Oct 29, 2023
9e7169b
doc
kdy1 Oct 29, 2023
6ff171d
fix
kdy1 Oct 29, 2023
b801aa1
Update test refs
kdy1 Oct 29, 2023
8defc06
fix
kdy1 Oct 29, 2023
fe839ab
Update test refs
kdy1 Oct 29, 2023
86b8557
fix
kdy1 Oct 29, 2023
60a9e14
Use cargo target dir
kdy1 Oct 29, 2023
d9a118f
Use `exec_with_node_test_runner`
kdy1 Oct 29, 2023
ddc0574
mocha
kdy1 Oct 29, 2023
936466e
Update test refs
kdy1 Oct 29, 2023
2650ec7
external helpers
kdy1 Oct 29, 2023
97e095f
Update test refs
kdy1 Oct 29, 2023
c4f1905
fix
kdy1 Oct 29, 2023
fb3b99d
Update test refs
kdy1 Oct 29, 2023
a6d3aab
exec_with_test_runner
kdy1 Oct 29, 2023
515e139
fix
kdy1 Oct 29, 2023
8694aec
Print input psth
kdy1 Oct 29, 2023
77e9ea9
src
kdy1 Oct 29, 2023
1716695
print code
kdy1 Oct 29, 2023
fd9cf29
Revert
kdy1 Oct 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/swc_ecma_transforms_compat/tests/es2015_arrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;
use swc_common::{chain, Mark};
use swc_ecma_transforms_base::resolver;
use swc_ecma_transforms_compat::es2015::arrow;
use swc_ecma_transforms_testing::{compare_stdout, test, test_fixture};
use swc_ecma_transforms_testing::{compare_stdout, test_fixture};
use swc_ecma_visit::Fold;

fn tr() -> impl Fold {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 0 additions & 18 deletions crates/swc_ecma_transforms_module/tests/system_js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,6 @@ fn tr(_tester: &mut Tester<'_>, config: Config) -> impl Fold {
)
}

test!(
syntax(),
|tester| tr(tester, Default::default()),
allow_continuous_assignment,
r#"var e = {}; e.a = e.b = e.c = e.d = e.e = e.f = e.g = e.h = e.i = e.j = e.k = e.l = e.m = e.n = e.o = e.p = e.q = e.r = e.s = e.t = e.u = e.v = e.w = e.x = e.y = e.z = e.A = e.B = e.C = e.D = e.E = e.F = e.G = e.H = e.I = e.J = e.K = e.L = e.M = e.N = e.O = e.P = e.Q = e.R = e.S = void 0;"#,
r#"System.register([], function (_export, _context) {
"use strict";
var e;
return {
setters: [],
execute: function () {
e = {};
e.a = e.b = e.c = e.d = e.e = e.f = e.g = e.h = e.i = e.j = e.k = e.l = e.m = e.n = e.o = e.p = e.q = e.r = e.s = e.t = e.u = e.v = e.w = e.x = e.y = e.z = e.A = e.B = e.C = e.D = e.E = e.F = e.G = e.H = e.I = e.J = e.K = e.L = e.M = e.N = e.O = e.P = e.Q = e.R = e.S = void 0;
}
};
});"#
);

test!(
syntax(),
|tester| tr(
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
/*#__PURE__*/ React.createElement("div", null, ...children);
/*#__PURE__*/ import { jsx as _jsx } from "react/jsx-runtime";
_jsx("div", {
children: [
...children
]
});
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/*#__PURE__*/ React.createElement("Namespace:Component", null);
/*#__PURE__*/ import { jsx as _jsx } from "react/jsx-runtime";
_jsx("Namespace:Component", {});
293 changes: 293 additions & 0 deletions crates/swc_ecma_transforms_testing/src/babel_like.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
use std::{fs::read_to_string, path::Path};

use ansi_term::Color;
use serde::Deserialize;
use serde_json::Value;
use swc_common::{chain, comments::SingleThreadedComments, sync::Lrc, Mark, SourceMap};
use swc_ecma_ast::{EsVersion, Program};
use swc_ecma_codegen::Emitter;
use swc_ecma_parser::{parse_file_as_program, Syntax};
use swc_ecma_transforms_base::{
assumptions::Assumptions,
fixer::fixer,
helpers::{inject_helpers, Helpers, HELPERS},
hygiene::hygiene,
resolver,
};
use swc_ecma_visit::{Fold, FoldWith, VisitMutWith};
use testing::NormalizedOutput;

use crate::{exec_with_node_test_runner, parse_options, stdout_of};

pub type PassFactory<'a> =
Box<dyn 'a + FnMut(&PassContext, &str, Option<Value>) -> Option<Box<dyn 'static + Fold>>>;

/// These tests use `options.json`.
///
///
/// Note: You should **not** use [resolver] by yourself.

pub struct BabelLikeFixtureTest<'a> {
input: &'a Path,

/// Default to [`Syntax::default`]
syntax: Syntax,

factories: Vec<Box<dyn 'a + FnOnce() -> PassFactory<'a>>>,

source_map: bool,
allow_error: bool,
}

impl<'a> BabelLikeFixtureTest<'a> {
pub fn new(input: &'a Path) -> Self {
Self {
input,
syntax: Default::default(),
factories: Default::default(),
source_map: false,
allow_error: false,
}
}

pub fn syntax(mut self, syntax: Syntax) -> Self {
self.syntax = syntax;
self
}

pub fn source_map(mut self) -> Self {
self.source_map = true;
self
}

pub fn allow_error(mut self) -> Self {
self.source_map = true;
self
}

/// This takes a closure which returns a [PassFactory]. This is because you
/// may need to create [Mark], which requires [swc_common::GLOBALS] to be
/// configured.
pub fn add_factory(mut self, factory: impl 'a + FnOnce() -> PassFactory<'a>) -> Self {
self.factories.push(Box::new(factory));
self
}

fn run(self, output_path: Option<&Path>, compare_stdout: bool) {
let err = testing::run_test(false, |cm, handler| {
let mut factories = self.factories.into_iter().map(|f| f()).collect::<Vec<_>>();

let options = parse_options::<BabelOptions>(self.input.parent().unwrap());

let comments = SingleThreadedComments::default();
let mut builder = PassContext {
cm: cm.clone(),
assumptions: options.assumptions,
unresolved_mark: Mark::new(),
top_level_mark: Mark::new(),
comments: comments.clone(),
};

let mut pass: Box<dyn Fold> = Box::new(resolver(
builder.unresolved_mark,
builder.top_level_mark,
self.syntax.typescript(),
));

// Build pass using babel options

//
for plugin in options.plugins {
let (name, options) = match plugin {
BabelPluginEntry::NameOnly(name) => (name, None),
BabelPluginEntry::WithConfig(name, options) => (name, Some(options)),
};

let mut done = false;
for factory in &mut factories {
if let Some(built) = factory(&builder, &name, options.clone()) {
pass = Box::new(chain!(pass, built));
done = true;
break;
}
}

if !done {
panic!("Unknown plugin: {}", name);
}
}

pass = Box::new(chain!(pass, hygiene(), fixer(Some(&comments))));

// Run pass

let src = read_to_string(self.input).expect("failed to read file");
let src = if output_path.is_none() && !compare_stdout {
format!(
"it('should work', async function () {{
{src}
}})",
)
} else {
src
};
let fm = cm.new_source_file(swc_common::FileName::Real(self.input.to_path_buf()), src);

let mut errors = vec![];
let input_program = parse_file_as_program(
&fm,
self.syntax,
EsVersion::latest(),
Some(&comments),
&mut errors,
);

let errored = !errors.is_empty();

for e in errors {
e.into_diagnostic(handler).emit();
}

let input_program = match input_program {
Ok(v) => v,
Err(err) => {
err.into_diagnostic(handler).emit();
return Err(());
}
};

if errored {
return Err(());
}

let helpers = Helpers::new(output_path.is_some());
let (code_without_helper, output_program) = HELPERS.set(&helpers, || {
let mut p = input_program.fold_with(&mut *pass);

let code_without_helper = builder.print(&p);

if output_path.is_none() {
p.visit_mut_with(&mut inject_helpers(builder.unresolved_mark))
}

(code_without_helper, p)
});

// Print output
let code = builder.print(&output_program);

println!(
"\t>>>>> {} <<<<<\n{}\n\t>>>>> {} <<<<<\n{}",
Color::Green.paint("Orig"),
fm.src,
Color::Green.paint("Code"),
code_without_helper
);

if let Some(output_path) = output_path {
// Fixture test

if !self.allow_error && handler.has_errors() {
return Err(());
}

NormalizedOutput::from(code)
.compare_to_file(output_path)
.unwrap();
} else if compare_stdout {
// Execution test, but compare stdout

let actual_stdout: String =
stdout_of(&code).expect("failed to execute transfomred code");
let expected_stdout =
stdout_of(&fm.src).expect("failed to execute transfomred code");

testing::assert_eq!(actual_stdout, expected_stdout);
} else {
// Execution test

exec_with_node_test_runner(&format!("// {}\n{code}", self.input.display()))
.expect("failed to execute transfomred code");
}

Ok(())
});

if self.allow_error {
match err {
Ok(_) => {}
Err(err) => {
err.compare_to_file(self.input.with_extension("stderr"))
.unwrap();
}
}
}
}

/// Execute using node.js and mocha
pub fn exec_with_test_runner(self) {
self.run(None, false)
}

/// Execute using node.js
pub fn compare_stdout(self) {
self.run(None, true)
}

/// Run a fixture test
pub fn fixture(self, output: &Path) {
self.run(Some(output), false)
}
}

#[derive(Debug, Deserialize)]
struct BabelOptions {
#[serde(default)]
assumptions: Assumptions,

#[serde(default)]
plugins: Vec<BabelPluginEntry>,
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase", untagged)]
enum BabelPluginEntry {
NameOnly(String),
WithConfig(String, Value),
}

#[derive(Clone)]
pub struct PassContext {
pub cm: Lrc<SourceMap>,

pub assumptions: Assumptions,
pub unresolved_mark: Mark,
pub top_level_mark: Mark,

/// [SingleThreadedComments] is cheap to clone.
pub comments: SingleThreadedComments,
}

impl PassContext {
fn print(&mut self, program: &Program) -> String {
let mut buf = vec![];
{
let mut emitter = Emitter {
cfg: Default::default(),
cm: self.cm.clone(),
wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new(
self.cm.clone(),
"\n",
&mut buf,
None,
)),
comments: Some(&self.comments),
};

emitter.emit_program(program).unwrap();
}

let s = String::from_utf8_lossy(&buf);
s.to_string()
}
}
Loading