Fix class ID generation when class is bound to a variable#872
Conversation
🦋 Changeset detectedLatest commit: 391d1ac The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
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 |
🧪 E2E Test Results
⏳ Tests are running... Started at: 2026-02-03T07:19:32Z ❌ Some tests failed Summary
❌ Failed Tests🌍 Community Worlds (161 failed)mongodb (40 failed):
redis (40 failed):
starter (41 failed):
turso (40 failed):
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
❌ 🌍 Community Worlds
✅ 📋 Other
|
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
Pull request overview
This PR fixes a bug in the SWC plugin where class expressions bound to variables were incorrectly using the internal class name for registration instead of the variable (binding) name. This caused runtime errors when the internal class name was not accessible at module scope.
Changes:
- Added
current_class_binding_namefield to track the variable name when class expressions are assigned to variables - Modified
visit_mut_var_declto capture binding names for class expressions - Updated
visit_mut_class_exprto use binding name for registration instead of internal class name - Added comprehensive test cases covering both named and anonymous class expressions
- Updated documentation to explain the class expression binding name handling
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/swc-plugin-workflow/transform/src/lib.rs | Added binding name tracking field and logic to use variable names for class expression registration |
| packages/swc-plugin-workflow/transform/tests/fixture/class-expression-binding-name/input.js | Test input with class expressions using different binding and internal names |
| packages/swc-plugin-workflow/transform/tests/fixture/class-expression-binding-name/output-workflow.js | Expected output showing correct registration using binding names |
| packages/swc-plugin-workflow/transform/tests/fixture/class-expression-binding-name/output-step.js | Expected output for step mode (identical to workflow mode for class registration) |
| packages/swc-plugin-workflow/transform/tests/fixture/class-expression-binding-name/output-client.js | Expected output for client mode (identical to other modes for class registration) |
| packages/swc-plugin-workflow/spec.md | Documentation explaining how class expressions with binding names are handled |
| .changeset/great-clouds-move.md | Changeset entry documenting the bug fix |
Comments suppressed due to low confidence (1)
packages/swc-plugin-workflow/transform/src/lib.rs:6289
- There's a potential bug when a VarDecl contains multiple class expressions in separate declarators (e.g.,
var A = class ClassA {}, B = class ClassB {}). The current implementation setscurrent_class_binding_namein a loop over all declarators, then callsvisit_mut_children_withonce after the loop completes. This means the binding name will be set to the last class expression's binding name, and when visiting the first class expression, it will incorrectly use the last binding name.
To fix this, each declarator should be visited individually after setting the binding name, rather than visiting all declarators after the loop. Consider restructuring to set the binding name and immediately visit that specific declarator, or move the binding name tracking into the visit logic for each declarator separately.
Expr::Class(_) => {
// Track the binding name for class expressions like:
// var Bash = class _Bash {}
// The binding name (Bash) is what's accessible at module scope,
// not the internal class name (_Bash)
// We set the binding name here; it will be used when visit_mut_class_expr
// is called during visit_mut_children_with below
self.current_class_binding_name = Some(name.clone());
}
_ => {}
}
}
}
}
var_decl.visit_mut_children_with(self);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Merge activity
|
1973bae to
391d1ac
Compare
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express workflow with 1 step💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
|

Fixed class ID generation for class expressions bound to variables in the SWC plugin.
What changed?
How to test?
Test with code patterns like:
Verify that the generated code correctly uses the binding name "Bash" for registration rather than the internal name "_Bash".
Why make this change?
When a class expression is assigned to a variable, the internal class name is only accessible inside the class body, not at module level. Using the binding name ensures that the registration call references a symbol that's actually in scope at module level, preventing runtime errors when the code attempts to register the class for serialization.