1#![deny(clippy::all)]
2#![deny(clippy::all)]
3#![deny(unused)]
4#![allow(clippy::result_unit_err)]
5
6use std::{
7 env,
8 fs::{self, create_dir_all, read_to_string, OpenOptions},
9 io::Write,
10 mem::{take, transmute},
11 panic,
12 path::{Path, PathBuf},
13 process::Command,
14 rc::Rc,
15};
16
17use ansi_term::Color;
18use anyhow::Error;
19use base64::prelude::{Engine, BASE64_STANDARD};
20use serde::de::DeserializeOwned;
21use sha2::{Digest, Sha256};
22use swc_common::{
23 comments::{Comments, SingleThreadedComments},
24 errors::{Handler, HANDLER},
25 source_map::SourceMapGenConfig,
26 sync::Lrc,
27 FileName, Mark, SourceMap, DUMMY_SP,
28};
29use swc_ecma_ast::*;
30use swc_ecma_codegen::{to_code_default, Emitter};
31use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax};
32use swc_ecma_testing::{exec_node_js, JsExecOptions};
33use swc_ecma_transforms_base::{
34 fixer,
35 helpers::{inject_helpers, HELPERS},
36 hygiene,
37};
38use swc_ecma_utils::{quote_ident, quote_str, ExprFactory};
39use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, Fold, FoldWith, VisitMut};
40use tempfile::tempdir_in;
41use testing::{
42 assert_eq, find_executable, NormalizedOutput, CARGO_TARGET_DIR, CARGO_WORKSPACE_ROOT,
43};
44
45pub mod babel_like;
46
47pub struct Tester<'a> {
48 pub cm: Lrc<SourceMap>,
49 pub handler: &'a Handler,
50 pub comments: Rc<SingleThreadedComments>,
57}
58
59impl Tester<'_> {
60 pub fn run<F, Ret>(op: F) -> Ret
61 where
62 F: FnOnce(&mut Tester<'_>) -> Result<Ret, ()>,
63 {
64 let comments = Rc::new(SingleThreadedComments::default());
65
66 let out = ::testing::run_test(false, |cm, handler| {
67 HANDLER.set(handler, || {
68 HELPERS.set(&Default::default(), || {
69 let cmts = comments.clone();
70 let c = Box::new(unsafe {
71 transmute::<&dyn Comments, &'static dyn Comments>(&*cmts)
73 }) as Box<dyn Comments>;
74 swc_common::comments::COMMENTS.set(&c, || {
75 op(&mut Tester {
76 cm,
77 handler,
78 comments,
79 })
80 })
81 })
82 })
83 });
84
85 match out {
86 Ok(ret) => ret,
87 Err(stderr) => panic!("Stderr:\n{stderr}"),
88 }
89 }
90
91 pub(crate) fn run_captured<F, T>(op: F) -> (Option<T>, NormalizedOutput)
92 where
93 F: FnOnce(&mut Tester<'_>) -> Result<T, ()>,
94 {
95 let mut res = None;
96 let output = ::testing::Tester::new().print_errors(|cm, handler| -> Result<(), _> {
97 HANDLER.set(&handler, || {
98 HELPERS.set(&Default::default(), || {
99 let result = op(&mut Tester {
100 cm,
101 handler: &handler,
102 comments: Default::default(),
103 });
104
105 res = result.ok();
106
107 Err(())
109 })
110 })
111 });
112
113 let output = output
114 .err()
115 .unwrap_or_else(|| NormalizedOutput::from(String::from("")));
116
117 (res, output)
118 }
119
120 pub fn with_parser<F, T>(
121 &mut self,
122 file_name: &str,
123 syntax: Syntax,
124 src: &str,
125 op: F,
126 ) -> Result<T, ()>
127 where
128 F: FnOnce(&mut Parser<Lexer>) -> Result<T, swc_ecma_parser::error::Error>,
129 {
130 let fm = self
131 .cm
132 .new_source_file(FileName::Real(file_name.into()).into(), src.to_string());
133
134 let mut p = Parser::new(syntax, StringInput::from(&*fm), Some(&self.comments));
135 let res = op(&mut p).map_err(|e| e.into_diagnostic(self.handler).emit());
136
137 for e in p.take_errors() {
138 e.into_diagnostic(self.handler).emit()
139 }
140
141 res
142 }
143
144 pub fn parse_module(&mut self, file_name: &str, src: &str) -> Result<Module, ()> {
145 self.with_parser(file_name, Syntax::default(), src, |p| p.parse_module())
146 }
147
148 pub fn parse_stmts(&mut self, file_name: &str, src: &str) -> Result<Vec<Stmt>, ()> {
149 let stmts = self.with_parser(file_name, Syntax::default(), src, |p| {
150 p.parse_script().map(|script| script.body)
151 })?;
152
153 Ok(stmts)
154 }
155
156 pub fn parse_stmt(&mut self, file_name: &str, src: &str) -> Result<Stmt, ()> {
157 let mut stmts = self.parse_stmts(file_name, src)?;
158 assert!(stmts.len() == 1);
159
160 Ok(stmts.pop().unwrap())
161 }
162
163 pub fn apply_transform<T: Pass>(
164 &mut self,
165 tr: T,
166 name: &str,
167 syntax: Syntax,
168 is_module: Option<bool>,
169 src: &str,
170 ) -> Result<Program, ()> {
171 let program =
172 self.with_parser(
173 name,
174 syntax,
175 src,
176 |parser: &mut Parser<Lexer>| match is_module {
177 Some(true) => parser.parse_module().map(Program::Module),
178 Some(false) => parser.parse_script().map(Program::Script),
179 None => parser.parse_program(),
180 },
181 )?;
182
183 Ok(program.apply(tr))
184 }
185
186 pub fn print(&mut self, program: &Program, comments: &Rc<SingleThreadedComments>) -> String {
187 to_code_default(self.cm.clone(), Some(comments), program)
188 }
189}
190
191struct RegeneratorHandler;
192
193impl VisitMut for RegeneratorHandler {
194 noop_visit_mut_type!();
195
196 fn visit_mut_module_item(&mut self, item: &mut ModuleItem) {
197 if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = item {
198 if &*import.src.value != "regenerator-runtime" {
199 return;
200 }
201
202 let s = import.specifiers.iter().find_map(|v| match v {
203 ImportSpecifier::Default(rt) => Some(rt.local.clone()),
204 _ => None,
205 });
206
207 let s = match s {
208 Some(v) => v,
209 _ => return,
210 };
211
212 let init = CallExpr {
213 span: DUMMY_SP,
214 callee: quote_ident!("require").as_callee(),
215 args: vec![quote_str!("regenerator-runtime").as_arg()],
216 ..Default::default()
217 }
218 .into();
219
220 let decl = VarDeclarator {
221 span: DUMMY_SP,
222 name: s.into(),
223 init: Some(init),
224 definite: Default::default(),
225 };
226 *item = VarDecl {
227 span: import.span,
228 kind: VarDeclKind::Var,
229 declare: false,
230 decls: vec![decl],
231 ..Default::default()
232 }
233 .into()
234 }
235 }
236}
237
238#[track_caller]
239pub fn test_transform<F, P>(
240 syntax: Syntax,
241 is_module: Option<bool>,
242 tr: F,
243 input: &str,
244 expected: &str,
245) where
246 F: FnOnce(&mut Tester) -> P,
247 P: Pass,
248{
249 Tester::run(|tester| {
250 let expected = tester.apply_transform(
251 swc_ecma_utils::DropSpan,
252 "output.js",
253 syntax,
254 is_module,
255 expected,
256 )?;
257
258 let expected_comments = take(&mut tester.comments);
259
260 println!("----- Actual -----");
261
262 let tr = (tr(tester), visit_mut_pass(RegeneratorHandler));
263 let actual = tester.apply_transform(tr, "input.js", syntax, is_module, input)?;
264
265 match ::std::env::var("PRINT_HYGIENE") {
266 Ok(ref s) if s == "1" => {
267 let hygiene_src = tester.print(
268 &actual.clone().fold_with(&mut HygieneVisualizer),
269 &tester.comments.clone(),
270 );
271 println!("----- Hygiene -----\n{hygiene_src}");
272 }
273 _ => {}
274 }
275
276 let actual = actual
277 .apply(::swc_ecma_utils::DropSpan)
278 .apply(hygiene::hygiene())
279 .apply(fixer::fixer(Some(&tester.comments)));
280
281 println!("{:?}", tester.comments);
282 println!("{expected_comments:?}");
283
284 {
285 let (actual_leading, actual_trailing) = tester.comments.borrow_all();
286 let (expected_leading, expected_trailing) = expected_comments.borrow_all();
287
288 if actual == expected
289 && *actual_leading == *expected_leading
290 && *actual_trailing == *expected_trailing
291 {
292 return Ok(());
293 }
294 }
295
296 let (actual_src, expected_src) = (
297 tester.print(&actual, &tester.comments.clone()),
298 tester.print(&expected, &expected_comments),
299 );
300
301 if actual_src == expected_src {
302 return Ok(());
303 }
304
305 println!(">>>>> {} <<<<<\n{}", Color::Green.paint("Orig"), input);
306 println!(">>>>> {} <<<<<\n{}", Color::Green.paint("Code"), actual_src);
307
308 if actual_src != expected_src {
309 panic!(
310 r#"assertion failed: `(left == right)`
311 {}"#,
312 ::testing::diff(&actual_src, &expected_src),
313 );
314 }
315
316 Err(())
317 });
318}
319
320#[doc(hidden)]
322#[track_caller]
323pub fn test_inline_input_output<F, P>(
324 syntax: Syntax,
325 is_module: Option<bool>,
326 tr: F,
327 input: &str,
328 output: &str,
329) where
330 F: FnOnce(&mut Tester) -> P,
331 P: Pass,
332{
333 let _logger = testing::init();
334
335 let expected = output;
336
337 let expected_src = Tester::run(|tester| {
338 let expected_program =
339 tester.apply_transform(noop_pass(), "expected.js", syntax, is_module, expected)?;
340
341 let expected_src = tester.print(&expected_program, &Default::default());
342
343 println!(
344 "----- {} -----\n{}",
345 Color::Green.paint("Expected"),
346 expected_src
347 );
348
349 Ok(expected_src)
350 });
351
352 let actual_src = Tester::run_captured(|tester| {
353 println!("----- {} -----\n{}", Color::Green.paint("Input"), input);
354
355 let tr = tr(tester);
356
357 println!("----- {} -----", Color::Green.paint("Actual"));
358
359 let actual = tester.apply_transform(tr, "input.js", syntax, is_module, input)?;
360
361 match ::std::env::var("PRINT_HYGIENE") {
362 Ok(ref s) if s == "1" => {
363 let hygiene_src = tester.print(
364 &actual.clone().fold_with(&mut HygieneVisualizer),
365 &Default::default(),
366 );
367 println!(
368 "----- {} -----\n{}",
369 Color::Green.paint("Hygiene"),
370 hygiene_src
371 );
372 }
373 _ => {}
374 }
375
376 let actual = actual
377 .apply(crate::hygiene::hygiene())
378 .apply(crate::fixer::fixer(Some(&tester.comments)));
379
380 let actual_src = tester.print(&actual, &Default::default());
381
382 Ok(actual_src)
383 })
384 .0
385 .unwrap();
386
387 assert_eq!(
388 expected_src, actual_src,
389 "Exepcted:\n{expected_src}\nActual:\n{actual_src}\n",
390 );
391}
392
393#[doc(hidden)]
395#[track_caller]
396pub fn test_inlined_transform<F, P>(
397 test_name: &str,
398 syntax: Syntax,
399 module: Option<bool>,
400 tr: F,
401 input: &str,
402) where
403 F: FnOnce(&mut Tester) -> P,
404 P: Pass,
405{
406 let loc = panic::Location::caller();
407
408 let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
409
410 let test_file_path = CARGO_WORKSPACE_ROOT.join(loc.file());
411
412 let snapshot_dir = manifest_dir.join("tests").join("__swc_snapshots__").join(
413 test_file_path
414 .strip_prefix(&manifest_dir)
415 .expect("test_inlined_transform does not support paths outside of the crate root"),
416 );
417
418 test_fixture_inner(
419 syntax,
420 Box::new(move |tester| Box::new(tr(tester))),
421 input,
422 &snapshot_dir.join(format!("{test_name}.js")),
423 FixtureTestConfig {
424 module,
425 ..Default::default()
426 },
427 )
428}
429
430#[doc(hidden)]
432#[macro_export]
433macro_rules! test_location {
434 () => {{
435 $crate::TestLocation {}
436 }};
437}
438
439#[macro_export]
440macro_rules! test_inline {
441 (ignore, $syntax:expr, $tr:expr, $test_name:ident, $input:expr, $output:expr) => {
442 #[test]
443 #[ignore]
444 fn $test_name() {
445 $crate::test_inline_input_output($syntax, None, $tr, $input, $output)
446 }
447 };
448
449 ($syntax:expr, $tr:expr, $test_name:ident, $input:expr, $output:expr) => {
450 #[test]
451 fn $test_name() {
452 $crate::test_inline_input_output($syntax, None, $tr, $input, $output)
453 }
454 };
455}
456
457test_inline!(
458 ignore,
459 Syntax::default(),
460 |_| noop_pass(),
461 test_inline_ignored,
462 "class Foo {}",
463 "class Foo {}"
464);
465
466test_inline!(
467 Syntax::default(),
468 |_| noop_pass(),
469 test_inline_pass,
470 "class Foo {}",
471 "class Foo {}"
472);
473
474#[test]
475#[should_panic]
476fn test_inline_should_fail() {
477 test_inline_input_output(
478 Default::default(),
479 None,
480 |_| noop_pass(),
481 "class Foo {}",
482 "",
483 );
484}
485
486#[macro_export]
487macro_rules! test {
488 (ignore, $syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
489 #[test]
490 #[ignore]
491 fn $test_name() {
492 $crate::test_inlined_transform(stringify!($test_name), $syntax, None, $tr, $input)
493 }
494 };
495
496 ($syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
497 #[test]
498 fn $test_name() {
499 $crate::test_inlined_transform(stringify!($test_name), $syntax, None, $tr, $input)
500 }
501 };
502
503 (module, $syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
504 #[test]
505 fn $test_name() {
506 $crate::test_inlined_transform(stringify!($test_name), $syntax, Some(true), $tr, $input)
507 }
508 };
509
510 (script, $syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
511 #[test]
512 fn $test_name() {
513 $crate::test_inlined_script_transform(
514 stringify!($test_name),
515 $syntax,
516 Some(false),
517 $tr,
518 $input,
519 )
520 }
521 };
522
523 ($syntax:expr, $tr:expr, $test_name:ident, $input:expr, ok_if_code_eq) => {
524 #[test]
525 fn $test_name() {
526 $crate::test_inlined_transform(stringify!($test_name), $syntax, None, $tr, $input)
527 }
528 };
529}
530
531pub fn compare_stdout<F, P>(syntax: Syntax, tr: F, input: &str)
534where
535 F: FnOnce(&mut Tester<'_>) -> P,
536 P: Pass,
537{
538 Tester::run(|tester| {
539 let tr = (tr(tester), visit_mut_pass(RegeneratorHandler));
540
541 let program = tester.apply_transform(tr, "input.js", syntax, Some(true), input)?;
542
543 match ::std::env::var("PRINT_HYGIENE") {
544 Ok(ref s) if s == "1" => {
545 let hygiene_src = tester.print(
546 &program.clone().fold_with(&mut HygieneVisualizer),
547 &tester.comments.clone(),
548 );
549 println!("----- Hygiene -----\n{hygiene_src}");
550 }
551 _ => {}
552 }
553
554 let mut program = program
555 .apply(hygiene::hygiene())
556 .apply(fixer::fixer(Some(&tester.comments)));
557
558 let src_without_helpers = tester.print(&program, &tester.comments.clone());
559 program = program.apply(inject_helpers(Mark::fresh(Mark::root())));
560
561 let transformed_src = tester.print(&program, &tester.comments.clone());
562
563 println!("\t>>>>> Orig <<<<<\n{input}\n\t>>>>> Code <<<<<\n{src_without_helpers}");
564
565 let expected = stdout_of(input).unwrap();
566
567 println!("\t>>>>> Expected stdout <<<<<\n{expected}");
568
569 let actual = stdout_of(&transformed_src).unwrap();
570
571 assert_eq!(expected, actual);
572
573 Ok(())
574 })
575}
576
577pub fn exec_tr<F, P>(_test_name: &str, syntax: Syntax, tr: F, input: &str)
579where
580 F: FnOnce(&mut Tester<'_>) -> P,
581 P: Pass,
582{
583 Tester::run(|tester| {
584 let tr = (tr(tester), visit_mut_pass(RegeneratorHandler));
585
586 let program = tester.apply_transform(
587 tr,
588 "input.js",
589 syntax,
590 Some(true),
591 &format!(
592 "it('should work', async function () {{
593 {input}
594 }})"
595 ),
596 )?;
597 match ::std::env::var("PRINT_HYGIENE") {
598 Ok(ref s) if s == "1" => {
599 let hygiene_src = tester.print(
600 &program.clone().fold_with(&mut HygieneVisualizer),
601 &tester.comments.clone(),
602 );
603 println!("----- Hygiene -----\n{hygiene_src}");
604 }
605 _ => {}
606 }
607
608 let mut program = program
609 .apply(hygiene::hygiene())
610 .apply(fixer::fixer(Some(&tester.comments)));
611
612 let src_without_helpers = tester.print(&program, &tester.comments.clone());
613 program = program.apply(inject_helpers(Mark::fresh(Mark::root())));
614
615 let src = tester.print(&program, &tester.comments.clone());
616
617 println!(
618 "\t>>>>> {} <<<<<\n{}\n\t>>>>> {} <<<<<\n{}",
619 Color::Green.paint("Orig"),
620 input,
621 Color::Green.paint("Code"),
622 src_without_helpers
623 );
624
625 exec_with_node_test_runner(&src).map(|_| {})
626 })
627}
628
629fn calc_hash(s: &str) -> String {
630 let mut hasher = Sha256::new();
631 hasher.update(s.as_bytes());
632 let sum = hasher.finalize();
633
634 hex::encode(sum)
635}
636
637fn exec_with_node_test_runner(src: &str) -> Result<(), ()> {
638 let root = CARGO_TARGET_DIR.join("swc-es-exec-testing");
639
640 create_dir_all(&root).expect("failed to create parent directory for temp directory");
641
642 let hash = calc_hash(src);
643 let success_cache = root.join(format!("{hash}.success"));
644
645 if env::var("SWC_CACHE_TEST").unwrap_or_default() == "1" {
646 println!("Trying cache as `SWC_CACHE_TEST` is `1`");
647
648 if success_cache.exists() {
649 println!("Cache: success");
650 return Ok(());
651 }
652 }
653
654 let tmp_dir = tempdir_in(&root).expect("failed to create a temp directory");
655 create_dir_all(&tmp_dir).unwrap();
656
657 let path = tmp_dir.path().join(format!("{hash}.test.js"));
658
659 let mut tmp = OpenOptions::new()
660 .create(true)
661 .truncate(true)
662 .write(true)
663 .open(&path)
664 .expect("failed to create a temp file");
665 write!(tmp, "{src}").expect("failed to write to temp file");
666 tmp.flush().unwrap();
667
668 let test_runner_path = find_executable("mocha").expect("failed to find `mocha` from path");
669
670 let mut base_cmd = if cfg!(target_os = "windows") {
671 let mut c = Command::new("cmd");
672 c.arg("/C").arg(&test_runner_path);
673 c
674 } else {
675 Command::new(&test_runner_path)
676 };
677
678 let output = base_cmd
679 .arg(format!("{}", path.display()))
680 .arg("--color")
681 .current_dir(root)
682 .output()
683 .expect("failed to run mocha");
684
685 println!(">>>>> {} <<<<<", Color::Red.paint("Stdout"));
686 println!("{}", String::from_utf8_lossy(&output.stdout));
687 println!(">>>>> {} <<<<<", Color::Red.paint("Stderr"));
688 println!("{}", String::from_utf8_lossy(&output.stderr));
689
690 if output.status.success() {
691 fs::write(&success_cache, "").unwrap();
692 return Ok(());
693 }
694 let dir_name = path.display().to_string();
695 ::std::mem::forget(tmp_dir);
696 panic!("Execution failed: {dir_name}")
697}
698
699fn stdout_of(code: &str) -> Result<String, Error> {
700 exec_node_js(
701 code,
702 JsExecOptions {
703 cache: true,
704 module: false,
705 ..Default::default()
706 },
707 )
708}
709
710#[macro_export]
712macro_rules! test_exec {
713 (@check) => {
714 if ::std::env::var("EXEC").unwrap_or(String::from("")) == "0" {
715 return;
716 }
717 };
718
719 (ignore, $syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
720 #[test]
721 #[ignore]
722 fn $test_name() {
723 $crate::exec_tr(stringify!($test_name), $syntax, $tr, $input)
724 }
725 };
726
727 ($syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
728 #[test]
729 fn $test_name() {
730 test_exec!(@check);
731 $crate::exec_tr(stringify!($test_name), $syntax, $tr, $input)
732 }
733 };
734}
735
736#[macro_export]
739macro_rules! compare_stdout {
740 ($syntax:expr, $tr:expr, $test_name:ident, $input:expr) => {
741 #[test]
742 fn $test_name() {
743 $crate::compare_stdout($syntax, $tr, $input)
744 }
745 };
746}
747
748pub struct HygieneTester;
750impl Fold for HygieneTester {
751 fn fold_ident(&mut self, ident: Ident) -> Ident {
752 Ident {
753 sym: format!("{}__{}", ident.sym, ident.ctxt.as_u32()).into(),
754 ..ident
755 }
756 }
757
758 fn fold_member_prop(&mut self, p: MemberProp) -> MemberProp {
759 match p {
760 MemberProp::Computed(..) => p.fold_children_with(self),
761 _ => p,
762 }
763 }
764
765 fn fold_prop_name(&mut self, p: PropName) -> PropName {
766 match p {
767 PropName::Computed(..) => p.fold_children_with(self),
768 _ => p,
769 }
770 }
771}
772
773pub struct HygieneVisualizer;
774impl Fold for HygieneVisualizer {
775 fn fold_ident(&mut self, ident: Ident) -> Ident {
776 Ident {
777 sym: format!("{}{:?}", ident.sym, ident.ctxt).into(),
778 ..ident
779 }
780 }
781}
782
783pub fn parse_options<T>(dir: &Path) -> T
786where
787 T: DeserializeOwned,
788{
789 type Map = serde_json::Map<String, serde_json::Value>;
790
791 let mut value = Map::default();
792
793 fn check(dir: &Path) -> Option<Map> {
794 let file = dir.join("options.json");
795 if let Ok(v) = read_to_string(&file) {
796 eprintln!("Using options.json at {}", file.display());
797 eprintln!("----- {} -----\n{}", Color::Green.paint("Options"), v);
798
799 return Some(
800 serde_json::from_str(&v)
801 .unwrap_or_else(|err| panic!("failed to deserialize options.json: {err}\n{v}")),
802 );
803 }
804
805 None
806 }
807
808 let mut c = Some(dir);
809
810 while let Some(dir) = c {
811 if let Some(new) = check(dir) {
812 for (k, v) in new {
813 if !value.contains_key(&k) {
814 value.insert(k, v);
815 }
816 }
817 }
818
819 c = dir.parent();
820 }
821
822 serde_json::from_value(serde_json::Value::Object(value.clone()))
823 .unwrap_or_else(|err| panic!("failed to deserialize options.json: {err}\n{value:?}"))
824}
825
826#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
828pub struct FixtureTestConfig {
829 pub sourcemap: bool,
833
834 pub allow_error: bool,
841
842 pub module: Option<bool>,
848}
849
850pub fn test_fixture<P>(
852 syntax: Syntax,
853 tr: &dyn Fn(&mut Tester) -> P,
854 input: &Path,
855 output: &Path,
856 config: FixtureTestConfig,
857) where
858 P: Pass,
859{
860 let input = fs::read_to_string(input).unwrap();
861
862 test_fixture_inner(
863 syntax,
864 Box::new(|tester| Box::new(tr(tester))),
865 &input,
866 output,
867 config,
868 );
869}
870
871fn test_fixture_inner<'a>(
872 syntax: Syntax,
873 tr: Box<dyn 'a + FnOnce(&mut Tester) -> Box<dyn 'a + Pass>>,
874 input: &str,
875 output: &Path,
876 config: FixtureTestConfig,
877) {
878 let _logger = testing::init();
879
880 let expected = read_to_string(output);
881 let _is_really_expected = expected.is_ok();
882 let expected = expected.unwrap_or_default();
883
884 let expected_src = Tester::run(|tester| {
885 let expected_program =
886 tester.apply_transform(noop_pass(), "expected.js", syntax, config.module, &expected)?;
887
888 let expected_src = tester.print(&expected_program, &tester.comments.clone());
889
890 println!(
891 "----- {} -----\n{}",
892 Color::Green.paint("Expected"),
893 expected_src
894 );
895
896 Ok(expected_src)
897 });
898
899 let mut src_map = if config.sourcemap {
900 Some(Vec::new())
901 } else {
902 None
903 };
904
905 let mut sourcemap = None;
906
907 let (actual_src, stderr) = Tester::run_captured(|tester| {
908 eprintln!("----- {} -----\n{}", Color::Green.paint("Input"), input);
909
910 let tr = tr(tester);
911
912 eprintln!("----- {} -----", Color::Green.paint("Actual"));
913
914 let actual = tester.apply_transform(tr, "input.js", syntax, config.module, input)?;
915
916 eprintln!("----- {} -----", Color::Green.paint("Comments"));
917 eprintln!("{:?}", tester.comments);
918
919 match ::std::env::var("PRINT_HYGIENE") {
920 Ok(ref s) if s == "1" => {
921 let hygiene_src = tester.print(
922 &actual.clone().fold_with(&mut HygieneVisualizer),
923 &tester.comments.clone(),
924 );
925 println!(
926 "----- {} -----\n{}",
927 Color::Green.paint("Hygiene"),
928 hygiene_src
929 );
930 }
931 _ => {}
932 }
933
934 let actual = actual
935 .apply(crate::hygiene::hygiene())
936 .apply(crate::fixer::fixer(Some(&tester.comments)));
937
938 let actual_src = {
939 let module = &actual;
940 let comments: &Rc<SingleThreadedComments> = &tester.comments.clone();
941
942 let mut buf = vec![];
943 {
944 let mut emitter = Emitter {
945 cfg: Default::default(),
946 cm: tester.cm.clone(),
947 wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new(
948 tester.cm.clone(),
949 "\n",
950 &mut buf,
951 src_map.as_mut(),
952 )),
953 comments: Some(comments),
954 };
955
956 emitter.emit_program(module).unwrap();
958 }
959
960 if let Some(src_map) = &mut src_map {
961 sourcemap = Some(
962 tester
963 .cm
964 .build_source_map(src_map, None, SourceMapConfigImpl),
965 );
966 }
967
968 String::from_utf8(buf).expect("codegen generated non-utf8 output")
969 };
970
971 Ok(actual_src)
972 });
973
974 if config.allow_error {
975 stderr
976 .compare_to_file(output.with_extension("stderr"))
977 .unwrap();
978 } else if !stderr.is_empty() {
979 panic!("stderr: {stderr}");
980 }
981
982 if let Some(actual_src) = actual_src {
983 eprintln!("{actual_src}");
984
985 if let Some(sourcemap) = &sourcemap {
986 eprintln!("----- ----- ----- ----- -----");
987 eprintln!("SourceMap: {}", visualizer_url(&actual_src, sourcemap));
988 }
989
990 if actual_src != expected_src {
991 NormalizedOutput::from(actual_src)
992 .compare_to_file(output)
993 .unwrap();
994 }
995 }
996
997 if let Some(sourcemap) = sourcemap {
998 let map = {
999 let mut buf = Vec::new();
1000 sourcemap.to_writer(&mut buf).unwrap();
1001 String::from_utf8(buf).unwrap()
1002 };
1003 NormalizedOutput::from(map)
1004 .compare_to_file(output.with_extension("map"))
1005 .unwrap();
1006 }
1007}
1008
1009fn visualizer_url(code: &str, map: &swc_sourcemap::SourceMap) -> String {
1011 let map = {
1012 let mut buf = Vec::new();
1013 map.to_writer(&mut buf).unwrap();
1014 String::from_utf8(buf).unwrap()
1015 };
1016
1017 let code_len = format!("{}\0", code.len());
1018 let map_len = format!("{}\0", map.len());
1019 let hash = BASE64_STANDARD.encode(format!("{code_len}{code}{map_len}{map}"));
1020
1021 format!("https://evanw.github.io/source-map-visualization/#{hash}")
1022}
1023
1024struct SourceMapConfigImpl;
1025
1026impl SourceMapGenConfig for SourceMapConfigImpl {
1027 fn file_name_to_source(&self, f: &swc_common::FileName) -> String {
1028 f.to_string()
1029 }
1030
1031 fn inline_sources_content(&self, _: &swc_common::FileName) -> bool {
1032 true
1033 }
1034}