From 78b427bc50956f060fa24bc4a0abb3f440e74d11 Mon Sep 17 00:00:00 2001 From: underfin <2218301630@qq.com> Date: Wed, 10 Jan 2024 07:59:56 -0800 Subject: [PATCH] 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. --- crates/oxc_semantic/src/scope.rs | 10 +- crates/oxc_transformer/src/es2015/mod.rs | 2 + .../oxc_transformer/src/es2015/new_target.rs | 133 ++++++++++++++++++ crates/oxc_transformer/src/lib.rs | 17 ++- crates/oxc_transformer/src/options.rs | 1 + tasks/transform_conformance/babel.snap.md | 10 +- .../transform_conformance/babel_exec.snap.md | 6 +- tasks/transform_conformance/src/lib.rs | 1 + tasks/transform_conformance/src/test_case.rs | 1 + 9 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 crates/oxc_transformer/src/es2015/new_target.rs diff --git a/crates/oxc_semantic/src/scope.rs b/crates/oxc_semantic/src/scope.rs index 403043e14..9ce819968 100644 --- a/crates/oxc_semantic/src/scope.rs +++ b/crates/oxc_semantic/src/scope.rs @@ -183,8 +183,13 @@ impl ScopeTree { expr.gather(&mut |part| parts.push(part)); let name = parts.join("$"); let name = name.trim_start_matches('_'); + self.generate_uid(name) + } + + // + pub fn generate_uid(&self, name: &str) -> Atom { 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) { return name; } @@ -192,7 +197,8 @@ impl ScopeTree { unreachable!() } - fn generate_uid(name: &str, i: i32) -> Atom { + // + fn internal_generate_uid(name: &str, i: i32) -> Atom { Atom::from(if i > 1 { format!("_{name}{i}") } else { format!("_{name}") }) } } diff --git a/crates/oxc_transformer/src/es2015/mod.rs b/crates/oxc_transformer/src/es2015/mod.rs index b77842f79..ce30b75b2 100644 --- a/crates/oxc_transformer/src/es2015/mod.rs +++ b/crates/oxc_transformer/src/es2015/mod.rs @@ -2,6 +2,7 @@ mod arrow_functions; mod duplicate_keys; mod function_name; mod instanceof; +mod new_target; mod shorthand_properties; mod template_literals; @@ -9,5 +10,6 @@ pub use arrow_functions::{ArrowFunctions, ArrowFunctionsOptions}; pub use duplicate_keys::DuplicateKeys; pub use function_name::FunctionName; pub use instanceof::Instanceof; +pub use new_target::NewTarget; pub use shorthand_properties::ShorthandProperties; pub use template_literals::TemplateLiterals; diff --git a/crates/oxc_transformer/src/es2015/new_target.rs b/crates/oxc_transformer/src/es2015/new_target.rs new file mode 100644 index 000000000..a9f721f45 --- /dev/null +++ b/crates/oxc_transformer/src/es2015/new_target.rs @@ -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: +/// * +/// * +pub struct NewTarget<'a> { + ast: Rc>, + ctx: TransformerCtx<'a>, + kinds: Vec<'a, NewTargetKind>, +} + +#[derive(Debug)] +enum NewTargetKind { + Method, + Constructor, + Function(Option), +} + +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>, + ctx: TransformerCtx<'a>, + options: &TransformOptions, + ) -> Option { + 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 { + 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.", + )); + } + } + } + } +} diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 51836ea0d..1e42347df 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -27,7 +27,7 @@ use std::{cell::RefCell, rc::Rc, sync::Arc}; use es2015::TemplateLiterals; use oxc_allocator::Allocator; -use oxc_ast::{ast::*, AstBuilder, VisitMut}; +use oxc_ast::{ast::*, AstBuilder, AstKind, VisitMut}; use oxc_diagnostics::Error; use oxc_semantic::Semantic; use oxc_span::SourceType; @@ -69,6 +69,7 @@ pub struct Transformer<'a> { es2015_template_literals: Option>, es2015_duplicate_keys: Option>, es2015_instanceof: Option>, + es2015_new_target: Option>, es3_property_literal: Option>, } @@ -108,6 +109,7 @@ impl<'a> Transformer<'a> { es2015_template_literals: TemplateLiterals::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_new_target: NewTarget::new(Rc::clone(&ast),ctx.clone(), &options), // other es3_property_literal: PropertyLiteral::new(Rc::clone(&ast), &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> { + 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>) { for directive in program.directives.iter_mut() { 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.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_new_target.as_mut().map(|t| t.transform_expression(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.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_expression(&mut prop.value); - if let Some(init) = &mut prop.init { self.visit_expression(init); } + self.leave_node(kind); } fn visit_class_body(&mut self, class_body: &mut ClassBody<'a>) { diff --git a/crates/oxc_transformer/src/options.rs b/crates/oxc_transformer/src/options.rs index 19caf8709..29927aeb7 100644 --- a/crates/oxc_transformer/src/options.rs +++ b/crates/oxc_transformer/src/options.rs @@ -32,6 +32,7 @@ pub struct TransformOptions { pub property_literals: bool, pub babel_8_breaking: Option, pub instanceof: bool, + pub new_target: bool, } /// See diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 2394472f9..07d429d4b 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 295/1171 +Passed: 297/1179 # All Passed: * babel-plugin-transform-numeric-separator @@ -824,6 +824,14 @@ Passed: 295/1171 # babel-plugin-transform-duplicate-keys (7/8) * 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) * class/abstract-class-decorated/input.ts * class/abstract-class-decorated-method/input.ts diff --git a/tasks/transform_conformance/babel_exec.snap.md b/tasks/transform_conformance/babel_exec.snap.md index d406c3cae..a2bbd458b 100644 --- a/tasks/transform_conformance/babel_exec.snap.md +++ b/tasks/transform_conformance/babel_exec.snap.md @@ -1,4 +1,4 @@ -Passed: 379/445 +Passed: 386/454 # All Passed: * babel-plugin-transform-class-static-block @@ -98,3 +98,7 @@ Passed: 379/445 # babel-plugin-transform-instanceof (0/1) * instanceof/instanceof/exec.js +# babel-plugin-transform-new-target (7/9) +* general/class-properties/exec.js +* general/class-properties-loose/exec.js + diff --git a/tasks/transform_conformance/src/lib.rs b/tasks/transform_conformance/src/lib.rs index a50df966b..5908a37b9 100644 --- a/tasks/transform_conformance/src/lib.rs +++ b/tasks/transform_conformance/src/lib.rs @@ -82,6 +82,7 @@ const CASES: &[&str] = &[ "babel-plugin-transform-template-literals", "babel-plugin-transform-duplicate-keys", "babel-plugin-transform-instanceof", + "babel-plugin-transform-new-target", // ES3 "babel-plugin-transform-property-literals", // TypeScript diff --git a/tasks/transform_conformance/src/test_case.rs b/tasks/transform_conformance/src/test_case.rs index 486d57867..2b3bb70fe 100644 --- a/tasks/transform_conformance/src/test_case.rs +++ b/tasks/transform_conformance/src/test_case.rs @@ -116,6 +116,7 @@ pub trait TestCase { template_literals: options.get_plugin("transform-template-literals").is_some(), property_literals: options.get_plugin("transform-property-literals").is_some(), duplicate_keys: options.get_plugin("transform-duplicate-keys").is_some(), + new_target: options.get_plugin("transform-new-target").is_some(), } }