Skip to content

Commit

Permalink
feat(transformer): Class Static Block
Browse files Browse the repository at this point in the history
  • Loading branch information
magic-akari committed Oct 7, 2023
1 parent a710e73 commit abf31e9
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 5 deletions.
31 changes: 31 additions & 0 deletions crates/oxc_ast/src/ast_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ impl<'a> AstBuilder<'a> {
mem::replace(expr, null_expr)
}

pub fn move_statement(&self, stmt: &mut Statement<'a>) -> Statement<'a> {
let empty_stmt = self.empty_statement(stmt.span());
mem::replace(stmt, empty_stmt)
}

pub fn program(
&self,
span: Span,
Expand Down Expand Up @@ -715,6 +720,32 @@ impl<'a> AstBuilder<'a> {
ClassElement::StaticBlock(self.alloc(StaticBlock { span, body }))
}

pub fn class_property(
&self,
span: Span,
key: PropertyKey<'a>,
value: Option<Expression<'a>>,
computed: bool,
r#static: bool,
decorators: Vec<'a, Decorator<'a>>,
) -> ClassElement<'a> {
ClassElement::PropertyDefinition(self.alloc(PropertyDefinition {
span,
key,
value,
computed,
r#static,
declare: false,
r#override: false,
optional: false,
definite: false,
readonly: false,
type_annotation: None,
accessibility: None,
decorators,
}))
}

pub fn accessor_property(
&self,
span: Span,
Expand Down
105 changes: 105 additions & 0 deletions crates/oxc_transformer/src/es2022/class_static_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use oxc_ast::{ast::*, AstBuilder};
use oxc_span::{Atom, Span};

use std::{collections::HashSet, mem, ops::DerefMut, rc::Rc};

/// ES2022: Class Static Block
///
/// References:
/// * <https://babel.dev/docs/babel-plugin-transform-class-static-block>
/// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-class-static-block>
pub struct ClassStaticBlock<'a> {
ast: Rc<AstBuilder<'a>>,
}

impl<'a> ClassStaticBlock<'a> {
pub fn new(ast: Rc<AstBuilder<'a>>) -> Self {
Self { ast }
}

pub fn transform_class_body<'b>(&mut self, class_body: &'b mut ClassBody<'a>) {
if !class_body.body.iter().any(|e| matches!(e, ClassElement::StaticBlock(..))) {
return;
}

let private_names: HashSet<Atom> = class_body
.body
.iter()
.filter_map(ClassElement::property_key)
.filter_map(|p| match p {
PropertyKey::PrivateIdentifier(p) => Some(p.name.clone()),
_ => None,
})
.collect();

let mut i = 0;
for element in class_body.body.iter_mut() {
let ClassElement::StaticBlock(block) = element else {
continue;
};

let static_block_private_id = generate_uid(&private_names, &mut i);
let key = PropertyKey::PrivateIdentifier(self.ast.alloc(PrivateIdentifier {
span: Span::default(),
name: static_block_private_id.clone(),
}));

let value = (block.body.len() == 1
&& matches!(block.body[0], Statement::ExpressionStatement(..)))
.then(|| {
// We special-case the single expression case to avoid the iife, since it's common.
let stmt = self.ast.move_statement(&mut block.body.deref_mut()[0]);
match stmt {
Statement::ExpressionStatement(mut expr_stmt) => {
self.ast.move_expression(&mut expr_stmt.expression)
}
_ => unreachable!(),
}
})
.unwrap_or_else(|| {
let statements = mem::replace(&mut block.body, self.ast.new_vec());
let callee = self.ast.parenthesized_expression(
Span::default(),
self.ast.arrow_expression(
Span::default(),
false,
false,
false,
self.ast.formal_parameters(
Span::default(),
FormalParameterKind::ArrowFormalParameters,
self.ast.new_vec(),
None,
),
self.ast.function_body(Span::default(), self.ast.new_vec(), statements),
None,
None,
),
);

self.ast.call_expression(Span::default(), callee, self.ast.new_vec(), false, None)
});

*element = self.ast.class_property(
block.span,
key,
Some(value),
false,
true,
self.ast.new_vec(),
);
}
}
}

