-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(es/testing): Support babel-like fixture testing officially (#8190)
- Loading branch information
Showing
7 changed files
with
333 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
...es/swc_ecma_transforms_module/tests/fixture/systemjs/allow-continuous-assignment/input.js
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 6 additions & 1 deletion
7
...sforms_react/tests/jsx/fixture/react-automatic/should-disallow-spread-children/output.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
] | ||
}); |
3 changes: 2 additions & 1 deletion
3
...sforms_react/tests/jsx/fixture/react-automatic/should-disallow-xml-namespacing/output.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", {}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
Oops, something went wrong.
e960614
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Benchmark
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.