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

new: Start on es2022 preset. Implement class-static-block. #3033

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions crates/oxc_ast/src/ast/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2126,6 +2126,15 @@ impl<'a> ClassElement<'a> {
matches!(self, Self::PropertyDefinition(_) | Self::AccessorProperty(_))
}

pub fn is_private(&self) -> bool {
match self {
Self::TSIndexSignature(_) | Self::StaticBlock(_) => false,
Self::MethodDefinition(def) => def.key.is_private_identifier(),
Self::PropertyDefinition(def) => def.key.is_private_identifier(),
Self::AccessorProperty(def) => def.key.is_private_identifier(),
}
}

pub fn is_ts_empty_body_function(&self) -> bool {
match self {
Self::PropertyDefinition(_)
Expand Down
40 changes: 40 additions & 0 deletions crates/oxc_ast/src/ast_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ impl<'a> AstBuilder<'a> {
}
}

pub fn move_vec<T>(&self, stmts: &mut Vec<'a, T>) -> Vec<'a, T> {
mem::replace(stmts, self.new_vec())
}

/// Moves the expression out by replacing it with a null expression.
pub fn move_expression(&self, expr: &mut Expression<'a>) -> Expression<'a> {
let null_literal = NullLiteral::new(expr.span());
Expand Down Expand Up @@ -1156,6 +1160,14 @@ impl<'a> AstBuilder<'a> {
PropertyKey::Expression(expr)
}

pub fn property_key_private_identifier(&self, ident: PrivateIdentifier<'a>) -> PropertyKey<'a> {
PropertyKey::PrivateIdentifier(self.alloc(ident))
}

pub fn private_identifier(&self, span: Span, name: &str) -> PrivateIdentifier<'a> {
PrivateIdentifier { span, name: self.new_atom(name) }
}

/* ---------- Modules ---------- */

pub fn module_declaration(&self, decl: ModuleDeclaration<'a>) -> Statement<'a> {
Expand Down Expand Up @@ -1873,4 +1885,32 @@ impl<'a> AstBuilder<'a> {
pub fn js_doc_unknown_type(&self, span: Span) -> TSType<'a> {
TSType::JSDocUnknownType(self.alloc(JSDocUnknownType { span }))
}

/* ---------- Patterns ---------- */

pub fn build_iife(&self, span: Span, statements: Vec<'a, Statement<'a>>) -> Expression<'a> {
self.call_expression(
span,
self.parenthesized_expression(
span,
self.arrow_function_expression(
span,
false,
false,
self.formal_parameters(
span,
FormalParameterKind::ArrowFormalParameters,
self.new_vec(),
None,
),
self.function_body(span, self.new_vec(), statements),
None,
None,
),
),
self.new_vec(),
false,
None,
)
}
}
79 changes: 79 additions & 0 deletions crates/oxc_transformer/src/es2022/class_static_block/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use std::rc::Rc;

use oxc_ast::ast::{ClassBody, ClassElement, PropertyDefinitionType, Statement};
use oxc_span::SPAN;
use rustc_hash::FxHashSet;

use crate::context::Ctx;

pub struct ClassStaticBlock<'a> {
ctx: Ctx<'a>,
}

impl<'a> ClassStaticBlock<'a> {
pub fn new(ctx: &Ctx<'a>) -> Self {
Self { ctx: Rc::clone(ctx) }
}

pub fn transform_class_body(&mut self, body: &mut ClassBody<'a>) {
let mut private_names = FxHashSet::default();
let ast = &self.ctx.ast;

for stmt in &body.body {
if stmt.is_private() {
if let Some(name) = stmt.property_key().and_then(|k| k.private_name()) {
private_names.insert(name.as_str().to_owned());
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to avoid the clones but kept running into borrow issues.

}
}
}

let mut generate_id = || -> String {
let mut id = "_".to_owned();
let mut i = 1;

while private_names.contains(&id) {
i += 1;
id = format!("_{i}");
}

private_names.insert(id.clone());

id
};

let stmts = ast.move_vec(&mut body.body).into_iter().map(|stmt| {
if let ClassElement::StaticBlock(mut block) = stmt {
let private_id = generate_id();

// We special-case the single expression case to avoid the iife,
// since it's common.
let replacement = if block.body.len() == 1
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better way to handle this branch??

&& matches!(&block.body[0], Statement::ExpressionStatement(_))
{
match block.body.remove(0) {
Statement::ExpressionStatement(mut expr) => {
ast.move_expression(&mut expr.expression)
}
_ => unreachable!(),
}
} else {
ast.build_iife(SPAN, ast.move_vec(&mut block.body))
};

ast.class_property(
PropertyDefinitionType::PropertyDefinition,
SPAN,
ast.property_key_private_identifier(ast.private_identifier(SPAN, &private_id)),
Some(replacement),
false,
true,
ast.new_vec(),
)
} else {
stmt
}
});

body.body = ast.new_vec_from_iter(stmts);
}
}
38 changes: 38 additions & 0 deletions crates/oxc_transformer/src/es2022/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
mod class_static_block;
mod options;

use std::rc::Rc;

use oxc_ast::ast::ClassBody;

use crate::context::Ctx;

use self::class_static_block::ClassStaticBlock;
pub use self::options::Es2022Options;

#[allow(dead_code)]
pub struct Es2022<'a> {
ctx: Ctx<'a>,
options: Rc<Es2022Options>,

// Plugins
class_static_block: ClassStaticBlock<'a>,
}

impl<'a> Es2022<'a> {
pub fn new(options: Es2022Options, ctx: &Ctx<'a>) -> Self {
let options = Rc::new(options);

Self {
options: Rc::clone(&options),
ctx: Rc::clone(ctx),
class_static_block: ClassStaticBlock::new(ctx),
}
}

pub fn transform_class_body(&mut self, body: &mut ClassBody<'a>) {
if self.options.class_static_block_plugin {
self.class_static_block.transform_class_body(body);
}
}
}
14 changes: 14 additions & 0 deletions crates/oxc_transformer/src/es2022/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use serde::Deserialize;

#[derive(Debug, Clone, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Es2022Options {
#[serde(skip)]
pub class_static_block_plugin: bool,
}

impl Default for Es2022Options {
fn default() -> Self {
Self { class_static_block_plugin: true }
}
}
11 changes: 9 additions & 2 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod compiler_assumptions;
mod context;
mod options;
// Presets: <https://babel.dev/docs/presets>
mod es2022;
mod react;
mod typescript;
mod utils;
Expand All @@ -22,6 +23,7 @@ mod helpers {

use std::{path::Path, rc::Rc};

use es2022::Es2022;
use oxc_allocator::{Allocator, Vec};
use oxc_ast::{
ast::*,
Expand All @@ -31,8 +33,8 @@ use oxc_diagnostics::Error;
use oxc_semantic::Semantic;

pub use crate::{
compiler_assumptions::CompilerAssumptions, options::TransformOptions, react::ReactOptions,
typescript::TypeScriptOptions,
compiler_assumptions::CompilerAssumptions, es2022::Es2022Options, options::TransformOptions,
react::ReactOptions, typescript::TypeScriptOptions,
};

use crate::{
Expand All @@ -43,9 +45,12 @@ use crate::{

pub struct Transformer<'a> {
ctx: Ctx<'a>,

// NOTE: all callbacks must run in order.
x0_typescript: TypeScript<'a>,
x1_react: React<'a>,
// x2_decorators,
x3_es2022: Es2022<'a>,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ordering here is weird. When we add es2024, es2025, etc, all the numbers will shift. We probably need a different ordering system.

}

impl<'a> Transformer<'a> {
Expand All @@ -60,6 +65,7 @@ impl<'a> Transformer<'a> {
ctx: Rc::clone(&ctx),
x0_typescript: TypeScript::new(options.typescript, &ctx),
x1_react: React::new(options.react, &ctx),
x3_es2022: Es2022::new(options.es2022, &ctx),
}
}

Expand Down Expand Up @@ -112,6 +118,7 @@ impl<'a> VisitMut<'a> for Transformer<'a> {

fn visit_class_body(&mut self, body: &mut ClassBody<'a>) {
self.x0_typescript.transform_class_body(body);
self.x3_es2022.transform_class_body(body);

walk_mut::walk_class_body_mut(self, body);
}
Expand Down
4 changes: 4 additions & 0 deletions crates/oxc_transformer/src/options.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::path::PathBuf;

use crate::es2022::Es2022Options;
use crate::{
compiler_assumptions::CompilerAssumptions, react::ReactOptions, typescript::TypeScriptOptions,
};
Expand All @@ -24,4 +25,7 @@ pub struct TransformOptions {

/// [preset-react](https://babeljs.io/docs/babel-preset-react)
pub react: ReactOptions,

// [es2022](https://babeljs.io/docs/babel-preset-env)
pub es2022: Es2022Options,
}
3 changes: 2 additions & 1 deletion tasks/transform_conformance/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Passed: 153/209
Passed: 160/216

# All Passed:
* babel-plugin-transform-class-static-block
* babel-plugin-transform-react-jsx-source
* babel-plugin-transform-react-jsx-development

Expand Down
4 changes: 2 additions & 2 deletions tasks/transform_conformance/babel_exec.snap.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Passed: 0/2
Passed: 11/13

# All Passed:

* babel-plugin-transform-class-static-block


# babel-plugin-transform-react-jsx-source (0/2)
Expand Down
2 changes: 1 addition & 1 deletion tasks/transform_conformance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const PLUGINS: &[&str] = &[
// "babel-plugin-transform-unicode-sets-regex",
// // ES2022
// "babel-plugin-transform-class-properties",
// "babel-plugin-transform-class-static-block",
"babel-plugin-transform-class-static-block",
// "babel-plugin-transform-private-methods",
// "babel-plugin-transform-private-property-in-object",
// // [Syntax] "babel-plugin-transform-syntax-top-level-await",
Expand Down
9 changes: 8 additions & 1 deletion tasks/transform_conformance/src/test_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::{SourceType, VALID_EXTENSIONS};
use oxc_tasks_common::{normalize_path, print_diff_in_terminal, BabelOptions, TestOs};
use oxc_transformer::{ReactOptions, TransformOptions, Transformer, TypeScriptOptions};
use oxc_transformer::{
Es2022Options, ReactOptions, TransformOptions, Transformer, TypeScriptOptions,
};

use crate::{fixture_root, packages_root, TestRunnerEnv, PLUGINS_NOT_SUPPORTED_YET};

Expand Down Expand Up @@ -95,6 +97,10 @@ fn transform_options(options: &BabelOptions) -> serde_json::Result<TransformOpti
react_options
};

let es2022 = Es2022Options {
class_static_block_plugin: options.get_plugin("transform-class-static-block").is_some(),
};

Ok(TransformOptions {
cwd: options.cwd.clone().unwrap(),
assumptions: serde_json::from_value(options.assumptions.clone()).unwrap_or_default(),
Expand All @@ -104,6 +110,7 @@ fn transform_options(options: &BabelOptions) -> serde_json::Result<TransformOpti
.transpose()?
.unwrap_or_default(),
react,
es2022,
})
}

Expand Down