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.
This commit is contained in:
Dunqing 2023-11-10 12:50:54 +08:00 committed by GitHub
parent b65094b995
commit 8c624abf9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 149 additions and 46 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -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"] }

View file

@ -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::<false>::new(source_text.len(), codegen_options).build(program);
println!("Transformed:\n");
println!("{printed}");

View file

@ -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<AstBuilder<'a>>,
semantic: Rc<RefCell<Semantic<'a>>>,
errors: Rc<RefCell<Vec<Error>>>,
}
impl<'a> TransformerCtx<'a> {
pub fn new(ast: Rc<AstBuilder<'a>>, semantic: Rc<RefCell<Semantic<'a>>>) -> 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<Error> {
mem::take(&mut self.errors.borrow_mut())
}
/// Push a Transform Error
pub fn error<T: Into<Error>>(&mut self, error: T) {
self.errors.borrow_mut().push(error.into());
}
}

View file

@ -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<TypeScript<'a>>,
react_jsx: Option<ReactJsx<'a>>,
@ -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<Error>` if any errors were collected during the transformation.
pub fn build(mut self, program: &mut Program<'a>) -> Result<(), Vec<Error>> {
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);
}

View file

@ -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;
/// * <https://github.com/babel/babel/tree/main/packages/babel-helper-builder-react-jsx>
pub struct ReactJsx<'a> {
ast: Rc<AstBuilder<'a>>,
ctx: Rc<TransformerCtx<'a>>,
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

View file

@ -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<std::string::String, std::vec::Vec<Error>> {
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::<false>::new(source_text.len(), CodegenOptions).build(program)
.build(program)
.map(move |()| Codegen::<false>::new(source_text.len(), CodegenOptions).build(program))
}
fn codegen(&self, source_text: &str) -> String {

View file

@ -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);

View file

@ -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
});
});

View file

@ -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 }

View file

@ -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

View file

@ -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<String, Vec<Error>> {
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::<false>::new(source_text.len(), CodegenOptions).build(transformed_program)
result.map(|()| {
Codegen::<false>::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::<false>::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::<false>::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::<false>::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 {