oxc/crates/oxc_transformer
oxc-bot de107246c8
release(crates): v0.36.0 (#7227)
## [0.36.0] - 2024-11-09

- b11ed2c ast: [**BREAKING**] Remove useless `ObjectProperty::init`
field (#7220) (Boshen)

- 0e4adc1 ast: [**BREAKING**] Remove invalid expressions from
`TSEnumMemberName` (#7219) (Boshen)

- 846711c transformer: [**BREAKING**] Change API to take a
`&TransformOptions` instead of `TransformOptions` (#7213) (Boshen)

- 092de67 types: [**BREAKING**] Append `rest` field into `elements` for
objects and arrays to align with estree (#7212) (ottomated)

- d1d1874 ast: [**BREAKING**] Change `comment.span` to real position
that contain `//` and `/*` (#7154) (Boshen)

- 843bce4 ast: [**BREAKING**] `IdentifierReference::reference_id` return
`ReferenceId` (#7126) (overlookmotel)

### Features

- cc8a191 ast: Methods on AST nodes to get `scope_id` etc (#7127)
(overlookmotel)
- dc0215c ast_tools: Add #[estree(append_to)], remove some custom
serialization code (#7149) (ottomated)
- 9d6cc9d estree: ESTree compatibility for all literals (#7152)
(ottomated)
- b74686c isolated-declarations: Support transform TSExportAssignment
declaration (#7204) (Dunqing)
- ad3a2f5 tasks/compat_data: Generate our own compat table (#7176)
(Boshen)
- b4258ee transformer: Add defaulted `Module::Preserve` option (#7225)
(Boshen)
- 324c3fe transformer: Add `TransformOptions::module` option (#7188)
(Boshen)
- a166a4a transformer: Add esbuild comma separated target API
`--target=es2020,chrome58` (#7210) (Boshen)
- 3a20b90 transformer: Add es target to `engineTargets` (#7193) (Boshen)
- 22898c8 transformer: Warn BigInt when targeting < ES2020 (#7184)
(Boshen)
- a579011 transformer: Add features `ES2018NamedCapturingGroupsRegex`
and `ES2018LookbehindRegex` (#7182) (Boshen)
- 8573f79 transformer: Turn on async_to_generator and
async_generator_functions plugins in enable_all (#7135) (Dunqing)
- df77241 transformer: Enable `ArrowFunctionConverter` in
`async-to-generator` and `async-generator-functions` plugins (#7113)
(Dunqing)
- b6a5750 transformer/arrow-function-converter: Move scope to changed
scope for `this_var` if scope have changed (#7125) (Dunqing)
- 1910227 transformer/async-to-generator: Support inferring the function
name from the ObjectPropertyValue's key (#7201) (Dunqing)
- ffa8604 transformer/async-to-generator: Do not transform await
expression if is not inside async function (#7138) (Dunqing)
- e536d47 transformer/babel: Add support for trying to get the `Module`
from `BabelPlugins` (#7218) (Dunqing)
- 5cfdc05 transformer/typescript: Support transform `export =` and
`import = require(...)` when module is commonjs (#7206) (Dunqing)

### Bug Fixes

- c82b273 transformer/async-generator-functions: Only transform object
method in exit_function (#7200) (Dunqing)
- b2a888d transformer/async-generator-functions: Incorrect
transformation for `for await` if it's not placed in a block (#7148)
(Dunqing)
- 19892ed transformer/async-generator-functions: Transform incorrectly
for `for await` if it's in LabeledStatement (#7147) (Dunqing)
- ede10dc transformer/async-to-generator: Incorrect transform when super
expression is inside async method (#7171) (Dunqing)
- 293d072 transformer/async-to-generator: Only transform object method
in exit_function (#7199) (Dunqing)
- ae692d7 transformer/async_to_generator: Fix checking if function is
class method (#7117) (overlookmotel)
- eea4ab8 transformer/helper-loader: Incorrect `SymbolFlags` for default
import when `SourceType` is script (#7226) (Dunqing)

### Refactor

- d27e14f ast: `AstKind::as_*` methods take `self` (#5546)
(overlookmotel)
- fac5042 ast: Use `scope_id` etc methods (#7130) (overlookmotel)
- a297765 minifier: Use `map` and `and_then` instead of let else (#7178)
(7086cmd)
- fc86703 napi/transform: Change test files to TypeScript (#7221)
(Boshen)
- c5485ae semantic: Add `ancestor_kinds` iterator function (#7217)
(camchenry)
- abf1602 semantic: Rename `iter_parents` to `ancestors` (#7216)
(camchenry)
- 42171eb semantic: Rename `ancestors` to `ancestor_ids` (#7215)
(camchenry)
- de56083 transformer: Add `impl TryFrom<EngineTargets> for EnvOptions`
(#7191) (Boshen)
- 0a43c64 transformer: Move `ESTarget` to its own file (#7189) (Boshen)
- 0e1f12c transformer: Remove unimplemented `EnvOptions::bugfixes`
(#7162) (Boshen)
- a981caf transformer: Add `Engine` enum for `EngineTargets` (#7161)
(Boshen)
- 8340243 transformer: Rename `Query` to `BrowserslistQuery` (#7143)
(Boshen)
- 481f7e6 transformer: Change `Targets` to `EngineTargets` (#7142)
(Boshen)
- 55e6989 transformer: Deserialize engine target strings to specific
keys (#7139) (Boshen)
- fdfd9a4 transformer: Use `scope_id` etc methods (#7128)
(overlookmotel)
- ff8bd50 transformer: Move implementation of ArrowFunction to
common/ArrowFunctionConverter (#7107) (Dunqing)
- 4a515be transformer/arrow-function-coverter: Rename function name and
add some comments to explain confusing parts. (#7203) (Dunqing)
- c307e1b transformer/arrow-functions: Pass `ArenaBox` as function param
(#7169) (overlookmotel)
- 217d433 transformer/arrow-functions: Remove unused `&mut self`
function param (#7165) (overlookmotel)
- 426df71 transformer/arrow-functions: Use `scope_id` method (#7164)
(overlookmotel)
- 11c5e12 transformer/arrow-functions: Correct comments (#7163)
(overlookmotel)
- 1238506 transformer/async-generator-function: Remove inactive
`#[allow(clippy::unused_self)]` attrs (#7167) (overlookmotel)
- 84ee581 transformer/async-generator-functions: Simplify identifying
whether within an async generator function (#7170) (overlookmotel)
- 1b12328 transformer/async-generator-functions: Use `clone` not
`clone_in` on `LabelIdentifier` (#7172) (overlookmotel)
- cd1006f transformer/async-generator-functions: Do not transform yield
expression where inside generator function (#7134) (Dunqing)
- 2c5734d transformer/async-generator-functions: Do not transform await
expression where inside ArrowFunctionExpression (#7132) (Dunqing)
- 5ce83bd transformer/async-generator-functions: Remove dead code for
handle await expression (#7131) (Dunqing)
- e04ee97 transformer/async-generator-functions: Move handling of
`MethodDefinition`'s value to `exit_function` (#7106) (Dunqing)
- b57d5a5 transformer/async-to-generator: Remove unused `&self` function
param (#7166) (overlookmotel)
- f80085c transformer/async-to-generator: Move handling of
`MethodDefinition`'s value to `exit_function` (#7105) (Dunqing)
- e2241e6 transformer/jsx-self: Remove unused `&self` function params
(#7159) (overlookmotel)
- 1dfd241 transformer/optional-catch-binding: Remove inactive
`#[allow(clippy::unused_self)]` attr (#7158) (overlookmotel)
- fd9b44c transformer/typescript: Remove inactive
`#[allow(clippy::unused_self)]` attr (#7160) (overlookmotel)
- cacfb9b traverse: Use `symbol_id` etc methods (#7129) (overlookmotel)

### Styling

- 38a6df6 transformer/arrow-functions: Semicolon after return statements
(#7168) (overlookmotel)
- 64b7e3a transformer/async-generator-functions: Import
`oxc_allocator::Vec` as `ArenaVec` (#7173) (overlookmotel)

### Testing

- be819dd napi/transform: Add test for not default es transform (Boshen)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2024-11-10 00:31:14 +08:00
..
examples feat(transformer)!: change API to take a &TransformOptions instead of TransformOptions (#7213) 2024-11-09 06:01:13 +00:00
src fix(transformer/helper-loader): incorrect SymbolFlags for default import when SourceType is script (#7226) 2024-11-09 14:32:44 +00:00
tests/integrations feat(transformer)!: change API to take a &TransformOptions instead of TransformOptions (#7213) 2024-11-09 06:01:13 +00:00
Cargo.toml release(crates): v0.36.0 (#7227) 2024-11-10 00:31:14 +08:00
CHANGELOG.md release(crates): v0.36.0 (#7227) 2024-11-10 00:31:14 +08:00
README.md chore: use dprint to format js, json and markdown 2024-09-08 13:24:58 +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
    }
}