mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 20:32:10 +00:00
feat(transformer): transformer prototype (#918)
This PR introduces the oxc transformer. The code is purposely arranged without any Rust trickery such as traits and macros. I intend to keep the code style this way until something blows up. The next steps are: * unit tests * conformance tests with TypeScript and Babel * benchmarks
This commit is contained in:
parent
91784515f0
commit
419d5aa6ee
11 changed files with 250 additions and 1 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -1718,6 +1718,18 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxc_transformer"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
"oxc_formatter",
|
||||
"oxc_parser",
|
||||
"oxc_span",
|
||||
"oxc_syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxc_type_synthesis"
|
||||
version = "0.0.0"
|
||||
|
|
|
|||
|
|
@ -257,6 +257,12 @@ pub struct IdentifierName {
|
|||
pub name: Atom,
|
||||
}
|
||||
|
||||
impl IdentifierName {
|
||||
pub fn new(span: Span, name: Atom) -> Self {
|
||||
Self { span, name }
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier Reference
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ impl Hash for NullLiteral {
|
|||
}
|
||||
}
|
||||
|
||||
impl NullLiteral {
|
||||
pub fn new(span: Span) -> Self {
|
||||
Self { span }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
|
||||
pub struct NumberLiteral<'a> {
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@
|
|||
)]
|
||||
|
||||
use oxc_allocator::{Allocator, Box, String, Vec};
|
||||
use oxc_span::{Atom, SourceType, Span};
|
||||
use oxc_span::{Atom, GetSpan, SourceType, Span};
|
||||
use oxc_syntax::{
|
||||
operator::{
|
||||
AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator, UpdateOperator,
|
||||
},
|
||||
NumberBase,
|
||||
};
|
||||
use std::mem;
|
||||
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use crate::ast::*;
|
||||
|
|
@ -54,6 +55,13 @@ impl<'a> AstBuilder<'a> {
|
|||
String::from_str_in(value, self.allocator).into_bump_str()
|
||||
}
|
||||
|
||||
/// 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());
|
||||
let null_expr = self.literal_null_expression(null_literal);
|
||||
mem::replace(expr, null_expr)
|
||||
}
|
||||
|
||||
pub fn program(
|
||||
&self,
|
||||
span: Span,
|
||||
|
|
|
|||
23
crates/oxc_transformer/Cargo.toml
Normal file
23
crates/oxc_transformer/Cargo.toml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "oxc_transformer"
|
||||
version = "0.2.0"
|
||||
publish = false
|
||||
authors.workspace = true
|
||||
description.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
keywords.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
oxc_ast = { workspace = true }
|
||||
oxc_span = { workspace = true }
|
||||
oxc_allocator = { workspace = true }
|
||||
oxc_syntax = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
oxc_parser = { workspace = true }
|
||||
oxc_formatter = { workspace = true }
|
||||
41
crates/oxc_transformer/examples/transformer.rs
Normal file
41
crates/oxc_transformer/examples/transformer.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use std::{env, path::Path};
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_formatter::{Formatter, FormatterOptions};
|
||||
use oxc_parser::Parser;
|
||||
use oxc_span::SourceType;
|
||||
use oxc_transformer::{TransformOptions, Transformer};
|
||||
|
||||
// Instruction:
|
||||
// create a `test.js`,
|
||||
// run `cargo run -p oxc_transformer --example transformer`
|
||||
// or `just watch "run -p oxc_transformer --example transformer"`
|
||||
|
||||
fn main() {
|
||||
let name = env::args().nth(1).unwrap_or_else(|| "test.js".to_string());
|
||||
let path = Path::new(&name);
|
||||
let source_text = std::fs::read_to_string(path).expect("{name} not found");
|
||||
let allocator = Allocator::default();
|
||||
let source_type = SourceType::from_path(path).unwrap();
|
||||
let ret = Parser::new(&allocator, &source_text, source_type).parse();
|
||||
|
||||
if !ret.errors.is_empty() {
|
||||
for error in ret.errors {
|
||||
let error = error.with_source_code(source_text.clone());
|
||||
println!("{error:?}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let formatter_options = FormatterOptions::default();
|
||||
let program = allocator.alloc(ret.program);
|
||||
let printed = Formatter::new(source_text.len(), formatter_options.clone()).build(program);
|
||||
println!("Original:\n");
|
||||
println!("{printed}");
|
||||
|
||||
let transform_options = TransformOptions::default();
|
||||
Transformer::new(&allocator, &transform_options).build(program);
|
||||
let printed = Formatter::new(source_text.len(), formatter_options).build(program);
|
||||
println!("Transformed:\n");
|
||||
println!("{printed}");
|
||||
}
|
||||
42
crates/oxc_transformer/src/es2016/exponentiation_operator.rs
Normal file
42
crates/oxc_transformer/src/es2016/exponentiation_operator.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use oxc_ast::{ast::*, AstBuilder};
|
||||
use oxc_span::Span;
|
||||
use oxc_syntax::operator::BinaryOperator;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
/// ES2016: Exponentiation Operator
|
||||
///
|
||||
/// References:
|
||||
/// * <https://babel.dev/docs/babel-plugin-transform-exponentiation-operator>
|
||||
/// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-exponentiation-operator>
|
||||
///
|
||||
pub struct ExponentiationOperator<'a> {
|
||||
ast: Rc<AstBuilder<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> ExponentiationOperator<'a> {
|
||||
pub fn new(ast: Rc<AstBuilder<'a>>) -> Self {
|
||||
Self { ast }
|
||||
}
|
||||
|
||||
pub fn transform_expression<'b>(&mut self, expr: &'b mut Expression<'a>) {
|
||||
let Expression::BinaryExpression(binary_expression) = expr else { return };
|
||||
if binary_expression.operator != BinaryOperator::Exponential {
|
||||
return;
|
||||
}
|
||||
// left ** right
|
||||
let left = self.ast.move_expression(&mut binary_expression.left);
|
||||
let right = self.ast.move_expression(&mut binary_expression.right);
|
||||
// Math.pow
|
||||
let ident_math = IdentifierReference::new("Math".into(), Span::default());
|
||||
let object = self.ast.identifier_expression(ident_math);
|
||||
let property = IdentifierName::new(Span::default(), "pow".into());
|
||||
let callee = self.ast.static_member_expression(Span::default(), object, property, false);
|
||||
// Math.pow(left, right)
|
||||
let mut arguments = self.ast.new_vec_with_capacity(2);
|
||||
arguments.push(Argument::Expression(left));
|
||||
arguments.push(Argument::Expression(right));
|
||||
let call_expr = self.ast.call_expression(Span::default(), callee, arguments, false, None);
|
||||
*expr = call_expr;
|
||||
}
|
||||
}
|
||||
3
crates/oxc_transformer/src/es2016/mod.rs
Normal file
3
crates/oxc_transformer/src/es2016/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
mod exponentiation_operator;
|
||||
|
||||
pub use exponentiation_operator::ExponentiationOperator;
|
||||
3
crates/oxc_transformer/src/es2019/mod.rs
Normal file
3
crates/oxc_transformer/src/es2019/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
mod optional_catch_binding;
|
||||
|
||||
pub use optional_catch_binding::OptionalCatchBinding;
|
||||
29
crates/oxc_transformer/src/es2019/optional_catch_binding.rs
Normal file
29
crates/oxc_transformer/src/es2019/optional_catch_binding.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use oxc_ast::{ast::*, AstBuilder};
|
||||
use oxc_span::Span;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
/// ES2019: Optional Catch Binding
|
||||
///
|
||||
/// References:
|
||||
/// * <https://babel.dev/docs/babel-plugin-transform-optional-catch-binding>
|
||||
/// * <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-optional-catch-binding>
|
||||
pub struct OptionalCatchBinding<'a> {
|
||||
ast: Rc<AstBuilder<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> OptionalCatchBinding<'a> {
|
||||
pub fn new(ast: Rc<AstBuilder<'a>>) -> Self {
|
||||
Self { ast }
|
||||
}
|
||||
|
||||
pub fn transform_catch_clause<'b>(&mut self, clause: &'b mut CatchClause<'a>) {
|
||||
if clause.param.is_some() {
|
||||
return;
|
||||
}
|
||||
let binding_identifier = BindingIdentifier::new("unused".into(), Span::default());
|
||||
let binding_pattern_kind = self.ast.binding_identifier(binding_identifier);
|
||||
let binding_pattern = self.ast.binding_pattern(binding_pattern_kind, None, false);
|
||||
clause.param = Some(binding_pattern);
|
||||
}
|
||||
}
|
||||
76
crates/oxc_transformer/src/lib.rs
Normal file
76
crates/oxc_transformer/src/lib.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#![allow(clippy::wildcard_imports, clippy::option_map_unit_fn)]
|
||||
|
||||
//! Transformer / Transpiler
|
||||
//!
|
||||
//! References:
|
||||
//! * <https://www.typescriptlang.org/tsconfig#target>
|
||||
//! * <https://babel.dev/docs/presets>
|
||||
//! * <https://github.com/microsoft/TypeScript/blob/main/src/compiler/transformer.ts>
|
||||
|
||||
mod es2016;
|
||||
mod es2019;
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_ast::{ast::*, AstBuilder, VisitMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
use es2016::ExponentiationOperator;
|
||||
use es2019::OptionalCatchBinding;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct TransformOptions {
|
||||
target: TransformTarget,
|
||||
}
|
||||
|
||||
/// See <https://www.typescriptlang.org/tsconfig#target>
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum TransformTarget {
|
||||
ES2016,
|
||||
ES2019,
|
||||
#[default]
|
||||
ESNext,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Transformer<'a> {
|
||||
// es2016
|
||||
es2016_exponentiation_operator: Option<ExponentiationOperator<'a>>,
|
||||
// es2019
|
||||
es2019_optional_catch_binding: Option<OptionalCatchBinding<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Transformer<'a> {
|
||||
pub fn new(allocator: &'a Allocator, options: &TransformOptions) -> Self {
|
||||
let ast = Rc::new(AstBuilder::new(allocator));
|
||||
|
||||
let mut t = Self::default();
|
||||
if options.target < TransformTarget::ES2016 {
|
||||
t.es2016_exponentiation_operator.replace(ExponentiationOperator::new(Rc::clone(&ast)));
|
||||
}
|
||||
if options.target < TransformTarget::ES2019 {
|
||||
t.es2019_optional_catch_binding.replace(OptionalCatchBinding::new(Rc::clone(&ast)));
|
||||
}
|
||||
t
|
||||
}
|
||||
|
||||
pub fn build<'b>(mut self, program: &'b mut Program<'a>) {
|
||||
self.visit_program(program);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> VisitMut<'a, 'b> for Transformer<'a> {
|
||||
fn visit_expression(&mut self, expr: &'b mut Expression<'a>) {
|
||||
self.es2016_exponentiation_operator.as_mut().map(|t| t.transform_expression(expr));
|
||||
|
||||
self.visit_expression_match(expr);
|
||||
}
|
||||
|
||||
fn visit_catch_clause(&mut self, clause: &'b mut CatchClause<'a>) {
|
||||
self.es2019_optional_catch_binding.as_mut().map(|t| t.transform_catch_clause(clause));
|
||||
|
||||
if let Some(param) = &mut clause.param {
|
||||
self.visit_binding_pattern(param);
|
||||
}
|
||||
self.visit_statements(&mut clause.body.body);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue