From 8c624abf9c5821acbbd266cd241c0bd97345fb21 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Fri, 10 Nov 2023 12:50:54 +0800 Subject: [PATCH] feat(transformer/react-jsx): throw the `pragma and pragmaFrag cannot be set when runtime is automatic` error (#1196) close: #1194 Here's a rough implementation of my idea of throwing an error. --- Cargo.lock | 2 + crates/oxc_transformer/Cargo.toml | 11 ++-- .../oxc_transformer/examples/transformer.rs | 3 +- crates/oxc_transformer/src/context.rs | 14 ++++- crates/oxc_transformer/src/lib.rs | 26 ++++++++-- crates/oxc_transformer/src/react_jsx/mod.rs | 20 ++++++- crates/oxc_transformer/src/tester.rs | 9 ++-- crates/oxc_wasm/src/lib.rs | 6 ++- tasks/benchmark/benches/transformer.rs | 3 +- tasks/transform_conformance/Cargo.toml | 1 + tasks/transform_conformance/babel.snap.md | 48 ++++++++++++----- tasks/transform_conformance/src/test_case.rs | 52 ++++++++++++++----- 12 files changed, 149 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5eeccbb5f..bf5090eb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1855,6 +1855,7 @@ dependencies = [ "mimalloc", "oxc_allocator", "oxc_codegen", + "oxc_diagnostics", "oxc_parser", "oxc_semantic", "oxc_span", @@ -1873,6 +1874,7 @@ dependencies = [ "oxc_allocator", "oxc_ast", "oxc_codegen", + "oxc_diagnostics", "oxc_parser", "oxc_semantic", "oxc_span", diff --git a/crates/oxc_transformer/Cargo.toml b/crates/oxc_transformer/Cargo.toml index 17059da7e..d5af3a59e 100644 --- a/crates/oxc_transformer/Cargo.toml +++ b/crates/oxc_transformer/Cargo.toml @@ -16,11 +16,12 @@ categories.workspace = true doctest = false [dependencies] -oxc_ast = { workspace = true } -oxc_span = { workspace = true } -oxc_allocator = { workspace = true } -oxc_syntax = { workspace = true } -oxc_semantic = { workspace = true } +oxc_ast = { workspace = true } +oxc_span = { workspace = true } +oxc_allocator = { workspace = true } +oxc_syntax = { workspace = true } +oxc_semantic = { workspace = true } +oxc_diagnostics = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/crates/oxc_transformer/examples/transformer.rs b/crates/oxc_transformer/examples/transformer.rs index 2c7f63ed3..fcde5a0cb 100644 --- a/crates/oxc_transformer/examples/transformer.rs +++ b/crates/oxc_transformer/examples/transformer.rs @@ -49,7 +49,8 @@ fn main() { }), ..TransformOptions::default() }; - Transformer::new(&allocator, source_type, semantic, transform_options).build(program); + Transformer::new(&allocator, source_type, semantic, transform_options).build(program).unwrap(); + let printed = Codegen::::new(source_text.len(), codegen_options).build(program); println!("Transformed:\n"); println!("{printed}"); diff --git a/crates/oxc_transformer/src/context.rs b/crates/oxc_transformer/src/context.rs index 6275de82a..d234d966d 100644 --- a/crates/oxc_transformer/src/context.rs +++ b/crates/oxc_transformer/src/context.rs @@ -1,9 +1,11 @@ use std::{ cell::{Ref, RefCell, RefMut}, + mem, rc::Rc, }; use oxc_ast::AstBuilder; +use oxc_diagnostics::Error; use oxc_semantic::{ScopeId, ScopeTree, Semantic, SymbolId, SymbolTable}; use oxc_span::Atom; @@ -11,11 +13,12 @@ use oxc_span::Atom; pub struct TransformerCtx<'a> { pub ast: Rc>, semantic: Rc>>, + errors: Rc>>, } impl<'a> TransformerCtx<'a> { pub fn new(ast: Rc>, semantic: Rc>>) -> Self { - Self { ast, semantic } + Self { ast, semantic, errors: Rc::new(RefCell::new(vec![])) } } pub fn semantic(&self) -> Ref<'_, Semantic<'a>> { @@ -38,4 +41,13 @@ impl<'a> TransformerCtx<'a> { // TODO: use the correct scope and symbol id self.scopes_mut().add_binding(ScopeId::new(0), name, SymbolId::new(0)); } + + pub fn errors(&self) -> Vec { + mem::take(&mut self.errors.borrow_mut()) + } + + /// Push a Transform Error + pub fn error>(&mut self, error: T) { + self.errors.borrow_mut().push(error.into()); + } } diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 4b3a25253..2570e23a5 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -22,11 +22,12 @@ mod tester; mod typescript; mod utils; -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, rc::Rc, sync::Arc}; use es2015::TemplateLiterals; -use oxc_allocator::{Allocator, Vec}; +use oxc_allocator::Allocator; use oxc_ast::{ast::*, AstBuilder, VisitMut}; +use oxc_diagnostics::Error; use oxc_semantic::Semantic; use oxc_span::SourceType; @@ -44,6 +45,7 @@ pub use crate::{ }; pub struct Transformer<'a> { + ctx: TransformerCtx<'a>, #[allow(unused)] typescript: Option>, react_jsx: Option>, @@ -76,7 +78,9 @@ impl<'a> Transformer<'a> { Rc::clone(&ast), Rc::new(RefCell::new(semantic)), ); + Self { + ctx: ctx.clone(), // TODO: pass verbatim_module_syntax from user config typescript: source_type.is_typescript().then(|| TypeScript::new(Rc::clone(&ast), ctx.clone(), false)), regexp_flags: RegexpFlags::new(Rc::clone(&ast), &options), @@ -91,8 +95,22 @@ impl<'a> Transformer<'a> { } } - pub fn build(mut self, program: &mut Program<'a>) { + /// # Errors + /// Returns `Vec` if any errors were collected during the transformation. + pub fn build(mut self, program: &mut Program<'a>) -> Result<(), Vec> { self.visit_program(program); + let errors: Vec<_> = self + .ctx + .errors() + .into_iter() + .map(|e| e.with_source_code(Arc::new(self.ctx.semantic().source_text().to_string()))) + .collect(); + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } } } @@ -108,7 +126,7 @@ impl<'a> VisitMut<'a> for Transformer<'a> { self.react_jsx.as_mut().map(|t| t.add_react_jsx_runtime_imports(program)); } - fn visit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { + fn visit_statements(&mut self, stmts: &mut oxc_allocator::Vec<'a, Statement<'a>>) { for stmt in stmts.iter_mut() { self.visit_statement(stmt); } diff --git a/crates/oxc_transformer/src/react_jsx/mod.rs b/crates/oxc_transformer/src/react_jsx/mod.rs index 9ab7e8180..e6cac8f46 100644 --- a/crates/oxc_transformer/src/react_jsx/mod.rs +++ b/crates/oxc_transformer/src/react_jsx/mod.rs @@ -4,6 +4,10 @@ use std::rc::Rc; use oxc_allocator::Vec; use oxc_ast::{ast::*, AstBuilder}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; use oxc_span::{Atom, SPAN}; use oxc_syntax::{ identifier::{is_irregular_whitespace, is_line_terminator}, @@ -13,6 +17,11 @@ use oxc_syntax::{ pub use self::options::{ReactJsxOptions, ReactJsxRuntime}; use crate::context::TransformerCtx; +#[derive(Debug, Error, Diagnostic)] +#[error("pragma and pragmaFrag cannot be set when runtime is automatic.")] +#[diagnostic(severity(warning), help("Remove `pragma` and `pragmaFrag` options."))] +struct PragmaAndPragmaFragCannotBeSet; + /// Transform React JSX /// /// References: @@ -20,7 +29,7 @@ use crate::context::TransformerCtx; /// * pub struct ReactJsx<'a> { ast: Rc>, - ctx: Rc>, + ctx: TransformerCtx<'a>, options: ReactJsxOptions, imports: Vec<'a, Statement<'a>>, @@ -83,7 +92,6 @@ impl<'a> ReactJsx<'a> { Atom::from(format!("{}/jsx-runtime", options.import_source)) }; - let ctx = Rc::new(ctx); Self { ast, ctx, @@ -114,6 +122,14 @@ impl<'a> ReactJsx<'a> { if self.options.runtime.is_classic() { return; } + + if self.options.pragma != "React.createElement" + || self.options.pragma_frag != "React.Fragment" + { + self.ctx.error(PragmaAndPragmaFragCannotBeSet); + return; + } + let imports = self.ast.move_statement_vec(&mut self.imports); let index = program .body diff --git a/crates/oxc_transformer/src/tester.rs b/crates/oxc_transformer/src/tester.rs index c3419945d..8b0b29700 100644 --- a/crates/oxc_transformer/src/tester.rs +++ b/crates/oxc_transformer/src/tester.rs @@ -1,5 +1,6 @@ use oxc_allocator::Allocator; use oxc_codegen::{Codegen, CodegenOptions}; +use oxc_diagnostics::Error; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; use oxc_span::SourceType; @@ -22,19 +23,19 @@ impl Tester { pub fn test(&self, tests: &[(&str, &str)]) { for (source_text, expected) in tests { - let transformed = self.transform(source_text); + let transformed = self.transform(source_text).unwrap(); let expected = self.codegen(expected); assert_eq!(transformed, expected, "{source_text}"); } } - fn transform(&self, source_text: &str) -> String { + fn transform(&self, source_text: &str) -> Result> { let program = Parser::new(&self.allocator, source_text, self.source_type).parse().program; let semantic = SemanticBuilder::new(source_text, self.source_type).build(&program).semantic; let program = self.allocator.alloc(program); Transformer::new(&self.allocator, self.source_type, semantic, self.options.clone()) - .build(program); - Codegen::::new(source_text.len(), CodegenOptions).build(program) + .build(program) + .map(move |()| Codegen::::new(source_text.len(), CodegenOptions).build(program)) } fn codegen(&self, source_text: &str) -> String { diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index 3fb1dbfee..deb5bd563 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -215,7 +215,11 @@ impl Oxc { let semantic = SemanticBuilder::new(source_text, source_type).build(program).semantic; let options = TransformOptions { target: TransformTarget::ES2015, ..TransformOptions::default() }; - Transformer::new(&allocator, source_type, semantic, options).build(program); + let result = + Transformer::new(&allocator, source_type, semantic, options).build(program); + if let Err(errs) = result { + self.save_diagnostics(errs); + } } let program = allocator.alloc(program); diff --git a/tasks/benchmark/benches/transformer.rs b/tasks/benchmark/benches/transformer.rs index ce3a1c5e0..8c00c694a 100644 --- a/tasks/benchmark/benches/transformer.rs +++ b/tasks/benchmark/benches/transformer.rs @@ -38,7 +38,8 @@ fn bench_transformer(criterion: &mut Criterion) { let program = allocator.alloc(program); let transform_options = TransformOptions::default(); Transformer::new(&allocator, source_type, semantic, transform_options) - .build(black_box(program)); + .build(black_box(program)) + .unwrap(); allocator }); }); diff --git a/tasks/transform_conformance/Cargo.toml b/tasks/transform_conformance/Cargo.toml index 8360dcb4e..6ee72ccbe 100644 --- a/tasks/transform_conformance/Cargo.toml +++ b/tasks/transform_conformance/Cargo.toml @@ -21,6 +21,7 @@ oxc_semantic = { workspace = true } oxc_codegen = { workspace = true } oxc_transformer = { workspace = true } oxc_tasks_common = { workspace = true } +oxc_diagnostics = { workspace = true } serde_json = { workspace = true } serde = { workspace = true } diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index ad833e72e..5bdf3e904 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 283/1113 +Passed: 263/1113 # All Passed: * babel-plugin-transform-numeric-separator @@ -14,7 +14,7 @@ Passed: 283/1113 * transform-u/basic/input.js * transform-u/string-properties/input.js -# babel-plugin-transform-class-properties (7/266) +# babel-plugin-transform-class-properties (0/266) * assumption-constantSuper/complex-super-class/input.js * assumption-constantSuper/instance-field/input.js * assumption-constantSuper/static-field/input.js @@ -56,6 +56,7 @@ Passed: 283/1113 * decorators-legacy-interop/local-define-property/input.js * decorators-legacy-interop/loose/input.js * decorators-legacy-interop/strict/input.js +* decorators-legacy-interop/wrong-order/input.js * nested-class/super-call-in-decorator/input.js * nested-class/super-call-in-key/input.js * nested-class/super-property-in-accessor-key/input.js @@ -203,6 +204,7 @@ Passed: 283/1113 * private-loose/super-statement/input.js * private-loose/update/input.js * public/arrow-static-this-without-transform/input.js +* public/arrow-this-without-transform/input.js * public/assignment/input.js * public/call/input.js * public/class-shadow-builtins/input.mjs @@ -236,11 +238,16 @@ Passed: 283/1113 * public/static-this/input.js * public/static-undefined/input.js * public/super-call/input.js +* public/super-destructuring-array-pattern/input.js +* public/super-destructuring-array-pattern-1/input.js +* public/super-destructuring-object-pattern/input.js +* public/super-destructuring-object-pattern-1/input.js * public/super-expression/input.js * public/super-statement/input.js * public/super-with-collision/input.js * public/update/input.js * public-loose/arrow-static-this-without-transform/input.js +* public-loose/arrow-this-without-transform/input.js * public-loose/class-shadow-builtins/input.mjs * public-loose/computed/input.js * public-loose/constructor-collision/input.js @@ -292,7 +299,7 @@ Passed: 283/1113 * integration-loose/preserve-comments/input.js * integration-loose/super-static-block/input.js -# babel-plugin-transform-private-methods (7/136) +# babel-plugin-transform-private-methods (0/136) * accessors/basic/input.js * accessors/get-only-setter/input.js * accessors/preserve-comments/input.js @@ -318,8 +325,15 @@ Passed: 283/1113 * accessors-privateFieldsAsSymbols/set-only-getter/input.js * accessors-privateFieldsAsSymbols/updates/input.js * assumption-constantSuper/private-method-super/input.js +* duplicated-names/get-get/input.js +* duplicated-names/get-method/input.js * duplicated-names/get-set/input.js +* duplicated-names/method-get/input.js +* duplicated-names/method-method/input.js +* duplicated-names/method-set/input.js * duplicated-names/set-get/input.js +* duplicated-names/set-method/input.js +* duplicated-names/set-set/input.js * private-method/assignment/input.js * private-method/async/input.js * private-method/before-fields/input.js @@ -493,7 +507,7 @@ Passed: 283/1113 * export-namespace/namespace-string/input.mjs * export-namespace/namespace-typescript/input.mjs -# babel-plugin-transform-dynamic-import (2/19) +# babel-plugin-transform-dynamic-import (0/19) * amd/missing-plugin/input.mjs * amd/module/input.mjs * amd/no-interop/input.js @@ -507,6 +521,8 @@ Passed: 283/1113 * commonjs/shadowed-require/input.js * commonjs/template-literal/input.js * commonjs/to-string/input.js +* missing-module-transform/missing-module-transform/input.js +* systemjs/missing-plugin/input.mjs * systemjs/missing-plugin-babel-7/input.mjs * systemjs/module/input.mjs * systemjs/script/input.js @@ -516,7 +532,7 @@ Passed: 283/1113 * assumption-noDocumentAll/transform-in-default-param/input.js * nullish-coalescing/transform-in-default-param/input.js -# babel-plugin-transform-optional-chaining (4/46) +# babel-plugin-transform-optional-chaining (1/46) * assumption-noDocumentAll/assignment/input.js * assumption-noDocumentAll/cast-to-boolean/input.js * assumption-noDocumentAll/in-function-params/input.js @@ -538,6 +554,9 @@ Passed: 283/1113 * general/in-method-key/input.js * general/in-method-key-loose/input.js * general/in-var-destructuring/input.js +* general/lhs-assignment/input.js +* general/lhs-assignment-read-and-update/input.js +* general/lhs-update/input.js * general/member-access/input.js * general/memoize/input.js * general/memoize-loose/input.js @@ -726,13 +745,14 @@ Passed: 283/1113 * loose/ignoreToPrimitiveHint/input.js * loose/mutableTemplateObject/input.js -# babel-plugin-transform-typescript (84/181) +# babel-plugin-transform-typescript (76/181) * class/abstract-class-decorated/input.ts * class/abstract-class-decorated-method/input.ts * class/abstract-class-decorated-parameter/input.ts * class/accessor-allowDeclareFields-false/input.ts * class/accessor-allowDeclareFields-true/input.ts * class/accessor-allowDeclareFields-true-babel-7/input.ts +* class/declare-not-enabled-babel-7/input.ts * class/decorated-declare-properties/input.ts * class/field-not-initialized-babel-7/input.ts * class/parameter-properties/input.ts @@ -742,6 +762,7 @@ Passed: 283/1113 * class/parameter-properties-with-parameters/input.ts * class/parameter-properties-with-super/input.ts * class/private-method-override-transform-private/input.ts +* class/transform-properties-declare-wrong-order/input.ts * class/uninitialized-definite-with-declare-disabled-babel-7/input.ts * declarations/erased/input.ts * declarations/export-declare-enum/input.ts @@ -764,6 +785,7 @@ Passed: 283/1113 * exports/declared-types/input.ts * exports/export-const-enums/input.ts * exports/export-type-star-from/input.ts +* exports/export=/input.ts * exports/export=-to-cjs/input.ts * exports/imported-types/input.ts * exports/imported-types-only-remove-type-imports/input.ts @@ -782,6 +804,7 @@ Passed: 283/1113 * imports/import-named-type-default-and-named/input.ts * imports/import-removed-exceptions/input.ts * imports/import=-declaration/input.ts +* imports/import=-module/input.ts * imports/import=-module-to-cjs/input.ts * imports/only-remove-type-imports/input.ts * imports/parameter-decorators/input.ts @@ -791,6 +814,8 @@ Passed: 283/1113 * imports/type-only-import-specifier-3/input.ts * imports/type-only-import-specifier-4/input.ts * namespace/alias/input.ts +* namespace/ambient-module-nested/input.ts +* namespace/ambient-module-nested-exported/input.ts * namespace/canonical/input.ts * namespace/clobber-class/input.ts * namespace/clobber-enum/input.ts @@ -803,6 +828,8 @@ Passed: 283/1113 * namespace/module-nested/input.ts * namespace/module-nested-export/input.ts * namespace/multiple/input.ts +* namespace/mutable-fail/input.ts +* namespace/namespace-flag/input.ts * namespace/namespace-nested-module/input.ts * namespace/nested/input.ts * namespace/nested-destructuring/input.ts @@ -825,16 +852,10 @@ Passed: 283/1113 * regression/11061/input.mjs * variable-declaration/non-null-in-optional-chain/input.ts -# babel-plugin-transform-react-jsx (121/170) +# babel-plugin-transform-react-jsx (128/170) * autoImport/after-polyfills-compiled-to-cjs/input.mjs * autoImport/auto-import-react-source-type-script/input.js * autoImport/complicated-scope-module/input.js -* pure/false-pragma-comment-automatic-runtime/input.js -* pure/false-pragma-option-automatic-runtime/input.js -* pure/true-pragma-comment-automatic-runtime/input.js -* pure/true-pragma-option-automatic-runtime/input.js -* pure/unset-pragma-comment-automatic-runtime/input.js -* pure/unset-pragma-option-automatic-runtime/input.js * react/adds-appropriate-newlines-when-using-spread-attribute-babel-7/input.js * react/arrow-functions/input.js * react/assignment-babel-7/input.js @@ -863,7 +884,6 @@ Passed: 283/1113 * react-automatic/should-handle-attributed-elements/input.js * react-automatic/should-throw-error-namespaces-if-not-flag/input.js * react-automatic/should-throw-when-filter-is-specified/input.js -* react-automatic/should-warn-when-pragma-or-pragmaFrag-is-set/input.js * regression/issue-12478-automatic/input.js * regression/issue-12478-classic/input.js * removed-options/invalid-use-builtins-false/input.js diff --git a/tasks/transform_conformance/src/test_case.rs b/tasks/transform_conformance/src/test_case.rs index cc081bc22..1c3cc0d41 100644 --- a/tasks/transform_conformance/src/test_case.rs +++ b/tasks/transform_conformance/src/test_case.rs @@ -5,6 +5,7 @@ use std::{ use oxc_allocator::Allocator; use oxc_codegen::{Codegen, CodegenOptions}; +use oxc_diagnostics::Error; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; use oxc_span::{SourceType, VALID_EXTENSIONS}; @@ -134,7 +135,7 @@ pub trait TestCase { false } - fn transform(&self, path: &Path) -> String { + fn transform(&self, path: &Path) -> Result> { let allocator = Allocator::default(); let source_text = fs::read_to_string(path).unwrap(); let source_type = SourceType::from_path(path).unwrap(); @@ -146,9 +147,12 @@ pub trait TestCase { .semantic; let transformed_program = allocator.alloc(ret.program); - Transformer::new(&allocator, source_type, semantic, self.transform_options()) + let result = Transformer::new(&allocator, source_type, semantic, self.transform_options()) .build(transformed_program); - Codegen::::new(source_text.len(), CodegenOptions).build(transformed_program) + + result.map(|()| { + Codegen::::new(source_text.len(), CodegenOptions).build(transformed_program) + }) } } @@ -207,13 +211,28 @@ impl TestCase for ConformanceTestCase { .build(&ret.program) .semantic; let program = allocator.alloc(ret.program); - Transformer::new(&allocator, source_type, semantic, self.transform_options()) - .build(program); - let transformed_code = Codegen::::new(input.len(), CodegenOptions).build(program); + let transform_options = self.transform_options(); + let transformer = + Transformer::new(&allocator, source_type, semantic, transform_options.clone()); + + let mut transformed_code = String::new(); + let mut actual_errors = String::new(); + let result = transformer.build(program); + if result.is_ok() { + transformed_code = Codegen::::new(input.len(), CodegenOptions).build(program); + } else { + actual_errors = + result.err().unwrap().iter().map(std::string::ToString::to_string).collect(); + } + + let babel_options = self.options(); // Get output.js by using our codeg so code comparison can match. let output = output_path.and_then(|path| fs::read_to_string(path).ok()).map_or_else( || { + if let Some(throws) = &babel_options.throws { + return throws.to_string(); + } // The transformation should be equal to input.js If output.js does not exist. let program = Parser::new(&allocator, &input, source_type).parse().program; Codegen::::new(input.len(), CodegenOptions).build(&program) @@ -225,16 +244,23 @@ impl TestCase for ConformanceTestCase { }, ); - let passed = transformed_code == output; + let passed = transformed_code == output || actual_errors.contains(&output); if filtered { println!("Input:\n"); println!("{input}\n"); println!("Options:"); - println!("{:?}\n", self.transform_options()); - println!("Expected:\n"); - println!("{output}\n"); - println!("Transformed:\n"); - println!("{transformed_code}\n"); + println!("{transform_options:?}\n"); + if babel_options.throws.is_some() { + println!("Expected Errors:\n"); + println!("{output}\n"); + println!("Actual Errors:\n"); + println!("{actual_errors}\n"); + } else { + println!("Expected:\n"); + println!("{output}\n"); + println!("Transformed:\n"); + println!("{transformed_code}"); + } println!("Passed: {passed}"); } passed @@ -291,7 +317,7 @@ impl TestCase for ExecTestCase { } fn test(&self, filtered: bool) -> bool { - let result = self.transform(&self.path); + let result = self.transform(&self.path).expect("Transform failed"); let target_path = self.write_to_test_files(&result); let passed = Self::run_test(&target_path); if filtered {