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

View file

@ -35,7 +35,7 @@ impl<'a> JSDocBuilder<'a> {
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> {
let (start, comment) = self.trivias.comments().range(..span.start).next()?;

View file

@ -35,7 +35,10 @@ fn main() {
println!("Original:\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 transform_options = TransformOptions {

View file

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

View file

@ -77,7 +77,7 @@ impl<'a> Transformer<'a> {
Self {
// TODO: pass verbatim_module_syntax from user config
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),
es2022_class_static_block: es2022::ClassStaticBlock::new(Rc::clone(&ast), &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};
use crate::context::TransformerCtx;
/// Transform React JSX
///
@ -67,8 +68,13 @@ impl<'a, 'b> JSXElementOrFragment<'a, 'b> {
}
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 options = options.with_comments(&ctx.semantic());
Self {
ast,
options,

View file

@ -1,5 +1,7 @@
use serde::Deserialize;
use oxc_semantic::Semantic;
#[derive(Debug, Default, Clone, Copy, Deserialize)]
pub struct ReactJsxOptions {
/// Decides which runtime to use.
@ -28,3 +30,30 @@ impl ReactJsxRuntime {
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:
* babel-plugin-transform-numeric-separator
@ -803,7 +803,7 @@ Passed: 241/1080
* regression/11061/input.mjs
* 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-script-not-supported/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
* runtime/defaults-to-automatic/input.js
* runtime/invalid-runtime/input.js
* runtime/pragma-runtime-classsic/input.js
* runtime/runtime-automatic/input.js
* spread-transform/transform-to-babel-extend/input.js
* spread-transform/transform-to-object-assign/input.js

View file

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