fn generate_uid(deny_list: &HashSet<Atom>, i: &mut u32) -> Atom {
*i += 1;

let mut uid: Atom = if *i == 1 { "_".to_string() } else { format!("_{i}") }.into();
while deny_list.contains(&uid) {
*i += 1;
uid = format!("_{i}").into();
}

uid
}
3 changes: 3 additions & 0 deletions crates/oxc_transformer/src/es2022/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod class_static_block;

pub use class_static_block::ClassStaticBlock;
14 changes: 14 additions & 0 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod es2015;
mod es2016;
mod es2019;
mod es2021;
mod es2022;
mod options;
mod react_jsx;
mod typescript;
Expand All @@ -35,6 +36,8 @@ pub use crate::options::{
pub struct Transformer<'a> {
typescript: Option<TypeScript<'a>>,
react_jsx: Option<ReactJsx<'a>>,
// es2022
es2022_class_static_block: Option<es2022::ClassStaticBlock<'a>>,
// es2021
es2021_logical_assignment_operators: Option<LogicalAssignmentOperators<'a>>,
// es2019
Expand All @@ -60,6 +63,9 @@ impl<'a> Transformer<'a> {
if let Some(react_options) = options.react {
t.react_jsx.replace(ReactJsx::new(Rc::clone(&ast), react_options));
}
if options.target < TransformTarget::ES2022 {
t.es2022_class_static_block.replace(es2022::ClassStaticBlock::new(Rc::clone(&ast)));
}
if options.target < TransformTarget::ES2021 {
t.es2021_logical_assignment_operators
.replace(LogicalAssignmentOperators::new(Rc::clone(&ast)));
Expand Down Expand Up @@ -109,4 +115,12 @@ impl<'a, 'b> VisitMut<'a, 'b> for Transformer<'a> {
self.visit_expression(init);
}
}

fn visit_class_body(&mut self, class_body: &'b mut ClassBody<'a>) {
self.es2022_class_static_block.as_mut().map(|t| t.transform_class_body(class_body));

class_body.body.iter_mut().for_each(|class_element| {
self.visit_class_element(class_element);
});
}
}
1 change: 1 addition & 0 deletions crates/oxc_transformer/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum TransformTarget {
ES2016,
ES2019,
ES2021,
ES2022,
#[default]
ESNext,
}
Expand Down
10 changes: 5 additions & 5 deletions tasks/transform_conformance/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Passed: 91/1078
Passed: 95/1078

# babel-plugin-transform-class-properties
* Failed: assumption-constantSuper/complex-super-class/input.js
Expand Down Expand Up @@ -269,10 +269,6 @@ Passed: 91/1078
* Passed: public-loose/arrow-this-without-transform/input.js

# babel-plugin-transform-class-static-block
* Failed: class-static-block/class-binding/input.js
* Failed: class-static-block/class-declaration/input.js
* Failed: class-static-block/in-class-heritage/input.js
* Failed: class-static-block/multiple-static-initializers/input.js
* Failed: class-static-block/name-conflict/input.js
* Failed: class-static-block/new-target/input.js
* Failed: class-static-block/preserve-comments/input.js
Expand All @@ -290,6 +286,10 @@ Passed: 91/1078
* Failed: integration-loose/name-conflict/input.js
* Failed: integration-loose/preserve-comments/input.js
* Failed: integration-loose/super-static-block/input.js
* Passed: class-static-block/class-binding/input.js
* Passed: class-static-block/class-declaration/input.js
* Passed: class-static-block/in-class-heritage/input.js
* Passed: class-static-block/multiple-static-initializers/input.js
* Passed: integration-loose/.new-target/input.js

# babel-plugin-transform-private-methods
Expand Down

0 comments on commit abf31e9

Please sign in to comment.