oxc/crates/oxc_transformer
oxc-bot 0b900110a2
Release crates v0.25.0 (#5107)
## [0.25.0] - 2024-08-23

- 78f135d ast: [**BREAKING**] Remove `ReferenceFlag` from
`IdentifierReference` (#5077) (Boshen)

- f2b8d82 semantic: [**BREAKING**] `ScopeTree::get_child_ids` +
`get_child_ids_mut` return value not `Option` (#5058) (overlookmotel)

- 5f4c9ab semantic: [**BREAKING**] Rename `SymbolTable::get_flag` to
`get_flags` (#5030) (overlookmotel)

- 58bf215 semantic: [**BREAKING**] Rename `Reference::flag` and
`flag_mut` methods to plural (#5025) (overlookmotel)

- c4c08a7 ast: [**BREAKING**] Rename
`IdentifierReference::reference_flags` field (#5024) (overlookmotel)

- d262a58 syntax: [**BREAKING**] Rename `ReferenceFlag` to
`ReferenceFlags` (#5023) (overlookmotel)

- c30e2e9 semantic: [**BREAKING**] `Reference::flag` method return
`ReferenceFlag` (#5019) (overlookmotel)

- ce4d469 codegen: [**BREAKING**] Remove const generic `MINIFY` (#5001)
(Boshen)

- b2ff2df parser: [**BREAKING**] Remove builder pattern from `Parser`
struct (#5000) (Boshen)

- f88970b ast: [**BREAKING**] Change order of fields in CallExpression
(#4859) (Burlin)

### Features

- 714373d ast: `inherit_variants!` macro add `into_*` methods (#5005)
(overlookmotel)
- 6800e69 oxc: Add `Compiler` and `CompilerInterface` (#4954) (Boshen)
- 2b21be3 oxc_minifier: Define plugin with postfix wildcard (#4979)
(IWANABETHATGUY)
- afe728a parser: Parse regular expression with regex parser (#4998)
(Boshen)
- 4b49cf8 transformer: Always pass in symbols and scopes (#5087)
(Boshen)
- f51d3f9 transformer/nullish-coalescing-operator: Handles nullish
coalescing expression in the FormalParamter (#4975) (Dunqing)
- f794870 transformer/nullish-coalescing-operator: Generate the correct
binding name (#4974) (Dunqing)
- 72ff2c6 transformer/nullish-coalescing-operator: Add comments in top
of file (#4972) (Dunqing)
- 6b885fe traverse: Expose `generate_uid_based_on_node` and
`generate_uid_in_current_scope_based_on_node` from `TraverseCtx` (#4965)
(Dunqing)

### Bug Fixes

- 7f3129e ast: Correct code comment (#5004) (overlookmotel)
- 1bd9365 coverage: Correctly check semantic data after transform
(#5035) (Boshen)
- 185eb20 isolated_declarations: Namespaces that are default exported
should be considered for expando functions (#4935) (michaelm)
- 2a5e15d npm: `libc` field should not be `null` (Boshen)
- efbdced parser: Only show flow error if it's a flow file (#5069)
(Boshen)
- ad2be97 semantic: Incorrect semantic check for label has same name
(#5041) (heygsc)
- d5de97d semantic: Transform checker check reference flags (#5092)
(overlookmotel)
- 90c74ee semantic: Transform checker check reference symbol IDs (#5090)
(overlookmotel)
- a8005b9 semantic: Transform checker check symbol redeclarations
(#5089) (overlookmotel)
- 205bff7 semantic: Transform checker check symbol references (#5088)
(overlookmotel)
- 4a57086 semantic: Transform checker check symbol IDs (#5078)
(overlookmotel)
- ea7d216 semantic: Transform checker check symbol spans (#5076)
(overlookmotel)
- 1b6b27a semantic: Transform checker check symbol flags (#5074)
(overlookmotel)
- 6d87b0f semantic: Fix error message for duplicated label (#5071)
(Boshen)
- 05fff16 semantic: Transform checker compare binding symbol IDs (#5057)
(overlookmotel)
- f187b71 semantic: Transform checker compare scope children (#5056)
(overlookmotel)
- b52c6a4 semantic: Transform checker compare scope parents (#5055)
(overlookmotel)
- da64014 semantic: Transform checker catch more scope flags mismatches
(#5054) (overlookmotel)
- 67d1a96 semantic: Transform checker compare scope flags (#5052)
(overlookmotel)
- 863b9cb semantic: Transform checker handle conditional scopes (#5040)
(overlookmotel)
- 47029c4 semantic: Transform checker output symbol names in errors
(#5038) (overlookmotel)
- 6ffbd78 transformer: Remove an `AstBuilder::copy` call from TS
namespace transform (#4987) (overlookmotel)
- a8dfdda transformer: Remove an `AstBuilder::copy` call from TS module
transform (#4986) (overlookmotel)
- 1467eb3 transformer: Remove an `AstBuilder::copy` call from TS enum
transform (#4985) (overlookmotel)
- 1365feb transformer: Remove an `AstBuilder::copy` call for TS
`AssignmentTarget` transform (#4984) (overlookmotel)
- edacf93 transformer: Remove an `AstBuilder::copy` call (#4983)
(overlookmotel)
- 3b35332 transformer/logical-assignment-operators: Fix semantic errors
(#5047) (Dunqing)- b7db235 Comments gen regression (#5003)
(IWANABETHATGUY)

### Documentation

- 178d1bd transformer: Add documentation for exponentiation-operator
plugin (#5084) (Dunqing)
- d50eb72 transformer: Add documentation for `optional-catch-binding`
plugin (#5064) (Dunqing)
- 4425b17 transformer: Add documentation for
`logical-assignment-operators` plugin (#5012) (Dunqing)
- 1bd5853 transformer: Updated README re: order of methods (#4993)
(overlookmotel)

### Refactor

- a4247e9 allocator: Move `Box` and `Vec` into separate files (#5034)
(overlookmotel)
- cca7440 ast: Replace `AstBuilder::move_statement_vec` with `move_vec`
(#4988) (overlookmotel)
- 4012260 ast: `AstBuilder::move_identifier_reference` do not allocate
empty string (#4977) (overlookmotel)
- 96422b6 ast: Make AstBuilder non-exhaustive (#4925) (DonIsaac)
- ca70cc7 linter, mangler, parser, semantic, transformer, traverse,
wasm: Rename various `flag` vars to `flags` (#5028) (overlookmotel)
- 0f64d10 minifier: Remove duplicated helper `move_out_expression`
(#5007) (IWANABETHATGUY)
- cd9cf5e oxc: Remove `remove_whitespace` (Boshen)
- b4407c4 oxc,mangler: `oxc` crate add mangler; mangler use options API
(Boshen)
- 9da6a21 semantic: Rename transform checker output for reference symbol
mismatches (#5091) (overlookmotel)
- fb46eaf semantic: Add remap functions to transform checker (#5082)
(overlookmotel)
- a00bf18 semantic: Add `IdMapping` to transform checker (#5079)
(overlookmotel)
- b14a302 semantic: Transform checker: change symbol name mismatch error
(#5075) (overlookmotel)
- b8c6ce5 semantic: Rename vars in transform checker (#5072)
(overlookmotel)
- 7156fd2 semantic: Transform checker `Pair` structure (#5053)
(overlookmotel)
- 0ba6f50 semantic: Simplify raising errors in transform checker (#5051)
(overlookmotel)
- ee7ac8b semantic: Store all data in `PostTransformChecker` in
transform checker (#5050) (overlookmotel)
- 4e1f4ab semantic: Add `SemanticIds` to transformer checker (#5048)
(overlookmotel)
- c1da574 semantic: Add comments to transformer checker (#5045)
(overlookmotel)
- 8cded08 semantic: Rename error labels in transformer checker snapshots
(#5044) (overlookmotel)
- 602244f semantic: Rename vars in transformer checker (#5043)
(overlookmotel)
- ae94b9a semantic: Remove unused function params in transformer checker
(#5042) (overlookmotel)
- 586e15c semantic: Reformat transform checker errors (#5039)
(overlookmotel)
- d69e34e semantic: Fix indentation (#5037) (overlookmotel)
- 4336a32 semantic: Rename fields in snapshots from `flag` to `flags`
(#5032) (overlookmotel)
- 83dfb14 semantic: Rename vars from `flag` to `flags` (#5031)
(overlookmotel)
- 3b7de18 semantic: Rename `SemanticBuilder::current_reference_flags`
field (#5027) (overlookmotel)
- 0bacdd8 semantic: Rename `Reference::flag` field to `flags` (#5026)
(overlookmotel)
- 896b92f semantic: Correct typo in doc comment (#5009) (overlookmotel)
- d677b8e semantic: Do not reserve space in `resolved_references`
(#4962) (overlookmotel)
- a7ef30d semantic: `UnresolvedReferencesStack` contain only
`ReferenceId` (#4960) (overlookmotel)
- 59d15c7 semantic: `root_unresolved_references` contain only
`ReferenceId` (#4959) (overlookmotel)
- 7706523 span: Clarify `Atom` conversion methods lifetimes (#4978)
(overlookmotel)
- 4fdf26d transform_conformance: Add driver (#4969) (Boshen)
- 8d15e65 transformer: Use `into_member_expression` (#5006)
(overlookmotel)
- 4796ece transformer: TS annotations transform use `move_expression`
(#4982) (overlookmotel)
- a9fcf29 transformer/es2016: Move all entry points to implementation of
Traverse trait (#5085) (Dunqing)
- deda6ac transformer/es2019: Move all entry points to implementation of
Traverse trait (#5065) (Dunqing)
- 9df2f80 transformer/es2020: Move all entry points to implementation of
Traverse trait (#4973) (Dunqing)
- 3f9433c transformer/es2021: Move all entry points to implementation of
Traverse trait (#5013) (Dunqing)
- c60a50d transformer/exponentiation-operator: Use built-in
`ctx.clone_identifier_reference` (#5086) (Dunqing)
- bcc8da9 transformer/logical-assignment-operator: Use
`ctx.clone_identifier_reference` (#5014) (Dunqing)
- 38d4434 transformer/nullish-coalescing-operator: Move internal methods
to bottom of the file (#4996) (Dunqing)

### Testing

- 0df1a94 semantic: Add more symbol and reference checks to
`PostTransformChecker` (Boshen)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2024-08-23 15:37:36 +08:00
..
examples feat(transformer): always pass in symbols and scopes (#5087) 2024-08-22 16:06:31 +00:00
src feat(traverse): add is_static method to TraverseCtx, which is moves out from SymbolTable (#5067) 2024-08-23 07:14:08 +00:00
Cargo.toml Release crates v0.25.0 (#5107) 2024-08-23 15:37:36 +08:00
CHANGELOG.md Release crates v0.25.0 (#5107) 2024-08-23 15:37:36 +08:00
README.md docs(transformer): updated README re: order of methods (#4993) 2024-08-20 04:43:47 +08:00

Oxc transformer

We mostly model Oxc's transforms on Babel's implementations.

First iteration of a transform will usually be as straight a port from Babel as possible. We may then iterate from there to gain better performance.

We import Babel's transformer tests, and aim to pass them all.

All transforms are implementations of the Traverse trait.

Composing transforms

We aim to run all transforms together in a single AST visitation pass.

This will be the most performant method, though it causes some complexity, especially due to interactions between different transforms acting on the same code. It is unclear at present if this methodology will be viable, but it is our initial aim, and we will only fall back to multiple passes if single-pass proves unworkable.

Style guide for implementing transforms

Transforms are complex. Please try to make the code as clear and easy to follow as possible.

NB: Not all the "rules" in this style guide are currently followed in transforms we've written so far. We will update those transforms to follow this guide when we have time. But all new transforms should follow this style guide closely.

Structure

Each transform should be in its own file.

Some transforms just delegate work to sub-transforms. e.g. React transform delegates to the ReactJsx and ReactDisplayName transforms.

Comments

For a maintainable and understandable codebase, please go big on code comments. The more, the merrier!

Top of file

Each transform should include a comment at top of file including:

  • High level explanation of what transform does.
  • One "before / after" example.
  • Link to Babel plugin.
  • Note of any ways in which our implementation diverges from Babel's, and why.

Methods

If it's a complicated transform with multiple visitors which interact with each other, add comments explaining how the pieces fit together.

Code snippets

AstBuilder calls are often very verbose. Preface each chunk of AstBuilder calls with a short comment showing what this code produces. e.g.:

// `let Foo;`
let declarations = {
    let ident = BindingIdentifier::new(SPAN, "Foo");
    let pattern_kind = self.ast.binding_pattern_identifier(ident);
    let binding = self.ast.binding_pattern(pattern_kind, None, false);
    let decl = self.ast.variable_declarator(SPAN, VariableDeclarationKind::Let, binding, None, false);
    self.ast.new_vec_single(decl)
};
let var_decl = Declaration::VariableDeclaration(self.ast.variable_declaration(
    SPAN,
    kind,
    declarations,
    Modifiers::empty(),
));

Where we can improve on Babel

Babel has less of an emphasis on performance than Oxc has. For this reason Babel's implementations are often not as efficient as they could be.

In some cases, we could do better, but we are unable to at present because a more efficient implementation would result in cosmetic differences between Oxc's output and Babel's (e.g. different variable names) which causes Babel's tests to fail when run on Oxc's output.

In future we may find a way to work around this problem.

So where we feel Babel's implementation is inefficient, but we have to follow it at present to pass their tests, make a // TODO(improve-on-babel): Babel's impl is inefficient because X, we could do better by Y comment, so we can return to it later.

Clear "entry points"

"Entry points" are where the visitor calls into the transform.

  • Entry points of transform should be implemented as impl Traverse for MyTransform.
  • Those methods have to be called enter_* and exit_*.
  • Parent transform will only interface with child transform via these entry points.
  • Only other method exposed externally should be new. That should be at top of the file.
  • Entry points go directly below new method definition.
  • Internal methods implemented lower down in an impl MyTransform block.
  • Internal methods named descriptively - add_id_to_function not transform_function.

i.e. File is laid out so logic flows from top of file to bottom.

e.g.:

struct FunctionRenamer {
    prefix: String,
}

// Initialization
impl FunctionRenamer {
    pub fn new(prefix: String) -> Self {
        Self { prefix }
    }
}

// Entry points
impl<'a> Traverse<'a> for FunctionRenamer {
    fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
        match stmt {
            Statement::FunctionDeclaration(func) => {
                self.rename_function(func, ctx);
            }
            Statement::ExportDefaultDeclaration(decl) => {
                if let ExportDefaultDeclarationKind::FunctionDeclaration(func) = &mut decl.declaration {
                    self.rename_function(func, ctx);
                }
            }
        }
    }
}

// Internal methods
impl FunctionRenamer {
    /// Rename the function
    // This function's name describes what it does, not just `transform_function`
    fn rename_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
        // Do stuff
    }
}

Encapsulate logic

All logic for each transform should live in that specific file, with no "leaking" into the parent transform. Each transform is only called into via the standard enter_*/exit_* entry points.

Only exception is that parent can check if child transform is enabled or not.

Bad! Don't do this.

Here some of logic from child transform is "leaked" into the parent:

// src/do_stuff/mod.rs
impl<'a> Traverse<'a> for ParentTransform {
    fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
        if self.child_is_enabled {
            match expr {
                Expression::JSXElement(e) => {
                    self.child.enter_jsx_element(e, ctx);
                }
                Expression::JSXFragment(e) => {
                    self.child.enter_jsx_fragment(e, ctx);
                }
                _ => {}
            }
        }
    }
}

// src/do_stuff/child.rs
impl<'a> Traverse<'a> for ChildTransform {
    fn enter_jsx_element(&mut self, elem: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) {
        // Do stuff
    }
    fn enter_jsx_fragment(&mut self, elem: &mut JSXFragment<'a>, ctx: &mut TraverseCtx<'a>) {
        // Do stuff
    }
}

Good!

All the child transform's logic is encapsulated in ChildTransform:

// src/do_stuff/mod.rs
impl<'a> Traverse<'a> for ParentTransform {
    fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
        if self.child_is_enabled {
            self.child.enter_expression(expr, ctx);
        }
    }
}

// src/do_stuff/child.rs
impl<'a> Traverse<'a> for ChildTransform {
    fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
        match expr {
            Expression::JSXElement(e) => {
                self.do_stuff_to_jsx_element(e, ctx);
            }
            Expression::JSXFragment(e) => {
                self.do_stuff_to_jsx_fragment(e, ctx);
            }
            _ => {}
        }
    }
}

impl ChildTransform {
    fn do_stuff_to_jsx_element(&mut self, elem: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) {
        // Do stuff
    }
    fn do_stuff_to_jsx_fragment(&mut self, elem: &mut JSXFragment<'a>, ctx: &mut TraverseCtx<'a>) {
        // Do stuff
    }
}