Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(transform): support es2015 new target #1967

Merged
merged 7 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions crates/oxc_semantic/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,16 +183,22 @@ impl ScopeTree {
expr.gather(&mut |part| parts.push(part));
let name = parts.join("$");
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.. {
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;
}
}
unreachable!()
}

fn generate_uid(name: &str, i: i32) -> Atom {
underfin marked this conversation as resolved.
Show resolved Hide resolved
// <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}") })
}
}
2 changes: 2 additions & 0 deletions crates/oxc_transformer/src/es2015/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ mod arrow_functions;
mod duplicate_keys;
mod function_name;
mod instanceof;
mod new_target;
mod shorthand_properties;
mod template_literals;

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;
133 changes: 133 additions & 0 deletions crates/oxc_transformer/src/es2015/new_target.rs
Original file line number Diff line number Diff line change
@@ -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>) {
Boshen marked this conversation as resolved.
Show resolved Hide resolved
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.",
));
}
}
}
}
}
17 changes: 15 additions & 2 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,6 +69,7 @@ pub struct Transformer<'a> {
es2015_template_literals: Option<TemplateLiterals<'a>>,
es2015_duplicate_keys: Option<DuplicateKeys<'a>>,
es2015_instanceof: Option<Instanceof<'a>>,
es2015_new_target: Option<NewTarget<'a>>,
es3_property_literal: Option<PropertyLiteral<'a>>,
}

Expand Down Expand Up @@ -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)
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand All @@ -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>) {
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_transformer/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub struct TransformOptions {
pub property_literals: bool,
pub babel_8_breaking: Option<bool>,
pub instanceof: bool,
pub new_target: bool,
}

/// See <https://www.typescriptlang.org/tsconfig#target>
Expand Down
10 changes: 9 additions & 1 deletion tasks/transform_conformance/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Passed: 295/1171
Passed: 297/1179

# All Passed:
* babel-plugin-transform-numeric-separator
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion tasks/transform_conformance/babel_exec.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Passed: 379/445
Passed: 386/454

# All Passed:
* babel-plugin-transform-class-static-block
Expand Down Expand Up @@ -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

1 change: 1 addition & 0 deletions tasks/transform_conformance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions tasks/transform_conformance/src/test_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}

Expand Down