main
md 153 lines 5.01 KB
Rendered Raw
1 # validateStaticComponents
2
3 ## File
4 `src/Validation/ValidateStaticComponents.ts`
5
6 ## Purpose
7 Validates that components used in JSX are not created dynamically during render. Components created during render will have their state reset on every re-render because React sees them as new component types each time. This is a common React anti-pattern that causes bugs and poor performance.
8
9 ## Input Invariants
10 - Operates on HIRFunction (pre-reactive transformation)
11 - All instructions and phi nodes are present
12 - JSX expressions have been lowered to `JsxExpression` instruction values
13
14 ## Validation Rules
15 When a JSX element uses a component that was dynamically created during render, the pass produces:
16 ```
17 Cannot create components during render. Components created during render will reset
18 their state each time they are created. Declare components outside of render
19 ```
20
21 The error includes two locations:
22 1. Where the component is used in JSX
23 2. Where the component was originally created
24
25 ### What constitutes "dynamically created"?
26 The following instruction kinds mark a value as dynamically created:
27 - `FunctionExpression` - An inline function definition
28 - `NewExpression` - A `new` constructor call
29 - `MethodCall` - A method call that returns a value
30 - `CallExpression` - A function call that returns a value
31
32 ## Algorithm
33
34 1. Create a `Map<IdentifierId, SourceLocation>` called `knownDynamicComponents` to track identifiers whose values are dynamically created
35
36 2. Iterate through all blocks in evaluation order
37
38 3. For each block, first process phi nodes:
39 - If any phi operand is in `knownDynamicComponents`, add the phi result to the map
40 - This propagates dynamic-ness through control flow joins
41
42 4. For each instruction in the block:
43 - **FunctionExpression, NewExpression, MethodCall, CallExpression**: Add the lvalue to `knownDynamicComponents` with its source location
44 - **LoadLocal**: If the loaded value is dynamic, mark the lvalue as dynamic
45 - **StoreLocal**: If the stored value is dynamic, mark both the lvalue and the store target as dynamic
46 - **JsxExpression**: If the JSX tag is an identifier that is in `knownDynamicComponents`, push a diagnostic error
47
48 5. Return the collected errors
49
50 ### Data Flow Tracking
51 The pass tracks how dynamic values flow through the program:
52 - Through variable assignments (`StoreLocal`, `LoadLocal`)
53 - Through phi nodes (conditional assignments)
54 - Into JSX component positions
55
56 ## Edge Cases
57
58 ### Conditionally Assigned Components
59 ```javascript
60 function Example({cond}) {
61 let Component;
62 if (cond) {
63 Component = createComponent(); // Dynamic!
64 } else {
65 Component = OtherComponent; // Static
66 }
67 return <Component />; // Error: Component may be dynamic
68 }
69 ```
70 The phi node joins the conditional paths, and since one path is dynamic, the result is considered dynamic.
71
72 ### Component Returned from Hooks/Functions
73 ```javascript
74 function Example() {
75 const Component = useCreateComponent(); // CallExpression - dynamic
76 return <Component />; // Error
77 }
78 ```
79
80 ### Factory Functions
81 ```javascript
82 function Example() {
83 const Component = createComponent(); // CallExpression - dynamic
84 return <Component />; // Error
85 }
86 ```
87
88 ### Safe Patterns (No Error)
89 ```javascript
90 // Component defined outside render
91 const MyComponent = () => <div />;
92
93 function Example() {
94 return <MyComponent />; // OK - not created during render
95 }
96 ```
97
98 ## TODOs
99 None found in the source.
100
101 ## Example
102
103 ### Fixture: `static-components/invalid-dynamically-construct-component-in-render.js`
104
105 **Input:**
106 ```javascript
107 // @validateStaticComponents
108 function Example(props) {
109 const Component = createComponent();
110 return <Component />;
111 }
112 ```
113
114 **Error (from logs):**
115 ```json
116 {
117 "kind": "CompileError",
118 "detail": {
119 "options": {
120 "category": "StaticComponents",
121 "reason": "Cannot create components during render",
122 "description": "Components created during render will reset their state each time they are created. Declare components outside of render",
123 "details": [
124 {
125 "kind": "error",
126 "loc": { "start": { "line": 4, "column": 10 } },
127 "message": "This component is created during render"
128 },
129 {
130 "kind": "error",
131 "loc": { "start": { "line": 3, "column": 20 } },
132 "message": "The component is created during render here"
133 }
134 ]
135 }
136 }
137 }
138 ```
139
140 **Why it fails:** The `createComponent()` call creates a new component type on every render. When this component is used in JSX, React will see a different component type each time, causing the component to unmount and remount (losing all state) on every render.
141
142 ### Fixture: `static-components/invalid-dynamically-constructed-component-function.js`
143
144 **Input:**
145 ```javascript
146 // @validateStaticComponents
147 function Example(props) {
148 const Component = () => <div />;
149 return <Component />;
150 }
151 ```
152
153 **Why it fails:** Even though this looks like a simple component definition, it creates a new function (and thus a new component type) on every render. The fix is to move the component definition outside of `Example`.