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:
Boshen 2023-09-16 16:05:35 +08:00 committed by GitHub
parent 91784515f0
commit 419d5aa6ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 250 additions and 1 deletions

12
Cargo.lock generated
View file

@ -1718,6 +1718,18 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "oxc_transformer"
version = "0.2.0"
dependencies = [
"oxc_allocator",
"oxc_ast",
"oxc_formatter",
"oxc_parser",
"oxc_span",
"oxc_syntax",
]
[[package]] [[package]]
name = "oxc_type_synthesis" name = "oxc_type_synthesis"
version = "0.0.0" version = "0.0.0"

View file

@ -257,6 +257,12 @@ pub struct IdentifierName {
pub name: Atom, pub name: Atom,
} }
impl IdentifierName {
pub fn new(span: Span, name: Atom) -> Self {
Self { span, name }
}
}
/// Identifier Reference /// Identifier Reference
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))] #[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]

View file

@ -43,6 +43,12 @@ impl Hash for NullLiteral {
} }
} }
impl NullLiteral {
pub fn new(span: Span) -> Self {
Self { span }
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))] #[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
pub struct NumberLiteral<'a> { pub struct NumberLiteral<'a> {

View file

@ -6,13 +6,14 @@
)] )]
use oxc_allocator::{Allocator, Box, String, Vec}; use oxc_allocator::{Allocator, Box, String, Vec};
use oxc_span::{Atom, SourceType, Span}; use oxc_span::{Atom, GetSpan, SourceType, Span};
use oxc_syntax::{ use oxc_syntax::{
operator::{ operator::{
AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator, UpdateOperator, AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator, UpdateOperator,
}, },
NumberBase, NumberBase,
}; };
use std::mem;
#[allow(clippy::wildcard_imports)] #[allow(clippy::wildcard_imports)]
use crate::ast::*; use crate::ast::*;
@ -54,6 +55,13 @@ impl<'a> AstBuilder<'a> {
String::from_str_in(value, self.allocator).into_bump_str() 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( pub fn program(
&self, &self,
span: Span, span: Span,

View 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 }

View 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}");
}

View 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;
}
}

View file

@ -0,0 +1,3 @@
mod exponentiation_operator;
pub use exponentiation_operator::ExponentiationOperator;

View file

@ -0,0 +1,3 @@
mod optional_catch_binding;
pub use optional_catch_binding::OptionalCatchBinding;

View 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);
}
}

View 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);
}
}