diff --git a/Cargo.lock b/Cargo.lock index fccdc8fe7be4..a143dfc53d54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6325,6 +6325,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "swc_ecma_transformer" +version = "0.1.0" +dependencies = [ + "serde", + "swc_common", + "swc_ecma_ast", + "swc_ecma_hooks", + "swc_ecma_visit", + "testing", +] + [[package]] name = "swc_ecma_transforms" version = "38.0.0" diff --git a/crates/swc_ecma_transformer/ARCHITECTURE.md b/crates/swc_ecma_transformer/ARCHITECTURE.md new file mode 100644 index 000000000000..a7e15db057ab --- /dev/null +++ b/crates/swc_ecma_transformer/ARCHITECTURE.md @@ -0,0 +1,327 @@ +# Architecture + +This document describes the architecture of `swc_ecma_transformer`, an oxc-inspired transformer implementation built on SWC's infrastructure. + +## Overview + +The `swc_ecma_transformer` crate provides a composable, hook-based architecture for JavaScript/TypeScript transformations. It draws inspiration from [oxc's transformer](https://github.com/oxc-project/oxc/tree/main/crates/oxc_transformer) design while adapting it to work with SWC's visitor patterns and infrastructure. + +## Core Concepts + +### Two-Trait Pattern: VisitMut + VisitMutHook + +Unlike oxc's single `Traverse` trait, SWC uses a two-trait pattern: + +1. **`VisitMut`** (from `swc_ecma_visit`): The base visitor trait that handles AST traversal +2. **`VisitMutHook`** (from `swc_ecma_hooks`): A composable hook trait with context passing + +This separation provides several benefits: +- Individual transforms can focus on specific node types using `VisitMutHook` +- The context type `C` can be customized per transformation pipeline +- Hooks can be composed using `CompositeHook` without manual visitor delegation +- The `VisitMutWithHook` adapter bridges between `VisitMut` and `VisitMutHook` + +### TraverseCtx: Transformation Context + +The `TraverseCtx` type serves a similar role to oxc's `TraverseCtx`, providing utilities for transformations: + +```rust +pub struct TraverseCtx<'a> { + unresolved_ctxt: SyntaxContext, + top_level_ctxt: SyntaxContext, + uid_counter: usize, + state: Option<&'a mut dyn std::any::Any>, +} +``` + +**Key features:** +- **Syntax Context Management**: Maintains `unresolved_ctxt` and `top_level_ctxt` for proper identifier resolution +- **Unique ID Generation**: `generate_uid_name()` creates unique temporary variable names +- **AST Building Helpers**: Methods like `create_ident_expr()`, `create_member_expr()`, `create_call_expr()` +- **User State**: Optional `state` field for transform-specific data + +**Comparison to oxc:** + +| oxc TraverseCtx | SWC TraverseCtx | Notes | +|----------------|-----------------|-------| +| `ancestry` field | Not included (yet) | Future: could add parent stack tracking | +| `scoping` field | `SyntaxContext` fields | SWC uses syntax contexts instead of explicit scope tree | +| `ast` builder | Helper methods | SWC doesn't use arena allocation, creates owned nodes | +| `generate_binding()` | `create_unresolved_ident()` | Different names, similar purpose | + +### Main Transformer + +The `Transformer` struct implements `VisitMut` and orchestrates all transformation hooks: + +```rust +pub struct Transformer { + options: TransformOptions, + unresolved_mark: Mark, + top_level_mark: Mark, +} + +impl VisitMut for Transformer { + fn visit_mut_program(&mut self, program: &mut Program) { + let mut ctx = self.create_context(); + + // Compose hooks based on options and call them in order + // (pseudocode - actual implementation pending) + let hook = build_hook_chain(&self.options); + let mut visitor = VisitMutWithHook { hook, context: ctx }; + program.visit_mut_with(&mut visitor); + } +} +``` + +## Module Organization + +The crate follows oxc's organizational structure: + +``` +src/ +├── lib.rs # Main Transformer struct, exports +├── context.rs # TraverseCtx implementation +├── options.rs # Configuration types +│ +├── es2015/ # ES2015 (ES6) transforms +├── es2016/ # ES2016 (ES7) transforms +├── es2017/ # ES2017 transforms +├── es2018/ # ES2018 transforms +├── es2019/ # ES2019 transforms +├── es2020/ # ES2020 transforms +├── es2021/ # ES2021 transforms +├── es2022/ # ES2022 transforms +├── es2026/ # Future ES2026 transforms +│ +├── jsx/ # JSX transformations +├── typescript/ # TypeScript transformations +├── decorator/ # Decorator transformations +├── regexp/ # RegExp transformations +│ +├── common/ # Shared utilities +├── proposals/ # Experimental proposals +└── utils/ # General utilities +``` + +### ES20XX Modules + +Each ES version module contains transforms for features introduced in that version: + +- **es2015**: Arrow functions, classes, template literals, destructuring, block scoping, for-of, spread, default/rest parameters +- **es2016**: Exponentiation operator +- **es2017**: Async functions +- **es2018**: Object rest/spread, async iteration, RegExp improvements +- **es2019**: Optional catch binding +- **es2020**: Optional chaining, nullish coalescing, BigInt, dynamic import +- **es2021**: Logical assignment operators, numeric separators +- **es2022**: Class fields, private methods/fields, static blocks, top-level await +- **es2026**: Future proposals + +### Feature Modules + +- **jsx**: Classic and automatic JSX transforms, React Refresh +- **typescript**: Type stripping, enums, namespaces, parameter properties +- **decorator**: Legacy and 2022-03 decorator proposals +- **regexp**: Sticky/unicode/dotAll flags, named capture groups, unicode property escapes + +### Infrastructure Modules + +- **common**: Shared transformation utilities +- **proposals**: Experimental JavaScript proposals not yet in spec +- **utils**: General-purpose helper functions + +## Transformation Order + +Transforms are applied in a specific order to ensure correctness: + +1. **TypeScript** (layer x0): Strip types, transform TS-specific syntax +2. **JSX** (layer x1): Transform JSX syntax +3. **Decorator** (layer x1): Transform decorators +4. **ES20XX** (layers x2-x4): Transform modern syntax to target version + - ES2022 → ES2021 → ES2020 → ... → ES2015 (reverse chronological order) +5. **RegExp** (layer x4): Transform RegExp features +6. **Common** (layer x4): Apply universal utilities + +This ordering mirrors oxc's layered approach and ensures that: +- Types are removed before syntax transformations +- Modern syntax is transformed step-by-step to older versions +- Feature detection and polyfills are added last + +## Hook Composition Pattern + +Individual transforms implement `VisitMutHook`: + +```rust +pub struct Es2015ArrowFunction { + // transform state +} + +impl VisitMutHook> for Es2015ArrowFunction { + fn enter_expr(&mut self, expr: &mut Expr, ctx: &mut TraverseCtx) { + if let Expr::Arrow(arrow) = expr { + // Transform arrow function to regular function + // Use ctx.generate_uid_name() for temp variables + // Use ctx.create_ident_expr() to build new AST nodes + } + } +} +``` + +Hooks are composed using `CompositeHook`: + +```rust +let es2015_hook = CompositeHook { + first: Es2015ArrowFunction::new(), + second: CompositeHook { + first: Es2015Classes::new(), + second: Es2015TemplateLiterals::new(), + }, +}; +``` + +The `VisitMutWithHook` adapter calls hooks before/after visiting children: + +```rust +let mut visitor = VisitMutWithHook { + hook: es2015_hook, + context: ctx, +}; +program.visit_mut_with(&mut visitor); +``` + +## SWC Integration + +### Syntax Contexts and Marks + +SWC uses `Mark` and `SyntaxContext` for hygiene and identifier resolution: + +- **`Mark`**: A unique stamp applied to scopes +- **`SyntaxContext`**: Combination of marks that identifies a scope chain + +The transformer maintains two marks: +- `unresolved_mark`: For references to global/unresolved identifiers +- `top_level_mark`: For top-level declarations + +These are applied to contexts in `TraverseCtx`: +```rust +let unresolved_ctxt = SyntaxContext::empty().apply_mark(self.unresolved_mark); +let top_level_ctxt = SyntaxContext::empty().apply_mark(self.top_level_mark); +``` + +### AST Ownership + +Unlike oxc (which uses arena allocation with `&'a Allocator`), SWC uses owned AST nodes: +- oxc: `Box<'a, Expr<'a>>` allocated in arena +- SWC: `Box` on the heap + +This means: +- No arena or lifetime parameters needed +- Slightly higher allocation overhead +- Simpler ownership model +- More familiar to Rust developers + +### Atom Usage + +When creating `Atom` instances for identifiers, follow SWC conventions: +- Prefer `&str` when possible: `sym: "foo".into()` +- Use `Cow` for conditional strings +- Avoid `String` unless necessary + +From CLAUDE.md: +> When creating Atom instances, it's better to use `Cow` or `&str` instead of `String`. Note that `&str` is better than `Cow` here. + +## Implementation Roadmap + +### Phase 1: Infrastructure (Current) +- [x] Create crate structure +- [x] Implement `TraverseCtx` +- [x] Define options types +- [x] Set up module directories +- [x] Implement main `Transformer` struct +- [x] Document architecture + +### Phase 2: Core ES2015 Transforms +- [ ] Arrow functions +- [ ] Classes +- [ ] Template literals +- [ ] Block scoping (let/const) +- [ ] Destructuring +- [ ] For-of loops +- [ ] Spread operator +- [ ] Default/rest parameters + +### Phase 3: Additional ES Versions +- [ ] ES2016 (exponentiation) +- [ ] ES2017 (async/await) +- [ ] ES2018 (object rest/spread, async iteration) +- [ ] ES2019 (optional catch binding) +- [ ] ES2020 (optional chaining, nullish coalescing) +- [ ] ES2021 (logical assignment) +- [ ] ES2022 (class fields, private members) + +### Phase 4: Feature Transforms +- [ ] JSX (classic and automatic) +- [ ] TypeScript (type stripping) +- [ ] Decorators +- [ ] RegExp transforms + +### Phase 5: Polish & Optimization +- [ ] Performance benchmarking +- [ ] Memory optimization +- [ ] Comprehensive test suite +- [ ] Integration with SWC CLI/API + +## Testing Strategy + +Each transform will include: + +1. **Unit tests**: Test individual transformation logic +2. **Integration tests**: Test composed transformation pipelines +3. **Fixture tests**: Compare output with expected transformed code +4. **Compatibility tests**: Verify behavior matches Babel/TypeScript/oxc where appropriate + +Example test structure: +```rust +#[test] +fn test_arrow_to_function() { + let input = "const fn = (x) => x * 2;"; + let expected = "const fn = function(x) { return x * 2; };"; + + let mut transformer = Transformer::new(TransformOptions { + target: EsVersion::Es5, + ..Default::default() + }); + + assert_transform(input, expected, &mut transformer); +} +``` + +## Performance Considerations + +Following SWC's performance-first philosophy (from CLAUDE.md): +> Write performant code. Always prefer performance over other things. + +Key strategies: +- Minimize allocations where possible +- Reuse AST nodes via `std::mem::replace` instead of cloning +- Use `&str` for Atom creation (faster than `String`) +- Avoid unnecessary traversals (only visit relevant node types) +- Benchmark against SWC's existing transforms + +## Future Enhancements + +Potential additions beyond initial port: + +1. **Ancestry Tracking**: Add parent stack to `TraverseCtx` like oxc +2. **Scope Analysis**: Integrate with SWC's scope analysis for better symbol resolution +3. **Parallel Transforms**: Explore parallel transform passes for independent transformations +4. **Plugin API**: Allow external hooks to be registered +5. **Incremental Transforms**: Only re-transform changed portions of AST + +## References + +- [oxc transformer source](https://github.com/oxc-project/oxc/tree/main/crates/oxc_transformer/src) +- [oxc Traverse trait](https://github.com/oxc-project/oxc/tree/main/crates/oxc_traverse/src) +- [SWC VisitMut API](https://rustdoc.swc.rs/swc_ecma_visit/trait.VisitMut.html) +- [SWC hooks crate](https://docs.rs/swc_ecma_hooks) +- [oxc transformer announcement](https://oxc.rs/blog/2024-09-29-transformer-alpha) diff --git a/crates/swc_ecma_transformer/Cargo.toml b/crates/swc_ecma_transformer/Cargo.toml new file mode 100644 index 000000000000..e3e9d0a7ec31 --- /dev/null +++ b/crates/swc_ecma_transformer/Cargo.toml @@ -0,0 +1,24 @@ +[package] +authors = ["강동윤 "] +description = "oxc-inspired composable transformer for ECMAScript" +documentation = "https://rustdoc.swc.rs/swc_ecma_transformer/" +edition = { workspace = true } +include = ["Cargo.toml", "src/**/*.rs"] +license = { workspace = true } +name = "swc_ecma_transformer" +repository = { workspace = true } +version = "0.1.0" + +[lib] +bench = false + +[dependencies] +serde = { workspace = true, features = ["derive"] } + +swc_common = { version = "17.0.1", path = "../swc_common" } +swc_ecma_ast = { version = "18.0.0", path = "../swc_ecma_ast", features = ["serde-impl"] } +swc_ecma_hooks = { version = "0.2.0", path = "../swc_ecma_hooks" } +swc_ecma_visit = { version = "18.0.1", path = "../swc_ecma_visit" } + +[dev-dependencies] +testing = { version = "18.0.0", path = "../testing" } diff --git a/crates/swc_ecma_transformer/src/common/mod.rs b/crates/swc_ecma_transformer/src/common/mod.rs new file mode 100644 index 000000000000..2b202bd2fe37 --- /dev/null +++ b/crates/swc_ecma_transformer/src/common/mod.rs @@ -0,0 +1,6 @@ +//! Common utilities shared across transformations. +//! +//! This module contains helper functions and utilities used by multiple +//! transformation modules. + +// TODO: Implement common utilities diff --git a/crates/swc_ecma_transformer/src/context.rs b/crates/swc_ecma_transformer/src/context.rs new file mode 100644 index 000000000000..93ae53262899 --- /dev/null +++ b/crates/swc_ecma_transformer/src/context.rs @@ -0,0 +1,201 @@ +//! Traversal context for the transformer. +//! +//! This module provides a context type that is passed through all +//! transformation hooks, similar to oxc's TraverseCtx. It provides utilities +//! for scope management, symbol resolution, and AST building. + +use swc_common::{Span, SyntaxContext, DUMMY_SP}; +use swc_ecma_ast::*; + +/// Traversal context passed through all transformation hooks. +/// +/// This context provides access to: +/// - Symbol and scope management +/// - AST building utilities +/// - Ancestry information +/// - User-defined state +/// +/// # Example +/// +/// ```ignore +/// impl<'a> VisitMutHook> for MyTransform { +/// fn enter_expr(&mut self, expr: &mut Expr, ctx: &mut TraverseCtx<'a>) { +/// // Use context for transformations +/// if let Expr::Ident(ident) = expr { +/// // Access scope information, generate new identifiers, etc. +/// } +/// } +/// } +/// ``` +pub struct TraverseCtx<'a> { + /// The syntax context for generating new nodes + unresolved_ctxt: SyntaxContext, + + /// The top-level syntax context + top_level_ctxt: SyntaxContext, + + /// Counter for generating unique identifiers + uid_counter: usize, + + /// User-defined state + state: Option<&'a mut dyn std::any::Any>, +} + +impl<'a> TraverseCtx<'a> { + /// Creates a new traversal context. + /// + /// # Arguments + /// + /// * `unresolved_ctxt` - The syntax context for unresolved references + /// * `top_level_ctxt` - The syntax context for top-level declarations + pub fn new(unresolved_ctxt: SyntaxContext, top_level_ctxt: SyntaxContext) -> Self { + Self { + unresolved_ctxt, + top_level_ctxt, + uid_counter: 0, + state: None, + } + } + + /// Gets the unresolved syntax context. + /// + /// This context is used for identifiers that are not resolved to any + /// specific scope, typically for global references or identifiers that + /// will be resolved later. + #[inline] + pub fn unresolved_ctxt(&self) -> SyntaxContext { + self.unresolved_ctxt + } + + /// Gets the top-level syntax context. + /// + /// This context is used for top-level declarations. + #[inline] + pub fn top_level_ctxt(&self) -> SyntaxContext { + self.top_level_ctxt + } + + /// Generates a unique identifier name. + /// + /// This is useful for creating temporary variables that don't conflict + /// with existing identifiers in the scope. + /// + /// # Arguments + /// + /// * `base` - The base name for the identifier (e.g., "temp") + /// + /// # Returns + /// + /// A unique identifier name like "temp_1", "temp_2", etc. + pub fn generate_uid_name(&mut self, base: &str) -> String { + self.uid_counter += 1; + format!("{}_{}", base, self.uid_counter) + } + + /// Creates a new identifier with the unresolved context. + /// + /// # Arguments + /// + /// * `sym` - The symbol name + /// * `span` - The source span + /// + /// # Returns + /// + /// An `Ident` with the unresolved syntax context. + pub fn create_unresolved_ident(&self, sym: &str, span: Span) -> Ident { + Ident { + span, + ctxt: self.unresolved_ctxt, + sym: sym.into(), + optional: false, + } + } + + /// Creates a new identifier reference expression. + /// + /// # Arguments + /// + /// * `sym` - The symbol name + /// * `span` - The source span + /// + /// # Returns + /// + /// An `Expr::Ident` with the unresolved syntax context. + pub fn create_ident_expr(&self, sym: &str, span: Span) -> Expr { + Expr::Ident(self.create_unresolved_ident(sym, span)) + } + + /// Creates a member expression accessing a property. + /// + /// # Arguments + /// + /// * `obj` - The object expression + /// * `prop` - The property name + /// + /// # Returns + /// + /// A member expression `obj.prop`. + pub fn create_member_expr(&self, obj: Box, prop: &str) -> Expr { + Expr::Member(MemberExpr { + span: DUMMY_SP, + obj, + prop: MemberProp::Ident(IdentName { + span: DUMMY_SP, + sym: prop.into(), + }), + }) + } + + /// Creates a call expression. + /// + /// # Arguments + /// + /// * `callee` - The callee expression + /// * `args` - The argument expressions + /// + /// # Returns + /// + /// A call expression `callee(args...)`. + pub fn create_call_expr(&self, callee: Expr, args: Vec) -> Expr { + Expr::Call(CallExpr { + span: DUMMY_SP, + callee: Callee::Expr(Box::new(callee)), + args, + ..Default::default() + }) + } + + /// Sets user-defined state in the context. + /// + /// This allows transforms to store and retrieve custom state during + /// traversal. + pub fn set_state(&mut self, state: &'a mut dyn std::any::Any) { + self.state = Some(state); + } + + /// Gets a reference to the user-defined state. + /// + /// # Returns + /// + /// `Some(&dyn Any)` if state was set, `None` otherwise. + pub fn state(&self) -> Option<&dyn std::any::Any> { + self.state.as_ref().map(|s| &**s as &dyn std::any::Any) + } + + /// Gets a mutable reference to the user-defined state. + /// + /// # Returns + /// + /// `Some(&mut dyn Any)` if state was set, `None` otherwise. + pub fn state_mut(&mut self) -> Option<&mut dyn std::any::Any> { + self.state + .as_mut() + .map(|s| &mut **s as &mut dyn std::any::Any) + } +} + +impl<'a> Default for TraverseCtx<'a> { + fn default() -> Self { + Self::new(SyntaxContext::empty(), SyntaxContext::empty()) + } +} diff --git a/crates/swc_ecma_transformer/src/decorator/mod.rs b/crates/swc_ecma_transformer/src/decorator/mod.rs new file mode 100644 index 000000000000..56b843f4db92 --- /dev/null +++ b/crates/swc_ecma_transformer/src/decorator/mod.rs @@ -0,0 +1,7 @@ +//! Decorator transformations. +//! +//! This module will contain transforms for decorator syntax, supporting: +//! - Legacy decorator proposal +//! - 2022-03 decorator proposal + +// TODO: Implement decorator transforms diff --git a/crates/swc_ecma_transformer/src/es2015/mod.rs b/crates/swc_ecma_transformer/src/es2015/mod.rs new file mode 100644 index 000000000000..09dd1dd2e1d7 --- /dev/null +++ b/crates/swc_ecma_transformer/src/es2015/mod.rs @@ -0,0 +1,15 @@ +//! ES2015 (ES6) transformations. +//! +//! This module will contain transforms for ES2015 features like: +//! - Arrow functions +//! - Classes +//! - Template literals +//! - Destructuring +//! - Block scoping (let/const) +//! - For-of loops +//! - Spread operator +//! - Default parameters +//! - Rest parameters +//! - And more + +// TODO: Implement ES2015 transforms diff --git a/crates/swc_ecma_transformer/src/es2016/mod.rs b/crates/swc_ecma_transformer/src/es2016/mod.rs new file mode 100644 index 000000000000..83c68d90bc42 --- /dev/null +++ b/crates/swc_ecma_transformer/src/es2016/mod.rs @@ -0,0 +1,6 @@ +//! ES2016 (ES7) transformations. +//! +//! This module will contain transforms for ES2016 features like: +//! - Exponentiation operator (**) + +// TODO: Implement ES2016 transforms diff --git a/crates/swc_ecma_transformer/src/es2017/mod.rs b/crates/swc_ecma_transformer/src/es2017/mod.rs new file mode 100644 index 000000000000..3ec4d0b2452f --- /dev/null +++ b/crates/swc_ecma_transformer/src/es2017/mod.rs @@ -0,0 +1,7 @@ +//! ES2017 transformations. +//! +//! This module will contain transforms for ES2017 features like: +//! - Async functions +//! - Trailing commas in function parameters + +// TODO: Implement ES2017 transforms diff --git a/crates/swc_ecma_transformer/src/es2018/mod.rs b/crates/swc_ecma_transformer/src/es2018/mod.rs new file mode 100644 index 000000000000..8d8c7d36e86b --- /dev/null +++ b/crates/swc_ecma_transformer/src/es2018/mod.rs @@ -0,0 +1,10 @@ +//! ES2018 transformations. +//! +//! This module will contain transforms for ES2018 features like: +//! - Object rest/spread +//! - Async iteration +//! - Promise.finally +//! - RegExp improvements (lookbehind, named capture groups, dotAll, unicode +//! property escapes) + +// TODO: Implement ES2018 transforms diff --git a/crates/swc_ecma_transformer/src/es2019/mod.rs b/crates/swc_ecma_transformer/src/es2019/mod.rs new file mode 100644 index 000000000000..c112bd04366c --- /dev/null +++ b/crates/swc_ecma_transformer/src/es2019/mod.rs @@ -0,0 +1,7 @@ +//! ES2019 transformations. +//! +//! This module will contain transforms for ES2019 features like: +//! - Optional catch binding +//! - JSON superset + +// TODO: Implement ES2019 transforms diff --git a/crates/swc_ecma_transformer/src/es2020/mod.rs b/crates/swc_ecma_transformer/src/es2020/mod.rs new file mode 100644 index 000000000000..31158e8d8804 --- /dev/null +++ b/crates/swc_ecma_transformer/src/es2020/mod.rs @@ -0,0 +1,11 @@ +//! ES2020 transformations. +//! +//! This module will contain transforms for ES2020 features like: +//! - Optional chaining (?.) +//! - Nullish coalescing (??) +//! - BigInt +//! - Dynamic import +//! - export * as ns +//! - import.meta + +// TODO: Implement ES2020 transforms diff --git a/crates/swc_ecma_transformer/src/es2021/mod.rs b/crates/swc_ecma_transformer/src/es2021/mod.rs new file mode 100644 index 000000000000..4c079f66d303 --- /dev/null +++ b/crates/swc_ecma_transformer/src/es2021/mod.rs @@ -0,0 +1,8 @@ +//! ES2021 transformations. +//! +//! This module will contain transforms for ES2021 features like: +//! - Logical assignment operators (&&=, ||=, ??=) +//! - Numeric separators +//! - WeakRef + +// TODO: Implement ES2021 transforms diff --git a/crates/swc_ecma_transformer/src/es2022/mod.rs b/crates/swc_ecma_transformer/src/es2022/mod.rs new file mode 100644 index 000000000000..d50d185a19fc --- /dev/null +++ b/crates/swc_ecma_transformer/src/es2022/mod.rs @@ -0,0 +1,9 @@ +//! ES2022 transformations. +//! +//! This module will contain transforms for ES2022 features like: +//! - Class fields +//! - Private methods and fields +//! - Static initialization blocks +//! - Top-level await + +// TODO: Implement ES2022 transforms diff --git a/crates/swc_ecma_transformer/src/es2026/mod.rs b/crates/swc_ecma_transformer/src/es2026/mod.rs new file mode 100644 index 000000000000..67d6c81c2ca3 --- /dev/null +++ b/crates/swc_ecma_transformer/src/es2026/mod.rs @@ -0,0 +1,5 @@ +//! ES2026 transformations (future spec). +//! +//! This module will contain transforms for future ES2026 features. + +// TODO: Implement ES2026 transforms diff --git a/crates/swc_ecma_transformer/src/jsx/mod.rs b/crates/swc_ecma_transformer/src/jsx/mod.rs new file mode 100644 index 000000000000..4b86d85535f6 --- /dev/null +++ b/crates/swc_ecma_transformer/src/jsx/mod.rs @@ -0,0 +1,8 @@ +//! JSX transformations. +//! +//! This module will contain transforms for JSX syntax, including: +//! - Classic JSX transform (React.createElement) +//! - Automatic JSX transform (react/jsx-runtime) +//! - React Refresh integration + +// TODO: Implement JSX transforms diff --git a/crates/swc_ecma_transformer/src/lib.rs b/crates/swc_ecma_transformer/src/lib.rs new file mode 100644 index 000000000000..7a5faac2b58d --- /dev/null +++ b/crates/swc_ecma_transformer/src/lib.rs @@ -0,0 +1,279 @@ +//! oxc-inspired composable transformer for ECMAScript. +//! +//! This crate provides a transformer architecture inspired by oxc, but built on +//! SWC's infrastructure. It uses the `VisitMutHook` pattern from +//! `swc_ecma_hooks` to compose multiple transformation passes in an organized, +//! layered manner. +//! +//! # Architecture +//! +//! The transformer is organized into modules that mirror oxc's structure: +//! +//! - **ES20XX modules** (es2015, es2016, ..., es2022, es2026): Transforms for +//! specific ECMAScript versions +//! - **Feature modules** (jsx, typescript, decorator, regexp): Language feature +//! transforms +//! - **Infrastructure modules** (common, proposals, utils): Shared utilities +//! and experimental features +//! +//! # Design +//! +//! Unlike oxc's single-pass `Traverse` trait, this implementation uses SWC's +//! two-trait pattern: +//! +//! - **`VisitMut`**: Implemented by the main `Transformer` struct, handles AST +//! traversal +//! - **`VisitMutHook`**: Implemented by individual transform +//! passes, receives `enter_*` and `exit_*` callbacks with context +//! +//! The `TraverseCtx` provides utilities similar to oxc's context: +//! - Syntax context management +//! - Unique identifier generation +//! - AST building helpers +//! +//! # Example +//! +//! ```ignore +//! use swc_ecma_transformer::{Transformer, TransformOptions}; +//! use swc_ecma_visit::VisitMutWith; +//! +//! let options = TransformOptions { +//! target: EsVersion::Es2015, +//! ..Default::default() +//! }; +//! +//! let mut transformer = Transformer::new(options); +//! let mut program = parse_code(); +//! program.visit_mut_with(&mut transformer); +//! ``` + +#![deny(clippy::all)] +#![allow(clippy::ptr_arg)] + +pub use swc_ecma_ast; +pub use swc_ecma_hooks; +pub use swc_ecma_visit; + +mod context; +mod options; + +// ES version-specific transforms +pub mod es2015; +pub mod es2016; +pub mod es2017; +pub mod es2018; +pub mod es2019; +pub mod es2020; +pub mod es2021; +pub mod es2022; +pub mod es2026; + +// Feature transforms +pub mod decorator; +pub mod jsx; +pub mod regexp; +pub mod typescript; + +// Infrastructure +pub mod common; +pub mod proposals; +pub mod utils; + +pub use context::TraverseCtx; +pub use options::*; +use swc_common::{Mark, SyntaxContext}; +use swc_ecma_ast::*; +use swc_ecma_visit::{VisitMut, VisitMutWith}; + +/// Main transformer struct that orchestrates all transformation passes. +/// +/// This struct implements `VisitMut` and coordinates multiple `VisitMutHook` +/// implementations that perform the actual transformations. The hooks are +/// called in a specific order to ensure correctness: +/// +/// 1. TypeScript transforms (type stripping, enums, etc.) +/// 2. JSX transforms +/// 3. Decorator transforms +/// 4. ES20XX transforms (in reverse chronological order: ES2022 -> ES2015) +/// 5. RegExp transforms +/// 6. Common transforms +/// +/// # Example +/// +/// ```ignore +/// let options = TransformOptions { +/// typescript: TypeScriptOptions { +/// enabled: true, +/// ..Default::default() +/// }, +/// jsx: JsxOptions { +/// enabled: true, +/// runtime: JsxRuntime::Automatic, +/// ..Default::default() +/// }, +/// target: EsVersion::Es2015, +/// ..Default::default() +/// }; +/// +/// let mut transformer = Transformer::new(options); +/// program.visit_mut_with(&mut transformer); +/// ``` +pub struct Transformer { + /// Transformation options + options: TransformOptions, + + /// Unresolved mark for generating unresolved references + unresolved_mark: Mark, + + /// Top-level mark for top-level declarations + top_level_mark: Mark, +} + +impl Transformer { + /// Creates a new transformer with the given options. + /// + /// # Arguments + /// + /// * `options` - Configuration options for the transformer + /// + /// # Example + /// + /// ```ignore + /// use swc_ecma_transformer::{Transformer, TransformOptions}; + /// + /// let transformer = Transformer::new(TransformOptions::default()); + /// ``` + pub fn new(options: TransformOptions) -> Self { + Self { + options, + unresolved_mark: Mark::new(), + top_level_mark: Mark::new(), + } + } + + /// Creates a new transformer with explicit marks. + /// + /// This is useful when you need to coordinate marks with other + /// transformations or when working with existing marked AST. + /// + /// # Arguments + /// + /// * `options` - Configuration options for the transformer + /// * `unresolved_mark` - Mark for unresolved references + /// * `top_level_mark` - Mark for top-level declarations + pub fn new_with_marks( + options: TransformOptions, + unresolved_mark: Mark, + top_level_mark: Mark, + ) -> Self { + Self { + options, + unresolved_mark, + top_level_mark, + } + } + + /// Gets the unresolved mark used by this transformer. + #[inline] + pub fn unresolved_mark(&self) -> Mark { + self.unresolved_mark + } + + /// Gets the top-level mark used by this transformer. + #[inline] + pub fn top_level_mark(&self) -> Mark { + self.top_level_mark + } + + /// Gets a reference to the transformation options. + #[inline] + pub fn options(&self) -> &TransformOptions { + &self.options + } + + /// Creates a traversal context for use with hooks. + fn create_context(&self) -> TraverseCtx { + let unresolved_ctxt = SyntaxContext::empty().apply_mark(self.unresolved_mark); + let top_level_ctxt = SyntaxContext::empty().apply_mark(self.top_level_mark); + TraverseCtx::new(unresolved_ctxt, top_level_ctxt) + } +} + +impl VisitMut for Transformer { + /// Visit and transform a Program node. + /// + /// This is the entry point for transformation. It creates a context and + /// orchestrates all enabled transformation hooks in the correct order. + fn visit_mut_program(&mut self, program: &mut Program) { + // Create the traversal context + let _ctx = self.create_context(); + + // TODO: Build and compose the actual hook chain based on options + // For now, we just traverse without any transforms + // This will be implemented as individual ES20XX and feature transforms are + // ported + + // Example of how hooks will be composed (commented out until transforms are + // implemented): let mut hook = CompositeHook { + // first: TypeScriptHook::new(&self.options.typescript), + // second: CompositeHook { + // first: JsxHook::new(&self.options.jsx), + // second: Es2015Hook::new(), + // }, + // }; + // + // let mut visitor = VisitMutWithHook { + // hook, + // context: ctx, + // }; + // + // program.visit_mut_with(&mut visitor); + + // For now, just traverse children + program.visit_mut_children_with(self); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_transformer_creation() { + ::testing::run_test(false, |_, _| { + let transformer = Transformer::new(TransformOptions::default()); + assert_eq!(transformer.options().target, EsVersion::Es5); + Ok(()) + }) + .unwrap(); + } + + #[test] + fn test_transformer_with_marks() { + ::testing::run_test(false, |_, _| { + let unresolved = Mark::new(); + let top_level = Mark::new(); + let transformer = + Transformer::new_with_marks(TransformOptions::default(), unresolved, top_level); + assert_eq!(transformer.unresolved_mark(), unresolved); + assert_eq!(transformer.top_level_mark(), top_level); + Ok(()) + }) + .unwrap(); + } + + #[test] + fn test_traverse_context_creation() { + ::testing::run_test(false, |_, _| { + let transformer = Transformer::new(TransformOptions::default()); + let ctx = transformer.create_context(); + let unresolved_ctxt = ctx.unresolved_ctxt(); + let top_level_ctxt = ctx.top_level_ctxt(); + + // Contexts should be different + assert_ne!(unresolved_ctxt, top_level_ctxt); + Ok(()) + }) + .unwrap(); + } +} diff --git a/crates/swc_ecma_transformer/src/options.rs b/crates/swc_ecma_transformer/src/options.rs new file mode 100644 index 000000000000..4c1d614af385 --- /dev/null +++ b/crates/swc_ecma_transformer/src/options.rs @@ -0,0 +1,144 @@ +//! Transformer options and configuration. +//! +//! This module defines the options for configuring the transformer, +//! similar to oxc's options structure. + +use serde::{Deserialize, Serialize}; +use swc_ecma_ast::EsVersion; + +/// Options for the transformer. +/// +/// These options control which transformations are enabled and their behavior. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransformOptions { + /// TypeScript transformation options + #[serde(default)] + pub typescript: TypeScriptOptions, + + /// JSX transformation options + #[serde(default)] + pub jsx: JsxOptions, + + /// ECMAScript version target + #[serde(default)] + pub target: EsVersion, + + /// Decorator transformation options + #[serde(default)] + pub decorator: DecoratorOptions, + + /// Regular expression transformation options + #[serde(default)] + pub regexp: RegExpOptions, +} + +/// TypeScript transformation options. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TypeScriptOptions { + /// Enable TypeScript transformations + #[serde(default)] + pub enabled: bool, + + /// Only strip types (don't transform TypeScript-specific syntax) + #[serde(default)] + pub only_remove_type_imports: bool, +} + +/// JSX transformation options. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JsxOptions { + /// Enable JSX transformations + #[serde(default)] + pub enabled: bool, + + /// JSX pragma (default: "React.createElement") + #[serde(default)] + pub pragma: Option, + + /// JSX pragma fragment (default: "React.Fragment") + #[serde(default)] + pub pragma_frag: Option, + + /// Use the new JSX transform + #[serde(default)] + pub runtime: JsxRuntime, + + /// Import source for the new JSX transform + #[serde(default)] + pub import_source: Option, + + /// Enable development mode (adds __source and __self) + #[serde(default)] + pub development: bool, + + /// Enable React refresh + #[serde(default)] + pub refresh: bool, +} + +/// JSX runtime mode. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum JsxRuntime { + /// Classic JSX transform (React.createElement) + #[default] + Classic, + /// Automatic JSX transform (react/jsx-runtime) + Automatic, +} + +/// Decorator transformation options. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DecoratorOptions { + /// Enable decorator transformations + #[serde(default)] + pub enabled: bool, + + /// Decorator version + #[serde(default)] + pub version: DecoratorVersion, +} + +/// Decorator proposal version. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum DecoratorVersion { + /// Legacy decorator proposal + Legacy, + /// 2022-03 decorator proposal + #[default] + V202203, +} + +/// Regular expression transformation options. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RegExpOptions { + /// Enable regular expression transformations + #[serde(default)] + pub enabled: bool, + + /// Transform sticky flag + #[serde(default)] + pub sticky_flag: bool, + + /// Transform unicode flag + #[serde(default)] + pub unicode_flag: bool, + + /// Transform dot-all flag + #[serde(default)] + pub dot_all_flag: bool, + + /// Transform named capture groups + #[serde(default)] + pub named_capture_groups: bool, + + /// Transform unicode property escapes + #[serde(default)] + pub unicode_property_escapes: bool, +} diff --git a/crates/swc_ecma_transformer/src/proposals/mod.rs b/crates/swc_ecma_transformer/src/proposals/mod.rs new file mode 100644 index 000000000000..384910d9e108 --- /dev/null +++ b/crates/swc_ecma_transformer/src/proposals/mod.rs @@ -0,0 +1,6 @@ +//! Experimental proposal transformations. +//! +//! This module will contain transforms for experimental JavaScript proposals +//! that are not yet part of the ECMAScript standard. + +// TODO: Implement proposal transforms diff --git a/crates/swc_ecma_transformer/src/regexp/mod.rs b/crates/swc_ecma_transformer/src/regexp/mod.rs new file mode 100644 index 000000000000..8fb013a7c0f5 --- /dev/null +++ b/crates/swc_ecma_transformer/src/regexp/mod.rs @@ -0,0 +1,10 @@ +//! Regular expression transformations. +//! +//! This module will contain transforms for RegExp features, including: +//! - Sticky flag (y) +//! - Unicode flag (u) +//! - DotAll flag (s) +//! - Named capture groups +//! - Unicode property escapes + +// TODO: Implement RegExp transforms diff --git a/crates/swc_ecma_transformer/src/typescript/mod.rs b/crates/swc_ecma_transformer/src/typescript/mod.rs new file mode 100644 index 000000000000..de9c4cdf9085 --- /dev/null +++ b/crates/swc_ecma_transformer/src/typescript/mod.rs @@ -0,0 +1,11 @@ +//! TypeScript transformations. +//! +//! This module will contain transforms for TypeScript-specific syntax, +//! including: +//! - Type annotation removal +//! - Enum transformations +//! - Namespace transformations +//! - Parameter properties +//! - Import/export type assertions + +// TODO: Implement TypeScript transforms diff --git a/crates/swc_ecma_transformer/src/utils/mod.rs b/crates/swc_ecma_transformer/src/utils/mod.rs new file mode 100644 index 000000000000..8303ef3f1b8e --- /dev/null +++ b/crates/swc_ecma_transformer/src/utils/mod.rs @@ -0,0 +1,6 @@ +//! General utility functions. +//! +//! This module contains general-purpose utility functions that may be used +//! throughout the transformer. + +// TODO: Implement utility functions