Commit a5d0a6e
authored
fix(react-transform): Fix JSX detection leaking to sibling functions (#814)
# Fix: JSX detection leaking outside of component scope
## The Bug
The Babel transform was incorrectly transforming non-component functions that used signals when a sibling component contained JSX. For example, in the following code:
```js
function wrapper() {
function Component() {
return <div>Hello</div>;
}
const CountModel = createModel(() => ({
count: signal(0),
increment() {
this.count.value++; // Uses .value
},
}));
}
```
The `increment` method inside `CountModel` was being incorrectly transformed because the plugin detected it "contained JSX" due to the sibling `Component` function.
### When This Bug Manifests
This bug requires the component to be nested inside another function scope. The parent function scope is where the `containsJSX` flag would be incorrectly stored, allowing sibling functions to find it when searching their scope chain.
For most application code where components are declared at the module's root scope, this bug wouldn't manifest since there's no parent function scope to hold the leaked flag. However, this is a common pattern in **test files** where components and helper functions are declared inside `describe()` or `it()` blocks—which is how I discovered the bug.
### Root Cause
The bug was multi-faceted:
1. **Function search logic was checking `containsJSX` before it was set**: The `isComponentFunction` helper expected `containsJSX` to already be set on the scope, but it was being called during the function path traversal that would set it. So when looking for a component candidate to mark with `containsJSX`, the current function wouldn't be recognized as a component, causing the search to continue upward.
2. **Fallback to highest-level component**: When searching for a component candidate, the setter function would fallback to the highest level component-like function, which in some scenarios (e.g., test suites with `describe()`) isn't a valid component.
3. **Scope data inheritance**: The critical issue was that `containsJSX` and `maybeUsesSignal` were being set on `scope` rather than on the function node itself. Babel's `scope.getData()` searches parent scopes for data. This meant that when we set `containsJSX` on a parent scope, sibling functions in that same scope would also find that data when querying their own scope, making them appear to "contain JSX" when they didn't.
## The Fix
### Core Change: Store data on function nodes, not scopes
Changed from using `scope.setData()`/`scope.getData()` to using `path.setData()`/`path.getData()` directly on the function node paths. This ensures that `containsJSX` and `maybeUsesSignal` flags are isolated to the specific function that actually contains JSX or signal usage, preventing leakage to sibling functions.
### Refactored `getComponentFunctionDeclaration` to `findParentComponentOrHook`
This function is mostly the same but with a couple bug fixes:
- Find and return the parent component or custom hook function path (not scope)
- Check if a function is a component or hook based on its name, rather than relying on `containsJSX` which may not be set yet
- If a matching component or hook function is now found, return `null` instead of the last seen function.
### Moved `isComponentFunction` check inside `shouldTransform`
Since `isComponentFunction` needs to check `containsJSX`, it should only be called after the function body has been fully traversed. Moving it inside `shouldTransform` (which is called on function exit) ensures the data is available.
## Additional Improvements
- **`getFunctionName` now handles default exports**: The function now takes a `filename` parameter and resolves `DefaultExportSymbol` to the filename internally, simplifying call sites.
- **Added `isCustomHookCallback` helper**: New utility function that detects when a function is being passed as a parameter to a custom hook call (e.g., `useCustomHook(() => { ... })`).
- **Added verbose logging**: New `signals:react-transform:verbose` debug namespace for more detailed logging during development and debugging.
- **Test infrastructure improvements**: Added ability to set `DEBUG_TEST_IDS = false` to skip all generated tests when debugging specific test cases.1 parent de905d2 commit a5d0a6e
4 files changed
Lines changed: 273 additions & 128 deletions
File tree
- .changeset
- packages
- react-transform
- src
- test/node
- react/runtime/test/browser
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| 39 | + | |
| 40 | + | |
39 | 41 | | |
40 | 42 | | |
41 | 43 | | |
| |||
45 | 47 | | |
46 | 48 | | |
47 | 49 | | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
| 50 | + | |
53 | 51 | | |
54 | | - | |
| 52 | + | |
55 | 53 | | |
56 | 54 | | |
57 | | - | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
58 | 78 | | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | | - | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
87 | 85 | | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
88 | 103 | | |
89 | 104 | | |
90 | | - | |
| 105 | + | |
91 | 106 | | |
92 | 107 | | |
93 | 108 | | |
94 | 109 | | |
95 | 110 | | |
96 | | - | |
97 | | - | |
98 | | - | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
99 | 119 | | |
100 | 120 | | |
101 | 121 | | |
| |||
202 | 222 | | |
203 | 223 | | |
204 | 224 | | |
205 | | - | |
206 | | - | |
207 | | - | |
208 | | - | |
209 | | - | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
210 | 238 | | |
211 | 239 | | |
212 | | - | |
| 240 | + | |
213 | 241 | | |
214 | 242 | | |
215 | 243 | | |
| |||
219 | 247 | | |
220 | 248 | | |
221 | 249 | | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
222 | 260 | | |
223 | 261 | | |
224 | 262 | | |
| |||
286 | 324 | | |
287 | 325 | | |
288 | 326 | | |
289 | | - | |
290 | | - | |
291 | | - | |
292 | | - | |
293 | | - | |
294 | | - | |
295 | | - | |
296 | | - | |
297 | | - | |
298 | | - | |
299 | 327 | | |
300 | 328 | | |
301 | 329 | | |
302 | 330 | | |
303 | 331 | | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
304 | 344 | | |
305 | 345 | | |
306 | 346 | | |
| |||
312 | 352 | | |
313 | 353 | | |
314 | 354 | | |
315 | | - | |
| 355 | + | |
316 | 356 | | |
317 | 357 | | |
318 | 358 | | |
| |||
556 | 596 | | |
557 | 597 | | |
558 | 598 | | |
559 | | - | |
560 | | - | |
| 599 | + | |
561 | 600 | | |
562 | 601 | | |
563 | 602 | | |
| |||
583 | 622 | | |
584 | 623 | | |
585 | 624 | | |
586 | | - | |
| 625 | + | |
587 | 626 | | |
588 | 627 | | |
589 | 628 | | |
| |||
725 | 764 | | |
726 | 765 | | |
727 | 766 | | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
728 | 771 | | |
729 | 772 | | |
730 | 773 | | |
| |||
790 | 833 | | |
791 | 834 | | |
792 | 835 | | |
793 | | - | |
794 | | - | |
| 836 | + | |
| 837 | + | |
795 | 838 | | |
796 | 839 | | |
797 | 840 | | |
798 | 841 | | |
799 | | - | |
800 | | - | |
801 | | - | |
802 | | - | |
803 | | - | |
804 | | - | |
805 | | - | |
806 | 842 | | |
807 | 843 | | |
808 | 844 | | |
| |||
814 | 850 | | |
815 | 851 | | |
816 | 852 | | |
817 | | - | |
818 | | - | |
819 | | - | |
820 | | - | |
821 | | - | |
822 | | - | |
| 853 | + | |
823 | 854 | | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
824 | 858 | | |
825 | | - | |
826 | | - | |
827 | | - | |
828 | | - | |
829 | | - | |
830 | | - | |
831 | | - | |
832 | | - | |
| 859 | + | |
833 | 860 | | |
834 | | - | |
| 861 | + | |
835 | 862 | | |
836 | 863 | | |
837 | 864 | | |
| |||
872 | 899 | | |
873 | 900 | | |
874 | 901 | | |
875 | | - | |
| 902 | + | |
876 | 903 | | |
877 | 904 | | |
878 | 905 | | |
| |||
892 | 919 | | |
893 | 920 | | |
894 | 921 | | |
895 | | - | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
896 | 928 | | |
897 | 929 | | |
898 | 930 | | |
899 | 931 | | |
900 | 932 | | |
901 | | - | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
902 | 939 | | |
903 | 940 | | |
904 | 941 | | |
905 | 942 | | |
906 | | - | |
| 943 | + | |
907 | 944 | | |
908 | 945 | | |
909 | | - | |
| 946 | + | |
910 | 947 | | |
911 | 948 | | |
912 | 949 | | |
| |||
0 commit comments