feat(transformer/react): read comment pragma @jsxRuntime classic / automatic (#1133)

closes #1120
This commit is contained in:
Boshen 2023-11-03 11:10:11 +08:00 committed by GitHub
parent c7fcb31812
commit 203cf37695
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 62 additions and 20 deletions

View file

@ -74,10 +74,7 @@ impl TriviasMap {
self.comments.insert(span.start, comment); self.comments.insert(span.start, comment);
} }
pub fn comments_spans(&self) -> Vec<(Comment, Span)> { pub fn comments_spans(&self) -> impl Iterator<Item = (Comment, Span)> + '_ {
self.comments() self.comments().iter().map(|(start, comment)| (*comment, Span::new(*start, comment.end)))
.iter()
.map(|(start, comment)| (*comment, Span::new(*start, comment.end)))
.collect()
} }
} }

View file

@ -35,7 +35,7 @@ impl<'a> JSDocBuilder<'a> {
comment_text.is_some() comment_text.is_some()
} }
/// Find the jsdoc doc in frontend this span, a.k.a leading comment /// Find the jsdoc doc in front of this span, a.k.a leading comment
fn find_jsdoc_comment(&self, span: Span) -> Option<&'a str> { fn find_jsdoc_comment(&self, span: Span) -> Option<&'a str> {
let (start, comment) = self.trivias.comments().range(..span.start).next()?; let (start, comment) = self.trivias.comments().range(..span.start).next()?;

View file

@ -35,7 +35,10 @@ fn main() {
println!("Original:\n"); println!("Original:\n");
println!("{printed}\n"); println!("{printed}\n");
let semantic = SemanticBuilder::new(&source_text, source_type).build(&ret.program).semantic; let semantic = SemanticBuilder::new(&source_text, source_type)
.with_trivias(ret.trivias)
.build(&ret.program)
.semantic;
let program = allocator.alloc(ret.program); let program = allocator.alloc(ret.program);
let transform_options = TransformOptions { let transform_options = TransformOptions {

View file

@ -18,6 +18,10 @@ impl<'a> TransformerCtx<'a> {
Self { ast, semantic } Self { ast, semantic }
} }
pub fn semantic(&self) -> Ref<'_, Semantic<'a>> {
self.semantic.borrow()
}
pub fn symbols(&self) -> Ref<'_, SymbolTable> { pub fn symbols(&self) -> Ref<'_, SymbolTable> {
Ref::map(self.semantic.borrow(), |semantic| semantic.symbols()) Ref::map(self.semantic.borrow(), |semantic| semantic.symbols())
} }

View file

@ -77,7 +77,7 @@ impl<'a> Transformer<'a> {
Self { Self {
// TODO: pass verbatim_module_syntax from user config // TODO: pass verbatim_module_syntax from user config
typescript: source_type.is_typescript().then(|| TypeScript::new(Rc::clone(&ast), ctx.clone(), false)), typescript: source_type.is_typescript().then(|| TypeScript::new(Rc::clone(&ast), ctx.clone(), false)),
react_jsx: options.react_jsx.map(|options| ReactJsx::new(Rc::clone(&ast), options)), react_jsx: options.react_jsx.map(|options| ReactJsx::new(Rc::clone(&ast), &ctx, options)),
regexp_flags: RegexpFlags::new(Rc::clone(&ast), &options), regexp_flags: RegexpFlags::new(Rc::clone(&ast), &options),
es2022_class_static_block: es2022::ClassStaticBlock::new(Rc::clone(&ast), &options), es2022_class_static_block: es2022::ClassStaticBlock::new(Rc::clone(&ast), &options),
es2021_logical_assignment_operators: LogicalAssignmentOperators::new(Rc::clone(&ast), ctx.clone(), &options), es2021_logical_assignment_operators: LogicalAssignmentOperators::new(Rc::clone(&ast), ctx.clone(), &options),

View file

@ -11,6 +11,7 @@ use oxc_syntax::{
}; };
pub use self::options::{ReactJsxOptions, ReactJsxRuntime}; pub use self::options::{ReactJsxOptions, ReactJsxRuntime};
use crate::context::TransformerCtx;
/// Transform React JSX /// Transform React JSX
/// ///
@ -67,8 +68,13 @@ impl<'a, 'b> JSXElementOrFragment<'a, 'b> {
} }
impl<'a> ReactJsx<'a> { impl<'a> ReactJsx<'a> {
pub fn new(ast: Rc<AstBuilder<'a>>, options: ReactJsxOptions) -> Self { pub fn new(
ast: Rc<AstBuilder<'a>>,
ctx: &TransformerCtx<'a>,
options: ReactJsxOptions,
) -> Self {
let imports = ast.new_vec(); let imports = ast.new_vec();
let options = options.with_comments(&ctx.semantic());
Self { Self {
ast, ast,
options, options,

View file

@ -1,5 +1,7 @@
use serde::Deserialize; use serde::Deserialize;
use oxc_semantic::Semantic;
#[derive(Debug, Default, Clone, Copy, Deserialize)] #[derive(Debug, Default, Clone, Copy, Deserialize)]
pub struct ReactJsxOptions { pub struct ReactJsxOptions {
/// Decides which runtime to use. /// Decides which runtime to use.
@ -28,3 +30,30 @@ impl ReactJsxRuntime {
matches!(self, Self::Automatic) matches!(self, Self::Automatic)
} }
} }
impl ReactJsxOptions {
/// Scan through all comments and find the following pragmas
///
/// * @jsxRuntime classic / automatic
///
/// The comment does not need to be a jsdoc,
/// otherwise `JSDoc` could be used instead.
///
/// This behavior is aligned with babel.
pub(crate) fn with_comments(mut self, semantic: &Semantic) -> Self {
for (_, span) in semantic.trivias().comments_spans() {
let comment = span.source_text(semantic.source_text());
// strip leading jsdoc comment `*` and then whitespaces
let comment = comment.strip_prefix('*').unwrap_or(comment).trim_start();
// strip leading `@`
let Some(comment) = comment.strip_prefix('@') else { continue };
// read jsxRuntime
match comment.strip_prefix("jsxRuntime").map(str::trim) {
Some("classic") => self.runtime = ReactJsxRuntime::Classic,
Some("automatic") => self.runtime = ReactJsxRuntime::Automatic,
_ => {}
}
}
self
}
}

View file

@ -1,4 +1,4 @@
Passed: 241/1080 Passed: 242/1080
# All Passed: # All Passed:
* babel-plugin-transform-numeric-separator * babel-plugin-transform-numeric-separator
@ -803,7 +803,7 @@ Passed: 241/1080
* regression/11061/input.mjs * regression/11061/input.mjs
* variable-declaration/non-null-in-optional-chain/input.ts * variable-declaration/non-null-in-optional-chain/input.ts
# babel-plugin-transform-react-jsx (92/170) # babel-plugin-transform-react-jsx (93/170)
* autoImport/after-polyfills-compiled-to-cjs/input.mjs * autoImport/after-polyfills-compiled-to-cjs/input.mjs
* autoImport/after-polyfills-script-not-supported/input.js * autoImport/after-polyfills-script-not-supported/input.js
* autoImport/auto-import-react-source-type-module/input.js * autoImport/auto-import-react-source-type-module/input.js
@ -878,7 +878,6 @@ Passed: 241/1080
* removed-options/invalid-use-spread-true/input.js * removed-options/invalid-use-spread-true/input.js
* runtime/defaults-to-automatic/input.js * runtime/defaults-to-automatic/input.js
* runtime/invalid-runtime/input.js * runtime/invalid-runtime/input.js
* runtime/pragma-runtime-classsic/input.js
* runtime/runtime-automatic/input.js * runtime/runtime-automatic/input.js
* spread-transform/transform-to-babel-extend/input.js * spread-transform/transform-to-babel-extend/input.js
* spread-transform/transform-to-object-assign/input.js * spread-transform/transform-to-object-assign/input.js

View file

@ -137,12 +137,13 @@ pub trait TestCase {
let allocator = Allocator::default(); let allocator = Allocator::default();
let source_text = fs::read_to_string(path).unwrap(); let source_text = fs::read_to_string(path).unwrap();
let source_type = SourceType::from_path(path).unwrap(); let source_type = SourceType::from_path(path).unwrap();
let transformed_program = let ret = Parser::new(&allocator, &source_text, source_type).parse();
Parser::new(&allocator, &source_text, source_type).parse().program;
let semantic = let semantic = SemanticBuilder::new(&source_text, source_type)
SemanticBuilder::new(&source_text, source_type).build(&transformed_program).semantic; .with_trivias(ret.trivias)
let transformed_program = allocator.alloc(transformed_program); .build(&ret.program)
.semantic;
let transformed_program = allocator.alloc(ret.program);
Transformer::new(&allocator, source_type, semantic, self.transform_options()) Transformer::new(&allocator, source_type, semantic, self.transform_options())
.build(transformed_program); .build(transformed_program);
@ -190,9 +191,12 @@ impl TestCase for ConformanceTestCase {
} }
// Transform input.js // Transform input.js
let program = Parser::new(&allocator, &input, source_type).parse().program; let ret = Parser::new(&allocator, &input, source_type).parse();
let semantic = SemanticBuilder::new(&input, source_type).build(&program).semantic; let semantic = SemanticBuilder::new(&input, source_type)
let program = allocator.alloc(program); .with_trivias(ret.trivias)
.build(&ret.program)
.semantic;
let program = allocator.alloc(ret.program);
Transformer::new(&allocator, source_type, semantic, self.transform_options()) Transformer::new(&allocator, source_type, semantic, self.transform_options())
.build(program); .build(program);
let transformed_code = Codegen::<false>::new(input.len(), CodegenOptions).build(program); let transformed_code = Codegen::<false>::new(input.len(), CodegenOptions).build(program);