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:
underfin 2024-01-10 07:59:56 -08:00 committed by GitHub
parent ac704cce14
commit 78b427bc50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 175 additions and 6 deletions

View file

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

View file

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

View 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.",
));
}
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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(),
} }
} }