-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
refactor(es/typescript): Convert TypeScript transforms to VisitMutHook pattern #11432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: cb17976 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Pull Request ReviewI've reviewed PR #11432 which refactors the TypeScript transforms to use the ✅ Strengths
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the TypeScript transforms to use the VisitMutHook pattern instead of the traditional VisitMut pattern, aligning with the architectural guidelines in AGENTS.md. The refactoring converts StripType and StripImportExport to implement VisitMutHook<()> and uses CompositeHook for composition.
Key changes:
- Converted visitor methods from
visit_mut_*toenter_*/exit_*hook callbacks - Introduced hook factory functions for creating visitor instances
- Made modules public to support external hook usage
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
crates/swc_ecma_transforms_typescript/src/typescript.rs |
Updated to use CompositeHook for composing strip_import_export and strip_type hooks with VisitMutWithHook |
crates/swc_ecma_transforms_typescript/src/strip_type.rs |
Converted from VisitMut to VisitMutHook<()> with enter_*/exit_* methods; removed two specialized visitor methods |
crates/swc_ecma_transforms_typescript/src/strip_import_export.rs |
Converted to VisitMutHook<()> with namespace depth tracking and separated processing logic into helper methods |
crates/swc_ecma_transforms_typescript/src/lib.rs |
Made strip_type and strip_import_export modules public; removed wildcard re-export |
crates/swc_ecma_transforms_typescript/Cargo.toml |
Added swc_ecma_hooks dependency |
Cargo.lock |
Reflected the new dependency in the lock file |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fn enter_ts_module_block(&mut self, _node: &mut TsModuleBlock, _ctx: &mut ()) { | ||
| self.in_namespace = true; | ||
| } | ||
|
|
||
| fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) { | ||
| fn exit_ts_module_block(&mut self, _node: &mut TsModuleBlock, _ctx: &mut ()) { | ||
| self.in_namespace = false; | ||
| } |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The namespace tracking logic doesn't properly handle nested namespaces. When entering a nested TsModuleBlock, in_namespace is unconditionally set to true, but when exiting, it's unconditionally set to false. This means that if we have nested namespaces, exiting the inner namespace will incorrectly mark the outer namespace as not being in a namespace.
The original code used mem::replace to save and restore the previous state. The hook-based pattern should track depth instead (similar to how StripImportExport does with namespace_depth), or save/restore the previous value of in_namespace.
Pull Request Review: Refactor TypeScript transforms to VisitMutHook patternSummaryThis PR successfully refactors ✅ Strengths1. Architectural Alignment
2. Code Quality
3. Namespace Handling ImprovementsThe namespace depth tracking is a notable improvement: fn enter_ts_module_block(&mut self, _node: &mut TsModuleBlock, _ctx: &mut ()) {
self.namespace_depth += 1;
}
fn exit_ts_module_block(&mut self, _node: &mut TsModuleBlock, _ctx: &mut ()) {
self.namespace_depth -= 1;
}This is more robust than the previous
|
Code Review: TypeScript Transforms Hook RefactoringSummaryThis PR successfully refactors the TypeScript transforms to follow the architectural guidelines in ✅ Strengths
|
Pull Request ReviewSummaryThis PR refactors TypeScript transforms to use the ✅ Strengths
|
Binary Sizes
Commit: 3c45ca3 |
CodSpeed Performance ReportMerging this PR will not alter performanceComparing Summary
|
Code Review for PR #11432SummaryThis PR refactors the TypeScript transforms to use the 🔴 Critical Issues1. Duplicated Code in
|
Pull Request Review: TypeScript Transform RefactoringSummaryThis PR successfully refactors the TypeScript transforms to follow the ✅ StrengthsArchitecture & Design
Code Quality
Performance Considerations
|
Code Review SummaryI've reviewed PR #11432 which refactors TypeScript transforms to use the ✅ StrengthsArchitecture & Design
Code Quality
|
Pull Request Review: TypeScript Transform Hook Pattern RefactoringOverviewThis PR refactors the TypeScript transforms to use the Positive AspectsArchitectural Alignment ✓
Code Quality ✓
Issues and Concerns1. CRITICAL: Large Generated File Added (hook_utils.rs - 2,482 lines)Location: Issue: This PR adds a 2,482-line generated file Problems:
Recommendation: // Instead of duplicating 2,482 lines, consider:
// 1. Add VisitMutImpl to swc_ecma_hooks crate with maybe_grow_default
// 2. Or create a macro/trait that applies stack growth to any VisitMut implementation
// 3. Or use the existing VisitMutWithHook and wrap specific methods that need stack growth
// Example approach:
use swc_ecma_hooks::VisitMutWithHook;
use swc_ecma_utils::stack_size::maybe_grow_default;
struct StackSafeVisitor<H, C> {
inner: VisitMutWithHook<H, C>,
}
impl<H: VisitMutHook<C>, C> VisitMut for StackSafeVisitor<H, C> {
fn visit_mut_expr(&mut self, node: &mut Expr) {
maybe_grow_default(|| self.inner.visit_mut_expr(node));
}
// Only override methods that need stack growth
}2. Namespace Depth Tracking LogicLocation: Issue: The Current Code: fn enter_ts_module_block(&mut self, _node: &mut TsModuleBlock, _ctx: &mut ()) {
self.ts_module_depth += 1;
}
fn exit_ts_module_block(&mut self, _node: &mut TsModuleBlock, _ctx: &mut ()) {
self.ts_module_depth -= 1;
}Concerns:
Recommendation: // Add documentation
/// Depth of TypeScript module/namespace nesting.
/// 0 = top-level, increments for each nested namespace
ts_module_depth: usize,
// Or use a guard pattern:
struct DepthGuard<'a>(&'a mut usize);
impl<'a> DepthGuard<'a> {
fn new(depth: &'a mut usize) -> Self {
*depth += 1;
Self(depth)
}
}
impl Drop for DepthGuard<'_> {
fn drop(&mut self) {
*self.0 -= 1;
}
}3. Processing Order in enter_module_itemsLocation: Issue: The comment says "Process module items BEFORE children are visited" but the depth check logic could be more explicit. fn enter_module_items(&mut self, n: &mut Vec<ModuleItem>, _ctx: &mut ()) {
if self.verbatim_module_syntax {
return;
}
// Only process top-level
// Nested namespaces are handled by process_all_nested_modules
if self.ts_module_depth == 0 { // ⚠️ Should this be strictly 0?
self.process_top_level_module_items(n);
}
}Questions:
Recommendation: Add more detailed comments explaining the processing phases and why certain depths are skipped. 4. Performance Consideration: Nested Module ProcessingLocation: Issue: The code processes nested modules recursively in two passes: fn process_top_level_module_items(&mut self, n: &mut Vec<ModuleItem>) {
// Process nested TsModules first
for module_item in n.iter_mut() {
if let ModuleItem::Stmt(Stmt::Decl(Decl::TsModule(ts_module))) = module_item {
if let Some(TsNamespaceBody::TsModuleBlock(ref mut block)) = ts_module.body {
self.process_all_nested_modules(&mut block.body); // Recursive
}
}
}
// Then process this level...
}Concern:
Recommendation: Profile with deeply nested namespaces. Consider a single-pass algorithm if performance becomes an issue. 5. Memory Usage: UsageCollect and DeclareCollectLocation: Issue: Both fn enter_module(&mut self, n: &mut Module, _ctx: &mut ()) {
if self.verbatim_module_syntax {
return;
}
n.visit_with(&mut self.usage_info); // Full traversal
n.visit_with(&mut self.declare_info); // Full traversal
self.usage_info.analyze_import_chain();
}Concern:
Recommendation:
6. Error Handling: Unwrap in strip_type.rsLocation: fn enter_simple_assign_target(&mut self, n: &mut SimpleAssignTarget, _ctx: &mut ()) {
while let SimpleAssignTarget::TsAs(TsAsExpr { expr, .. })
// ... other patterns
{
*n = expr.take().try_into().unwrap(); // ⚠️ Panic risk
}
}Issue: Uses Recommendation: // Add a comment explaining why this is safe:
// SAFETY: Expr can always convert to SimpleAssignTarget in these cases
// because we're only stripping type assertion wrappers
*n = expr.take().try_into().unwrap();
// Or use expect with a message:
*n = expr.take().try_into()
.expect("Type assertion should always contain valid SimpleAssignTarget");Testing RecommendationsMissing from PR:
Test Commands to Run: # As mentioned in AGENTS.md for this crate
cd crates/swc_ecma_transforms_typescript
./scripts/test.sh
# If execution tests exist
./scripts/exec.shSecurity Considerations✓ No security issues identified. The refactoring doesn't introduce:
Best Practices per CLAUDE.md✓ Followed:
|
|
@magic-akari I'm working to merge the whole typescript pass into a single visit using |
This isn’t specifically about this PR, but about the broader direction of this migration. For context, this split-visit design was something I originally suggested, so this is more of a retrospective concern on my side. Over time, I’ve found that the oxc-style pattern of splitting a single visit into two parts can feel ergonomically awkward and tends to make code review harder. I’ve experimented with a few alternative approaches and suspect there may be cleaner solutions, although I haven’t landed on a concrete replacement yet. I’m curious what your thoughts are on this design today. If better approaches emerge, would we be open to revisiting or evolving this direction? |
|
Yeap, of course. My primary concern of this migration is the performance overhead of the visitor pattern. If there's a better alternative, I'm 100% open to migrating to it. Beside it, I agree that this kind of visitor hook system has bad impact on code review. It has far more interactions between passes compared to standalone visitors, and that's why I'm trying to do it gradually, but doing it gradually wasn't enough for code review. |
I was initially expecting this migration to bring some performance improvements, but looking at the Codespeed results, there's no measurable change. I'm curious to understand your specific concerns about the visitor pattern overhead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
|
I'll try merging more visitors in this PR to see if it really improves the performance. |
…isitMutHook Refactored StripType and StripImportExport from VisitMut to VisitMutHook-based implementations and combined them using CompositeHook. This allows a single visit_mut_with call instead of two sequential calls. Changes: - Converted StripType to implement VisitMutHook<()> - Converted StripImportExport to implement VisitMutHook<()> - Added swc_ecma_hooks dependency to swc_ecma_transforms_typescript - Combined hooks using CompositeHook in typescript.rs - Changed from visit_mut_with calls to CompositeHook pattern Note: 189/190 tests pass. The namespace_005 test needs investigation due to differences in how hooks handle module_items traversal. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
…xport Fixed StripImportExport to work correctly with VisitMutHook by properly handling nested namespaces. The key changes: - Added namespace_depth tracking to distinguish top-level from nested - Top-level processing handles TsModules by recursively processing nested bodies - Removed all VisitMut implementations (StripTsImportEquals) - Hook system now automatically handles all levels through enter_module_items All tests now pass: - swc_ecma_transforms_typescript: 5226 tests - swc_ecma_transforms_react: 268 tests - swc --test tsc: 4580 tests - swc --test projects: 861 tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email protected]>
Refactor TypeScript transforms to use VisitMutHook pattern.
Pull Request Review: TypeScript Transform Refactoring to VisitMutHook PatternOverviewThis PR successfully refactors the TypeScript transforms to follow the VisitMutHook pattern as outlined in Code Quality & Best Practices✅ Strengths
|
|
No dependency changes detected. Learn more about Socket for GitHub. 👍 No dependency changes detected in pull request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 6 out of 8 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #![allow(clippy::mutable_key_type)] | ||
|
|
||
| pub use self::{strip_type::*, typescript::*}; | ||
| pub use self::typescript::*; |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The removal of pub use self::{strip_type::*, ...} from the public API represents a breaking change. Previously, items from strip_type module were re-exported, but now only items from typescript module are re-exported.
This is likely intentional as part of the refactoring to make the hook-based implementation internal, but it should be clearly noted as a breaking API change. Users who were directly using strip_type() or other exported items from that module will need to update their code.
| pub use self::typescript::*; | |
| pub use self::{strip_type::*, typescript::*}; |
Pull Request Review: TypeScript Transforms to VisitMutHook PatternSummaryThis PR refactors the TypeScript transforms ( ✅ Strengths
|
Summary
StripTypeandStripImportExportto implementVisitMutHook<()>instead ofVisitMutTypeScripttransform to useCompositeHookandVisitMutWithHookfor compositionstrip_typeandstrip_import_exportmodules public for hook usageStripImportExportto properly track depth and process nested modulesTechnical Details
This refactoring aligns with the architectural guidelines in
crates/swc_ecma_transformer/AGENTS.md:VisitMutHook<TraverseCtx>instead ofVisitMutCompositeHookand executed viaVisitMutWithHookTransformerorchestrates hook executionKey changes:
StripType: Converted allvisit_mut_*methods toenter_*andexit_*hook callbacksStripImportExport: Properly handles namespace depth tracking and processes module items before childrenCompositeHookto chainstrip_import_export::hook()andstrip_type::hook()Test plan
🤖 Generated with Claude Code