mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(transformer): do not elide jsx imports if a jsx element appears somewhere (#3237)
This commit is contained in:
parent
64cd8a9d69
commit
1b29e63300
5 changed files with 139 additions and 21 deletions
|
|
@ -158,6 +158,14 @@ impl<'a> Traverse<'a> for Transformer<'a> {
|
|||
self.x0_typescript.transform_function(func);
|
||||
}
|
||||
|
||||
fn enter_jsx_element(&mut self, node: &mut JSXElement<'a>, _ctx: &TraverseCtx<'a>) {
|
||||
self.x0_typescript.transform_jsx_element(node);
|
||||
}
|
||||
|
||||
fn enter_jsx_fragment(&mut self, node: &mut JSXFragment<'a>, _ctx: &TraverseCtx<'a>) {
|
||||
self.x0_typescript.transform_jsx_fragment(node);
|
||||
}
|
||||
|
||||
fn enter_jsx_opening_element(
|
||||
&mut self,
|
||||
elem: &mut JSXOpeningElement<'a>,
|
||||
|
|
|
|||
|
|
@ -20,18 +20,46 @@ pub struct TypeScriptAnnotations<'a> {
|
|||
/// Assignments to be added to the constructor body
|
||||
assignments: Vec<'a, Statement<'a>>,
|
||||
has_super_call: bool,
|
||||
|
||||
has_jsx_element: bool,
|
||||
has_jsx_fragment: bool,
|
||||
jsx_element_import_name: String,
|
||||
jsx_fragment_import_name: String,
|
||||
}
|
||||
|
||||
impl<'a> TypeScriptAnnotations<'a> {
|
||||
pub fn new(options: &Rc<TypeScriptOptions>, ctx: &Ctx<'a>) -> Self {
|
||||
let jsx_element_import_name = if options.jsx_pragma.contains('.') {
|
||||
options.jsx_pragma.split('.').next().map(String::from).unwrap()
|
||||
} else {
|
||||
options.jsx_pragma.to_string()
|
||||
};
|
||||
|
||||
let jsx_fragment_import_name = if options.jsx_pragma_frag.contains('.') {
|
||||
options.jsx_pragma_frag.split('.').next().map(String::from).unwrap()
|
||||
} else {
|
||||
options.jsx_pragma_frag.to_string()
|
||||
};
|
||||
|
||||
Self {
|
||||
has_super_call: false,
|
||||
assignments: ctx.ast.new_vec(),
|
||||
options: Rc::clone(options),
|
||||
ctx: Rc::clone(ctx),
|
||||
has_jsx_element: false,
|
||||
has_jsx_fragment: false,
|
||||
jsx_element_import_name,
|
||||
jsx_fragment_import_name,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the given name is a JSX pragma or fragment pragma import
|
||||
/// and if the file contains JSX elements or fragments
|
||||
fn is_jsx_imports(&self, name: &str) -> bool {
|
||||
self.has_jsx_element && name == self.jsx_element_import_name
|
||||
|| self.has_jsx_fragment && name == self.jsx_fragment_import_name
|
||||
}
|
||||
|
||||
// Creates `this.name = name`
|
||||
fn create_this_property_assignment(&self, name: &Atom<'a>) -> Statement<'a> {
|
||||
let ast = &self.ctx.ast;
|
||||
|
|
@ -108,6 +136,7 @@ impl<'a> TypeScriptAnnotations<'a> {
|
|||
}
|
||||
|
||||
references.has_reference(&s.local.name)
|
||||
|| self.is_jsx_imports(&s.local.name)
|
||||
}
|
||||
ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => {
|
||||
if is_type {
|
||||
|
|
@ -119,6 +148,7 @@ impl<'a> TypeScriptAnnotations<'a> {
|
|||
return true;
|
||||
}
|
||||
references.has_reference(&s.local.name)
|
||||
|| self.is_jsx_imports(&s.local.name)
|
||||
}
|
||||
ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => {
|
||||
if is_type {
|
||||
|
|
@ -130,6 +160,7 @@ impl<'a> TypeScriptAnnotations<'a> {
|
|||
}
|
||||
|
||||
references.has_reference(&s.local.name)
|
||||
|| self.is_jsx_imports(&s.local.name)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -357,4 +388,12 @@ impl<'a> TypeScriptAnnotations<'a> {
|
|||
) {
|
||||
expr.type_parameters = None;
|
||||
}
|
||||
|
||||
pub fn transform_jsx_element(&mut self, _elem: &mut JSXElement<'a>) {
|
||||
self.has_jsx_element = true;
|
||||
}
|
||||
|
||||
pub fn transform_jsx_fragment(&mut self, _elem: &mut JSXFragment<'a>) {
|
||||
self.has_jsx_fragment = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ mod diagnostics;
|
|||
mod r#enum;
|
||||
mod module;
|
||||
mod namespace;
|
||||
mod options;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use oxc_allocator::Vec;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_traverse::TraverseCtx;
|
||||
|
|
@ -20,13 +19,7 @@ use self::{
|
|||
r#enum::TypeScriptEnum,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct TypeScriptOptions {
|
||||
/// When set to true, the transform will only remove type-only imports (introduced in TypeScript 3.8).
|
||||
/// This should only be used if you are using TypeScript >= 3.8.
|
||||
only_remove_type_imports: bool,
|
||||
}
|
||||
pub use self::options::TypeScriptOptions;
|
||||
|
||||
/// [Preset TypeScript](https://babeljs.io/docs/babel-preset-typescript)
|
||||
///
|
||||
|
|
@ -61,7 +54,7 @@ pub struct TypeScript<'a> {
|
|||
|
||||
impl<'a> TypeScript<'a> {
|
||||
pub fn new(options: TypeScriptOptions, ctx: &Ctx<'a>) -> Self {
|
||||
let options = Rc::new(options);
|
||||
let options = Rc::new(options.update_with_comments(ctx));
|
||||
|
||||
Self {
|
||||
annotations: TypeScriptAnnotations::new(&options, ctx),
|
||||
|
|
@ -210,4 +203,12 @@ impl<'a> TypeScript<'a> {
|
|||
self.transform_ts_export_assignment(ts_export_assignment);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform_jsx_element(&mut self, elem: &mut JSXElement<'a>) {
|
||||
self.annotations.transform_jsx_element(elem);
|
||||
}
|
||||
|
||||
pub fn transform_jsx_fragment(&mut self, elem: &mut JSXFragment<'a>) {
|
||||
self.annotations.transform_jsx_fragment(elem);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
79
crates/oxc_transformer/src/typescript/options.rs
Normal file
79
crates/oxc_transformer/src/typescript/options.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::context::Ctx;
|
||||
|
||||
fn default_for_jsx_pragma() -> Cow<'static, str> {
|
||||
Cow::Borrowed("React.createElement")
|
||||
}
|
||||
|
||||
fn default_for_jsx_pragma_frag() -> Cow<'static, str> {
|
||||
Cow::Borrowed("React.Fragment")
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct TypeScriptOptions {
|
||||
/// Replace the function used when compiling JSX expressions.
|
||||
/// This is so that we know that the import is not a type import, and should not be removed.
|
||||
/// defaults to React
|
||||
#[serde(default = "default_for_jsx_pragma")]
|
||||
pub jsx_pragma: Cow<'static, str>,
|
||||
|
||||
/// Replace the function used when compiling JSX fragment expressions.
|
||||
/// This is so that we know that the import is not a type import, and should not be removed.
|
||||
/// defaults to React.Fragment
|
||||
#[serde(default = "default_for_jsx_pragma_frag")]
|
||||
pub jsx_pragma_frag: Cow<'static, str>,
|
||||
/// When set to true, the transform will only remove type-only imports (introduced in TypeScript 3.8).
|
||||
/// This should only be used if you are using TypeScript >= 3.8.
|
||||
pub only_remove_type_imports: bool,
|
||||
}
|
||||
|
||||
impl TypeScriptOptions {
|
||||
/// Scan through all comments and find the following pragmas
|
||||
///
|
||||
/// * @jsx React.createElement
|
||||
/// * @jsxFrag React.Fragment
|
||||
///
|
||||
/// The comment does not need to be a jsdoc,
|
||||
/// otherwise `JSDoc` could be used instead.
|
||||
///
|
||||
/// This behavior is aligned with babel.
|
||||
pub(crate) fn update_with_comments(mut self, ctx: &Ctx) -> Self {
|
||||
for (_, span) in ctx.trivias.comments() {
|
||||
let mut comment = span.source_text(ctx.source_text).trim_start();
|
||||
// strip leading jsdoc comment `*` and then whitespaces
|
||||
while let Some(cur_comment) = comment.strip_prefix('*') {
|
||||
comment = cur_comment.trim_start();
|
||||
}
|
||||
// strip leading `@`
|
||||
let Some(comment) = comment.strip_prefix('@') else { continue };
|
||||
|
||||
// read jsxFrag
|
||||
if let Some(pragma_frag) = comment.strip_prefix("jsxFrag").map(str::trim) {
|
||||
self.jsx_pragma_frag = Cow::from(pragma_frag.to_string());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Put this condition at the end to avoid breaking @jsxXX
|
||||
// read jsx
|
||||
if let Some(pragma) = comment.strip_prefix("jsx").map(str::trim) {
|
||||
self.jsx_pragma = Cow::from(pragma.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TypeScriptOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
jsx_pragma: default_for_jsx_pragma(),
|
||||
jsx_pragma_frag: default_for_jsx_pragma_frag(),
|
||||
only_remove_type_imports: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
Passed: 295/362
|
||||
Passed: 304/362
|
||||
|
||||
# All Passed:
|
||||
* babel-preset-react
|
||||
|
|
@ -24,7 +24,7 @@ Passed: 295/362
|
|||
* opts/optimizeConstEnums/input.ts
|
||||
* opts/rewriteImportExtensions/input.ts
|
||||
|
||||
# babel-plugin-transform-typescript (110/156)
|
||||
# babel-plugin-transform-typescript (119/156)
|
||||
* class/accessor-allowDeclareFields-false/input.ts
|
||||
* class/accessor-allowDeclareFields-true/input.ts
|
||||
* enum/mix-references/input.ts
|
||||
|
|
@ -32,15 +32,6 @@ Passed: 295/362
|
|||
* enum/ts5.0-const-foldable/input.ts
|
||||
* exports/declared-types/input.ts
|
||||
* exports/export-type-star-from/input.ts
|
||||
* imports/elide-jsx-pragma-namespace-no/input.ts
|
||||
* imports/elide-jsx-pragma-no/input.ts
|
||||
* imports/elide-jsx-pragmaFrag-namespace-no/input.ts
|
||||
* imports/elide-jsx-pragmaFrag-no/input.ts
|
||||
* imports/elide-preact-no-1/input.ts
|
||||
* imports/elide-preact-no-2/input.ts
|
||||
* imports/elide-react-no-1/input.ts
|
||||
* imports/elide-react-no-2/input.ts
|
||||
* imports/elide-react-no-3/input.ts
|
||||
* imports/enum-value/input.ts
|
||||
* imports/type-only-export-specifier-2/input.ts
|
||||
* namespace/ambient-module-nested/input.ts
|
||||
|
|
|
|||
Loading…
Reference in a new issue