mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 20:32:10 +00:00
feat(transform): support es2015 new target (#1967)
Here implementing the es2015 new target transform, see detail at https://babel.dev/docs/babel-plugin-transform-template-new-target. Here has three kinds need to be distinguished. - `NewTargetKind::Method`, it from `AstKind::ObjectMethod` or `AstKind::MethodDefinitionKind::Get/Set/Method`. It will be transformed to `void 0`. - `NewTargetKind::Constructor`, is from ` AstKind::MethodDefinitionKind::Constructor`. It will be transformed to `this.constructor`. - `NewTargetKind::Function`, is from ` AstKind::Function`, here the function is not the above function. It will be transformed to `this instanceof _target ? this.constructor : void 0`, here `_target` comes from the function name or is created by scope uid ident.
This commit is contained in:
parent
ac704cce14
commit
78b427bc50
9 changed files with 175 additions and 6 deletions
|
|
@ -183,8 +183,13 @@ impl ScopeTree {
|
||||||
expr.gather(&mut |part| parts.push(part));
|
expr.gather(&mut |part| parts.push(part));
|
||||||
let name = parts.join("$");
|
let name = parts.join("$");
|
||||||
let name = name.trim_start_matches('_');
|
let name = name.trim_start_matches('_');
|
||||||
|
self.generate_uid(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// <https://github.com/babel/babel/blob/419644f27c5c59deb19e71aaabd417a3bc5483ca/packages/babel-traverse/src/scope/index.ts#L495>
|
||||||
|
pub fn generate_uid(&self, name: &str) -> Atom {
|
||||||
for i in 0.. {
|
for i in 0.. {
|
||||||
let name = Self::generate_uid(name, i);
|
let name = Self::internal_generate_uid(name, i);
|
||||||
if !self.has_binding(ScopeId::new(0), &name) {
|
if !self.has_binding(ScopeId::new(0), &name) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
@ -192,7 +197,8 @@ impl ScopeTree {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_uid(name: &str, i: i32) -> Atom {
|
// <https://github.com/babel/babel/blob/419644f27c5c59deb19e71aaabd417a3bc5483ca/packages/babel-traverse/src/scope/index.ts#L523>
|
||||||
|
fn internal_generate_uid(name: &str, i: i32) -> Atom {
|
||||||
Atom::from(if i > 1 { format!("_{name}{i}") } else { format!("_{name}") })
|
Atom::from(if i > 1 { format!("_{name}{i}") } else { format!("_{name}") })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ mod arrow_functions;
|
||||||
mod duplicate_keys;
|
mod duplicate_keys;
|
||||||
mod function_name;
|
mod function_name;
|
||||||
mod instanceof;
|
mod instanceof;
|
||||||
|
mod new_target;
|
||||||
mod shorthand_properties;
|
mod shorthand_properties;
|
||||||
mod template_literals;
|
mod template_literals;
|
||||||
|
|
||||||
|
|
@ -9,5 +10,6 @@ pub use arrow_functions::{ArrowFunctions, ArrowFunctionsOptions};
|
||||||
pub use duplicate_keys::DuplicateKeys;
|
pub use duplicate_keys::DuplicateKeys;
|
||||||
pub use function_name::FunctionName;
|
pub use function_name::FunctionName;
|
||||||
pub use instanceof::Instanceof;
|
pub use instanceof::Instanceof;
|
||||||
|
pub use new_target::NewTarget;
|
||||||
pub use shorthand_properties::ShorthandProperties;
|
pub use shorthand_properties::ShorthandProperties;
|
||||||
pub use template_literals::TemplateLiterals;
|
pub use template_literals::TemplateLiterals;
|
||||||
|
|
|
||||||
133
crates/oxc_transformer/src/es2015/new_target.rs
Normal file
133
crates/oxc_transformer/src/es2015/new_target.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
use crate::{context::TransformerCtx, TransformOptions, TransformTarget};
|
||||||
|
use oxc_allocator::Vec;
|
||||||
|
use oxc_ast::{ast::*, AstBuilder, AstKind, VisitMut};
|
||||||
|
use oxc_diagnostics::miette;
|
||||||
|
use oxc_span::{Atom, Span, SPAN};
|
||||||
|
use oxc_syntax::operator::BinaryOperator;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// ES2015: New Target
|
||||||
|
///
|
||||||
|
/// References:
|
||||||
|
/// * <https://babel.dev/docs/babel-plugin-transform-template-new-target>
|
||||||
|
/// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-new-target>
|
||||||
|
pub struct NewTarget<'a> {
|
||||||
|
ast: Rc<AstBuilder<'a>>,
|
||||||
|
ctx: TransformerCtx<'a>,
|
||||||
|
kinds: Vec<'a, NewTargetKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum NewTargetKind {
|
||||||
|
Method,
|
||||||
|
Constructor,
|
||||||
|
Function(Option<Atom>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> VisitMut<'a> for NewTarget<'a> {
|
||||||
|
fn enter_node(&mut self, kind: AstKind<'a>) {
|
||||||
|
if let Some(kind) = self.get_kind(kind) {
|
||||||
|
self.kinds.push(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leave_node(&mut self, kind: AstKind<'a>) {
|
||||||
|
if self.get_kind(kind).is_some() {
|
||||||
|
self.kinds.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NewTarget<'a> {
|
||||||
|
pub fn new(
|
||||||
|
ast: Rc<AstBuilder<'a>>,
|
||||||
|
ctx: TransformerCtx<'a>,
|
||||||
|
options: &TransformOptions,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let kinds = ast.new_vec();
|
||||||
|
(options.target < TransformTarget::ES2015 || options.new_target).then(|| Self {
|
||||||
|
ast,
|
||||||
|
ctx,
|
||||||
|
kinds,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_kind(&self, kind: AstKind<'a>) -> Option<NewTargetKind> {
|
||||||
|
match kind {
|
||||||
|
AstKind::MethodDefinition(def) => match def.kind {
|
||||||
|
MethodDefinitionKind::Get
|
||||||
|
| MethodDefinitionKind::Set
|
||||||
|
| MethodDefinitionKind::Method => Some(NewTargetKind::Method),
|
||||||
|
MethodDefinitionKind::Constructor => Some(NewTargetKind::Constructor),
|
||||||
|
},
|
||||||
|
AstKind::ObjectProperty(property) => property.method.then_some(NewTargetKind::Method),
|
||||||
|
AstKind::Function(function) => {
|
||||||
|
// oxc visitor `MethodDefinitionKind` will enter `Function` node, here need to exclude it
|
||||||
|
if let Some(kind) = self.kinds.last() {
|
||||||
|
if !matches!(kind, NewTargetKind::Function(_)) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function.id.as_ref().map(|id| NewTargetKind::Function(Some(id.name.clone())))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_constructor_expr(&self, span: Span) -> Expression<'a> {
|
||||||
|
self.ast.static_member_expression(
|
||||||
|
span,
|
||||||
|
self.ast.this_expression(span),
|
||||||
|
IdentifierName { span, name: "constructor".into() },
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_expression<'b>(&mut self, expr: &'b mut Expression<'a>) {
|
||||||
|
if let Expression::MetaProperty(meta) = expr {
|
||||||
|
if meta.meta.name == "new" && meta.property.name == "target" {
|
||||||
|
if let Some(kind) = self.kinds.last() {
|
||||||
|
match kind {
|
||||||
|
NewTargetKind::Constructor => {
|
||||||
|
*expr = self.create_constructor_expr(meta.span);
|
||||||
|
}
|
||||||
|
NewTargetKind::Method => {
|
||||||
|
*expr = self.ast.void_0();
|
||||||
|
}
|
||||||
|
NewTargetKind::Function(name) => {
|
||||||
|
// TODO packages/babel-helper-create-class-features-plugin/src/fields.ts#L192 unshadow
|
||||||
|
// It will mutate previous ast node, it is difficult at now.
|
||||||
|
let id = name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| self.ctx.scopes().generate_uid("target"));
|
||||||
|
let test = self.ast.binary_expression(
|
||||||
|
SPAN,
|
||||||
|
self.ast.this_expression(SPAN),
|
||||||
|
BinaryOperator::Instanceof,
|
||||||
|
self.ast.identifier_reference_expression(IdentifierReference::new(
|
||||||
|
SPAN, id,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
let consequent = self.ast.static_member_expression(
|
||||||
|
SPAN,
|
||||||
|
self.ast.this_expression(SPAN),
|
||||||
|
IdentifierName { span: SPAN, name: "constructor".into() },
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
*expr = self.ast.conditional_expression(
|
||||||
|
meta.span,
|
||||||
|
test,
|
||||||
|
consequent,
|
||||||
|
self.ast.void_0(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.ctx.error(miette::Error::msg(
|
||||||
|
"new.target must be under a (non-arrow) function or a class.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,7 +27,7 @@ use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
use es2015::TemplateLiterals;
|
use es2015::TemplateLiterals;
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
use oxc_ast::{ast::*, AstBuilder, VisitMut};
|
use oxc_ast::{ast::*, AstBuilder, AstKind, VisitMut};
|
||||||
use oxc_diagnostics::Error;
|
use oxc_diagnostics::Error;
|
||||||
use oxc_semantic::Semantic;
|
use oxc_semantic::Semantic;
|
||||||
use oxc_span::SourceType;
|
use oxc_span::SourceType;
|
||||||
|
|
@ -69,6 +69,7 @@ pub struct Transformer<'a> {
|
||||||
es2015_template_literals: Option<TemplateLiterals<'a>>,
|
es2015_template_literals: Option<TemplateLiterals<'a>>,
|
||||||
es2015_duplicate_keys: Option<DuplicateKeys<'a>>,
|
es2015_duplicate_keys: Option<DuplicateKeys<'a>>,
|
||||||
es2015_instanceof: Option<Instanceof<'a>>,
|
es2015_instanceof: Option<Instanceof<'a>>,
|
||||||
|
es2015_new_target: Option<NewTarget<'a>>,
|
||||||
es3_property_literal: Option<PropertyLiteral<'a>>,
|
es3_property_literal: Option<PropertyLiteral<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,6 +109,7 @@ impl<'a> Transformer<'a> {
|
||||||
es2015_template_literals: TemplateLiterals::new(Rc::clone(&ast), &options),
|
es2015_template_literals: TemplateLiterals::new(Rc::clone(&ast), &options),
|
||||||
es2015_duplicate_keys: DuplicateKeys::new(Rc::clone(&ast), &options),
|
es2015_duplicate_keys: DuplicateKeys::new(Rc::clone(&ast), &options),
|
||||||
es2015_instanceof: Instanceof::new(Rc::clone(&ast), ctx.clone(), &options),
|
es2015_instanceof: Instanceof::new(Rc::clone(&ast), ctx.clone(), &options),
|
||||||
|
es2015_new_target: NewTarget::new(Rc::clone(&ast),ctx.clone(), &options),
|
||||||
// other
|
// other
|
||||||
es3_property_literal: PropertyLiteral::new(Rc::clone(&ast), &options),
|
es3_property_literal: PropertyLiteral::new(Rc::clone(&ast), &options),
|
||||||
react_jsx: ReactJsx::new(Rc::clone(&ast), ctx.clone(), options)
|
react_jsx: ReactJsx::new(Rc::clone(&ast), ctx.clone(), options)
|
||||||
|
|
@ -134,6 +136,14 @@ impl<'a> Transformer<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> VisitMut<'a> for Transformer<'a> {
|
impl<'a> VisitMut<'a> for Transformer<'a> {
|
||||||
|
fn enter_node(&mut self, kind: oxc_ast::AstKind<'a>) {
|
||||||
|
self.es2015_new_target.as_mut().map(|t| t.enter_node(kind));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leave_node(&mut self, kind: oxc_ast::AstKind<'a>) {
|
||||||
|
self.es2015_new_target.as_mut().map(|t| t.leave_node(kind));
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_program(&mut self, program: &mut Program<'a>) {
|
fn visit_program(&mut self, program: &mut Program<'a>) {
|
||||||
for directive in program.directives.iter_mut() {
|
for directive in program.directives.iter_mut() {
|
||||||
self.visit_directive(directive);
|
self.visit_directive(directive);
|
||||||
|
|
@ -184,6 +194,7 @@ impl<'a> VisitMut<'a> for Transformer<'a> {
|
||||||
self.es2015_instanceof.as_mut().map(|t| t.transform_expression(expr));
|
self.es2015_instanceof.as_mut().map(|t| t.transform_expression(expr));
|
||||||
self.es2016_exponentiation_operator.as_mut().map(|t| t.transform_expression(expr));
|
self.es2016_exponentiation_operator.as_mut().map(|t| t.transform_expression(expr));
|
||||||
self.es2015_template_literals.as_mut().map(|t| t.transform_expression(expr));
|
self.es2015_template_literals.as_mut().map(|t| t.transform_expression(expr));
|
||||||
|
self.es2015_new_target.as_mut().map(|t| t.transform_expression(expr));
|
||||||
|
|
||||||
self.visit_expression_match(expr);
|
self.visit_expression_match(expr);
|
||||||
}
|
}
|
||||||
|
|
@ -210,12 +221,14 @@ impl<'a> VisitMut<'a> for Transformer<'a> {
|
||||||
self.es2015_shorthand_properties.as_mut().map(|t| t.transform_object_property(prop));
|
self.es2015_shorthand_properties.as_mut().map(|t| t.transform_object_property(prop));
|
||||||
self.es3_property_literal.as_mut().map(|t| t.transform_object_property(prop));
|
self.es3_property_literal.as_mut().map(|t| t.transform_object_property(prop));
|
||||||
|
|
||||||
|
let kind = AstKind::ObjectProperty(self.alloc(prop));
|
||||||
|
self.enter_node(kind);
|
||||||
self.visit_property_key(&mut prop.key);
|
self.visit_property_key(&mut prop.key);
|
||||||
self.visit_expression(&mut prop.value);
|
self.visit_expression(&mut prop.value);
|
||||||
|
|
||||||
if let Some(init) = &mut prop.init {
|
if let Some(init) = &mut prop.init {
|
||||||
self.visit_expression(init);
|
self.visit_expression(init);
|
||||||
}
|
}
|
||||||
|
self.leave_node(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_class_body(&mut self, class_body: &mut ClassBody<'a>) {
|
fn visit_class_body(&mut self, class_body: &mut ClassBody<'a>) {
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ pub struct TransformOptions {
|
||||||
pub property_literals: bool,
|
pub property_literals: bool,
|
||||||
pub babel_8_breaking: Option<bool>,
|
pub babel_8_breaking: Option<bool>,
|
||||||
pub instanceof: bool,
|
pub instanceof: bool,
|
||||||
|
pub new_target: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See <https://www.typescriptlang.org/tsconfig#target>
|
/// See <https://www.typescriptlang.org/tsconfig#target>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Passed: 295/1171
|
Passed: 297/1179
|
||||||
|
|
||||||
# All Passed:
|
# All Passed:
|
||||||
* babel-plugin-transform-numeric-separator
|
* babel-plugin-transform-numeric-separator
|
||||||
|
|
@ -824,6 +824,14 @@ Passed: 295/1171
|
||||||
# babel-plugin-transform-duplicate-keys (7/8)
|
# babel-plugin-transform-duplicate-keys (7/8)
|
||||||
* combination/dupes/input.js
|
* combination/dupes/input.js
|
||||||
|
|
||||||
|
# babel-plugin-transform-new-target (2/8)
|
||||||
|
* general/arrow/input.js
|
||||||
|
* general/class-properties/input.js
|
||||||
|
* general/class-properties-loose/input.js
|
||||||
|
* general/function/input.js
|
||||||
|
* general/function-duplicate-name/input.js
|
||||||
|
* general/object/input.js
|
||||||
|
|
||||||
# babel-plugin-transform-typescript (66/158)
|
# babel-plugin-transform-typescript (66/158)
|
||||||
* class/abstract-class-decorated/input.ts
|
* class/abstract-class-decorated/input.ts
|
||||||
* class/abstract-class-decorated-method/input.ts
|
* class/abstract-class-decorated-method/input.ts
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Passed: 379/445
|
Passed: 386/454
|
||||||
|
|
||||||
# All Passed:
|
# All Passed:
|
||||||
* babel-plugin-transform-class-static-block
|
* babel-plugin-transform-class-static-block
|
||||||
|
|
@ -98,3 +98,7 @@ Passed: 379/445
|
||||||
# babel-plugin-transform-instanceof (0/1)
|
# babel-plugin-transform-instanceof (0/1)
|
||||||
* instanceof/instanceof/exec.js
|
* instanceof/instanceof/exec.js
|
||||||
|
|
||||||
|
# babel-plugin-transform-new-target (7/9)
|
||||||
|
* general/class-properties/exec.js
|
||||||
|
* general/class-properties-loose/exec.js
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ const CASES: &[&str] = &[
|
||||||
"babel-plugin-transform-template-literals",
|
"babel-plugin-transform-template-literals",
|
||||||
"babel-plugin-transform-duplicate-keys",
|
"babel-plugin-transform-duplicate-keys",
|
||||||
"babel-plugin-transform-instanceof",
|
"babel-plugin-transform-instanceof",
|
||||||
|
"babel-plugin-transform-new-target",
|
||||||
// ES3
|
// ES3
|
||||||
"babel-plugin-transform-property-literals",
|
"babel-plugin-transform-property-literals",
|
||||||
// TypeScript
|
// TypeScript
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ pub trait TestCase {
|
||||||
template_literals: options.get_plugin("transform-template-literals").is_some(),
|
template_literals: options.get_plugin("transform-template-literals").is_some(),
|
||||||
property_literals: options.get_plugin("transform-property-literals").is_some(),
|
property_literals: options.get_plugin("transform-property-literals").is_some(),
|
||||||
duplicate_keys: options.get_plugin("transform-duplicate-keys").is_some(),
|
duplicate_keys: options.get_plugin("transform-duplicate-keys").is_some(),
|
||||||
|
new_target: options.get_plugin("transform-new-target").is_some(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue