Skip to content

Commit

Permalink
feat(es/testing): Support babel-like fixture testing officially (#8190)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdy1 committed Oct 29, 2023
1 parent 5ba6150 commit e960614
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 38 deletions.
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()
}
}

1 comment on commit e960614

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: e960614 Previous: bb02cdd Ratio
es/full/bugs-1 299172 ns/iter (± 7667) 298578 ns/iter (± 6088) 1.00
es/full/minify/libraries/antd 1373697663 ns/iter (± 27227801) 1486112444 ns/iter (± 35178749) 0.92
es/full/minify/libraries/d3 294610725 ns/iter (± 5222816) 305182569 ns/iter (± 4329440) 0.97
es/full/minify/libraries/echarts 1099175181 ns/iter (± 6851978) 1147726147 ns/iter (± 30730669) 0.96
es/full/minify/libraries/jquery 87434335 ns/iter (± 546609) 87261083 ns/iter (± 366871) 1.00
es/full/minify/libraries/lodash 102264030 ns/iter (± 471016) 102735490 ns/iter (± 872426) 1.00
es/full/minify/libraries/moment 51554167 ns/iter (± 107233) 51432789 ns/iter (± 204092) 1.00
es/full/minify/libraries/react 18394474 ns/iter (± 91192) 18341540 ns/iter (± 138944) 1.00
es/full/minify/libraries/terser 227015744 ns/iter (± 1552705) 225852642 ns/iter (± 2099047) 1.01
es/full/minify/libraries/three 399916091 ns/iter (± 1972820) 408145521 ns/iter (± 3077060) 0.98
es/full/minify/libraries/typescript 2751838451 ns/iter (± 13263834) 2752304274 ns/iter (± 12251041) 1.00
es/full/minify/libraries/victory 586643802 ns/iter (± 4566654) 608725327 ns/iter (± 5454452) 0.96
es/full/minify/libraries/vue 124506789 ns/iter (± 428838) 124798748 ns/iter (± 473786) 1.00
es/full/codegen/es3 34461 ns/iter (± 110) 34337 ns/iter (± 75) 1.00
es/full/codegen/es5 34598 ns/iter (± 80) 34390 ns/iter (± 146) 1.01
es/full/codegen/es2015 34576 ns/iter (± 202) 34288 ns/iter (± 58) 1.01
es/full/codegen/es2016 34565 ns/iter (± 69) 34441 ns/iter (± 96) 1.00
es/full/codegen/es2017 34648 ns/iter (± 101) 34487 ns/iter (± 63) 1.00
es/full/codegen/es2018 34587 ns/iter (± 54) 34352 ns/iter (± 88) 1.01
es/full/codegen/es2019 34464 ns/iter (± 88) 34353 ns/iter (± 28) 1.00
es/full/codegen/es2020 34413 ns/iter (± 79) 34386 ns/iter (± 126) 1.00
es/full/all/es3 177497388 ns/iter (± 1970381) 179891236 ns/iter (± 1293795) 0.99
es/full/all/es5 169841974 ns/iter (± 1322239) 173116742 ns/iter (± 1599540) 0.98
es/full/all/es2015 128234609 ns/iter (± 782099) 130565278 ns/iter (± 1064861) 0.98
es/full/all/es2016 127691057 ns/iter (± 807543) 129621999 ns/iter (± 1148681) 0.99
es/full/all/es2017 126341760 ns/iter (± 1177427) 129291073 ns/iter (± 711630) 0.98
es/full/all/es2018 124768534 ns/iter (± 549715) 126850152 ns/iter (± 977508) 0.98
es/full/all/es2019 124013350 ns/iter (± 594730) 126210845 ns/iter (± 381354) 0.98
es/full/all/es2020 120459104 ns/iter (± 1308068) 122093603 ns/iter (± 848551) 0.99
es/full/parser 562053 ns/iter (± 3913) 569697 ns/iter (± 2965) 0.99
es/full/base/fixer 17328 ns/iter (± 244) 17622 ns/iter (± 175) 0.98
es/full/base/resolver_and_hygiene 84155 ns/iter (± 169) 85456 ns/iter (± 149) 0.98

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.