feat(flags): Support indirect destructuring and property access patterns#583
feat(flags): Support indirect destructuring and property access patterns#583
Conversation
Extends the build-time transformer to support multiple ways of accessing feature flags:
**New Patterns Supported:**
1. Indirect destructuring: `const flags = useFlags(); const { featureA } = flags;`
2. Property access: `const flags = useFlags(); if (flags.featureA) {}`
3. Mixed patterns: All patterns can be used together in the same file
**Implementation:**
- Added `FlagObjectInfo` struct and `flag_object_map` to track flag object variables
- Created `extract_flags_from_object_pattern()` method for shared destructuring logic
- Enhanced `analyze_declarator()` to detect flag object bindings and indirect destructuring
- Extended `visit_mut_expr()` to transform member expressions (`flags.featureA` → `__SWC_FLAGS__.featureA`)
- Smart declaration removal: Only removes when flags are actually transformed
**Test Coverage:**
- Added 5 new test fixtures with 10 files total
- Tests cover indirect destructuring, property access, mixed patterns, scope safety, and exclude flags
- All existing tests continue to pass (backward compatible)
**Test Infrastructure:**
- Added support for per-test `options.json` configuration files
- Added `serde_json` as dev dependency for test configuration parsing
**Documentation:**
- Updated README.md with "Supported Usage Patterns" section
- Added examples for all three pattern types
All patterns transform identically and maintain scope safety using SWC's Id system.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 0579961 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 |
Code Review: PR #583 - Support indirect destructuring and property access patternsSummaryThis PR extends the ✅ Strengths1. Excellent Code Quality
2. Robust Implementation
3. Comprehensive Testing
4. Documentation
🔍 Observations & Suggestions1. Potential Edge Case: Nested Destructuring (Low Priority)The current implementation may not handle deeply nested destructuring: const flags = useExperimentalFlags();
const { nested: { featureA } } = flags; // May not be transformedHowever, this is likely an acceptable limitation given typical usage patterns. Consider documenting this limitation if it's intentional. 2. Member Expression Edge Case (Low Priority)In
These are edge cases that may not need support, but worth considering for completeness. 3. Code Organization (Minor)The implementation at lines 152-174 has a comment about avoiding borrow checker issues by extracting // Alternative approach (optional)
if let Pat::Object(obj_pat) = &declarator.name {
if let Some(init) = &declarator.init {
if let Expr::Ident(ident) = &**init {
let source_id = ident.to_id();
if let Some(info) = self.flag_object_map.get(&source_id) {
let source_span = info.span_lo;
let extracted = self.extract_flags_from_object_pattern(obj_pat);
if extracted {
self.declarators_to_remove.insert(declarator.span.lo.0);
self.declarators_to_remove.insert(source_span);
}
}
}
}
}4. Test Infrastructure (Positive)The addition of 🔒 Security Considerations✅ No security concerns identified:
⚡ Performance Considerations✅ Performance impact is minimal and well-designed:
🧪 Test Coverage✅ Excellent test coverage:
All 10 tests pass successfully. 📋 Best Practices Compliance✅ Follows Rust/SWC best practices:
🎯 RecommendationAPPROVE with minor suggestions ✅ This is a high-quality PR that:
The implementation is production-ready. The suggestions above are minor enhancements that could be addressed in future PRs if needed. 💬 Questions for Author
Great work on this feature! The code quality and testing are exemplary. 🎉 Review generated by Claude Code |
|
We don't need a feature to exclude a feature flag. @claude Discard changes related to 'exclude' feature |
|
Claude finished @kdy1's task in 4m 57s —— View job Completed: Exclude feature removedSuccessfully removed all exclude flags functionality from the PR while preserving the core indirect destructuring and property access patterns feature. Changes MadeRemoved:
Preserved:
Verification
--- | Branch: kdy1/more-dce |
Remove the exclude flags functionality as it's not needed. This change: - Removes exclude-flags test fixture and related test infrastructure - Removes exclude_flags field from config structures - Removes exclude checks from build_time.rs - Removes serde_json dev dependency - Updates documentation to remove exclude mentions All core functionality (indirect destructuring, property access) is preserved. All tests pass. Co-authored-by: Donny/강동윤 <kdy1@users.noreply.github.com>
|
|
|
@claude Fix CI |
|
Claude finished @kdy1's task in 5m 53s —— View job CI Fixed ✅What Was WrongThe CI was failing because when the Changes Made
Verification
The CI should now pass once the new workflow run completes. --- | Branch: kdy1/more-dce |
Pull Request Review: Feature Flags - Support for Indirect Destructuring and Property AccessSummaryThis PR extends the ✅ Strengths1. Strong Architecture
2. Excellent Test Coverage
3. Backward Compatibility
4. Documentation
🔍 Code Quality Observationsbuild_time.rs (lines 65-106)Good:
Minor Concern: build_time.rs (lines 108-165)Issue - Potential Bug in Indirect Destructuring: In Pattern 3 (lines 142-164), when handling indirect destructuring like self.declarators_to_remove.insert(declarator.span.lo.0);
self.declarators_to_remove.insert(source_span);Problem: If Potential Edge Case: const flags = useExperimentalFlags();
const { featureA } = flags; // First usage - marks flags for removal
const { featureB } = flags; // Second usage - tries to mark again (already in set)
console.log(flags.featureC); // Third usage - also marks for removalThis should work fine since build_time.rs (lines 245-261)Potential Issue - Member Expression Handling: When transforming const flags = useExperimentalFlags();
const x = flags.unknownProperty; // This WILL be transformed
const y = flags.toString(); // This too!Recommendation: Consider whether you want to validate against a known set of flags, or document this behavior clearly. The current approach is permissive (transforms all property accesses), which could be intentional for flexibility. config.rs - Breaking Change ConsiderationThe removal of Before: pub struct BuildTimeConfig {
pub libraries: HashMap<String, LibraryConfig>,
pub exclude_flags: Vec<String>, // REMOVED
pub marker_object: String,
}Impact:
Recommendation: Add a deprecation note to the documentation explaining that 🐛 Potential Bugs1. Multiple Flag Object Declarations with Same NameScenario: function outer() {
const flags = useExperimentalFlags();
if (flags.featureA) {
function inner() {
const flags = useExperimentalFlags(); // Different flags, same name
if (flags.featureB) {
// ...
}
}
}
}Analysis: The code uses SWC's 2. Destructuring from Non-Flag VariablesScenario: const flags = useExperimentalFlags();
const notFlags = { featureA: false };
const { featureA } = notFlags; // Should NOT be transformedAnalysis: Looking at lines 145-151, the code checks 🎯 Best PracticesGood Practices Used:
🚀 Performance ConsiderationsCurrent Approach:
Potential Optimization (Low Priority):Consider using a single-pass visitor if performance becomes a concern. However, the current approach prioritizes clarity and correctness, which is appropriate for a transformer. 🔒 Security ConcernsNo security issues identified. The transformation is purely syntactic and doesn't introduce injection risks or unsafe operations. 📝 Suggestions1. Add Test Case: Computed Property Accessconst flags = useExperimentalFlags();
const key = 'featureA';
console.log(flags[key]); // What happens here?Currently, this would NOT be transformed (only 2. Add Test Case: Spread Operatorconst flags = useExperimentalFlags();
const allFlags = { ...flags }; // What happens here?This is an interesting edge case worth testing. 3. Add Test Case: Renamed Destructuringconst flags = useExperimentalFlags();
const { featureA: renamedFeature } = flags;
console.log(renamedFeature); // Should this be transformed?Looking at line 90, the code extracts 4. Consider Warning on Unused Flag ObjectsIf a flag object is declared but never used (no properties accessed or destructured), consider emitting a warning since the original call is removed. 5. Documentation: Add Migration GuideSince 📊 Test Coverage AssessmentCurrent Coverage: Good ✅
Additional Tests to Consider:
🎓 Overall AssessmentVerdict: APPROVE with minor suggestions ✅ This is a well-implemented feature that maintains backward compatibility while adding useful flexibility. The code quality is good, tests are comprehensive, and documentation is thorough. Before Merging:
Minor Issues (Non-blocking):
The implementation correctly handles scope safety, multiple patterns, and avoids common pitfalls. Great work! 🎉 Review conducted by: Claude Code |
Co-authored-by: Donny/강동윤 <kdy1@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR extends the swc_feature_flags build-time transformer to support indirect destructuring and property access patterns for feature flags, beyond the previously supported direct destructuring pattern. However, it also removes the exclude_flags configuration option as a breaking change, which is not properly documented.
Changes:
- Added support for indirect destructuring (
const flags = useFlags(); const { featureA } = flags;) - Added support for property access patterns (
flags.featureA) - Removed
exclude_flagsconfiguration option from all config structs - Added 4 new test fixtures for the new patterns
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/swc_feature_flags/src/build_time.rs | Core transformation logic extended with flag_object_map tracking and member expression transformation |
| crates/swc_feature_flags/src/config.rs | Removed exclude_flags field from BuildTimeConfig and FeatureFlagsConfig structs (breaking change) |
| crates/swc_feature_flags/src/lib.rs | Updated documentation examples to remove exclude_flags references |
| crates/swc_feature_flags/tests/fixture.rs | Removed exclude_flags initialization from test configuration |
| crates/swc_feature_flags/README.md | Updated documentation to remove exclude_flags and add new usage patterns |
| crates/swc_feature_flags/tests/fixture/build-time/*/input.js | New test fixtures for indirect destructuring, property access, mixed patterns, and scope safety |
| crates/swc_feature_flags/tests/fixture/build-time/*/output.js | Expected outputs for new test fixtures |
| .changeset/little-cherries-compete.md | Changeset file (incorrectly marked as minor) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
crates/swc_feature_flags/tests/fixture/build-time/indirect-destructuring/input.js
Show resolved
Hide resolved
crates/swc_feature_flags/tests/fixture/build-time/mixed-patterns/input.js
Show resolved
Hide resolved
crates/swc_feature_flags/tests/fixture/build-time/property-access/input.js
Show resolved
Hide resolved
crates/swc_feature_flags/tests/fixture/build-time/scope-safety-object/input.js
Show resolved
Hide resolved
|
@claude fix CI |
|
Claude finished @kdy1's task in 2m 47s —— View job CI Fixed ✅SummaryThe CI was failing because a test case for the removed Changes MadeRemoved test case "Should handle exclude flags in mark mode":
Verification✅ All 9 Rust tests pass locally The CI should now pass once the new workflow run completes. |
The exclude_flags feature was removed in a previous commit, but the test case that verified its behavior was still present. This was causing CI failures because the test expected excludedFlag to not be transformed, but without the exclude feature, all flags are now transformed. Removed: - Test case 'Should handle exclude flags in mark mode' from wasm.test.ts - Corresponding snapshot from wasm.test.ts.snap All tests now pass (9/9 Rust tests passing). Co-authored-by: Donny/강동윤 <kdy1@users.noreply.github.com>
Summary
Extends the
swc_feature_flagsbuild-time transformer to support multiple ways of accessing feature flags beyond direct destructuring.New patterns now supported:
const flags = useFlags(); const { featureA } = flags;const flags = useFlags(); if (flags.featureA) {}All patterns transform identically:
Motivation
Previously, the build-time transform only supported direct destructuring:
This was limiting for developers who prefer intermediate variables or property access:
This PR makes the transform work consistently across all usage patterns.
Implementation Details
Core Changes
FlagObjectInfostruct andflag_object_mapto track flag object variablesanalyze_declarator()to detect three patterns:const { featureA } = useFlags()const flags = useFlags()const { featureA } = flagsvisit_mut_expr()to transform member expressions:flags.featureA→__SWC_FLAGS__.featureAexclude_flags)Key Features
Idsystem to correctly handle variable shadowingTesting
Added 5 new test fixtures (10 files total):
const flags = useFlags(); const { featureA } = flags;const flags = useFlags(); if (flags.featureA) {}Test results: ✅ All 10 tests passing (7 build-time + 3 runtime)
Example Transformations
Indirect Destructuring:
Property Access:
Scope Safety:
Test Infrastructure Improvements
options.jsonconfiguration filesserde_jsonas dev dependency for test configuration parsingexclude_flagsper fixtureDocumentation
Updated README.md with:
Files Changed
crates/swc_feature_flags/src/build_time.rs- Core transformation logiccrates/swc_feature_flags/tests/fixture.rs- Test infrastructurecrates/swc_feature_flags/Cargo.toml- Added serde_json dependencycrates/swc_feature_flags/README.md- DocumentationBreaking Changes
None. This is a pure feature addition that's backward compatible.
Test Plan
🤖 Generated with Claude Code