@@ -19,8 +19,9 @@ import {
19
19
Instruction ,
20
20
isUseStateType ,
21
21
isUseRefType ,
22
+ GeneratedSource ,
23
+ SourceLocation ,
22
24
} from '../HIR' ;
23
- import { printInstruction } from '../HIR/PrintHIR' ;
24
25
import { eachInstructionLValue , eachInstructionOperand } from '../HIR/visitors' ;
25
26
import { isMutable } from '../ReactiveScopes/InferReactiveScopeVariables' ;
26
27
import { assertExhaustive } from '../Utils/utils' ;
@@ -60,6 +61,10 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
60
61
const functions : Map < IdentifierId , FunctionExpression > = new Map ( ) ;
61
62
62
63
const derivationCache : Map < IdentifierId , DerivationMetadata > = new Map ( ) ;
64
+ const setStateCache : Map < string | undefined | null , Array < Place > > = new Map ( ) ;
65
+
66
+ const effects : Array < HIRFunction > = [ ] ;
67
+
63
68
if ( fn . fnType === 'Hook' ) {
64
69
for ( const param of fn . params ) {
65
70
if ( param . kind === 'Identifier' ) {
@@ -128,11 +133,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
128
133
) {
129
134
const effectFunction = functions . get ( value . args [ 0 ] . identifier . id ) ;
130
135
if ( effectFunction != null ) {
131
- validateEffect (
132
- effectFunction . loweredFunc . func ,
133
- errors ,
134
- derivationCache ,
135
- ) ;
136
+ effects . push ( effectFunction . loweredFunc . func ) ;
136
137
}
137
138
} else if ( isUseStateType ( lvalue . identifier ) ) {
138
139
const stateValueSource = value . args [ 0 ] ;
@@ -144,6 +145,25 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
144
145
}
145
146
146
147
for ( const operand of eachInstructionOperand ( instr ) ) {
148
+ // Record setState usages everywhere
149
+ switch ( instr . value . kind ) {
150
+ case 'JsxExpression' :
151
+ case 'CallExpression' :
152
+ case 'MethodCall' :
153
+ if (
154
+ isSetStateType ( operand . identifier ) &&
155
+ operand . loc !== GeneratedSource
156
+ ) {
157
+ if ( setStateCache . has ( operand . loc . identifierName ) ) {
158
+ setStateCache . get ( operand . loc . identifierName ) ! . push ( operand ) ;
159
+ } else {
160
+ setStateCache . set ( operand . loc . identifierName , [ operand ] ) ;
161
+ }
162
+ }
163
+ break ;
164
+ default :
165
+ }
166
+
147
167
const operandMetadata = derivationCache . get ( operand . identifier . id ) ;
148
168
149
169
if ( operandMetadata === undefined ) {
@@ -212,6 +232,10 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
212
232
}
213
233
}
214
234
235
+ for ( const effect of effects ) {
236
+ validateEffect ( effect , errors , derivationCache , setStateCache ) ;
237
+ }
238
+
215
239
if ( errors . hasAnyErrors ( ) ) {
216
240
throw errors ;
217
241
}
@@ -269,11 +293,17 @@ function validateEffect(
269
293
effectFunction : HIRFunction ,
270
294
errors : CompilerError ,
271
295
derivationCache : Map < IdentifierId , DerivationMetadata > ,
296
+ setStateCache : Map < string | undefined | null , Array < Place > > ,
272
297
) : void {
298
+ const effectSetStateCache : Map <
299
+ string | undefined | null ,
300
+ Array < Place >
301
+ > = new Map ( ) ;
273
302
const seenBlocks : Set < BlockId > = new Set ( ) ;
274
303
275
304
const effectDerivedSetStateCalls : Array < {
276
305
value : CallExpression ;
306
+ loc : SourceLocation ;
277
307
sourceIds : Set < IdentifierId > ;
278
308
} > = [ ] ;
279
309
@@ -292,6 +322,28 @@ function validateEffect(
292
322
return ;
293
323
}
294
324
325
+ for ( const operand of eachInstructionOperand ( instr ) ) {
326
+ switch ( instr . value . kind ) {
327
+ case 'JsxExpression' :
328
+ case 'CallExpression' :
329
+ case 'MethodCall' :
330
+ if (
331
+ isSetStateType ( operand . identifier ) &&
332
+ operand . loc !== GeneratedSource
333
+ ) {
334
+ if ( effectSetStateCache . has ( operand . loc . identifierName ) ) {
335
+ effectSetStateCache
336
+ . get ( operand . loc . identifierName ) !
337
+ . push ( operand ) ;
338
+ } else {
339
+ effectSetStateCache . set ( operand . loc . identifierName , [ operand ] ) ;
340
+ }
341
+ }
342
+ break ;
343
+ default :
344
+ }
345
+ }
346
+
295
347
if (
296
348
instr . value . kind === 'CallExpression' &&
297
349
isSetStateType ( instr . value . callee . identifier ) &&
@@ -305,6 +357,7 @@ function validateEffect(
305
357
if ( argMetadata !== undefined ) {
306
358
effectDerivedSetStateCalls . push ( {
307
359
value : instr . value ,
360
+ loc : instr . value . callee . loc ,
308
361
sourceIds : argMetadata . sourcesIds ,
309
362
} ) ;
310
363
}
@@ -337,13 +390,22 @@ function validateEffect(
337
390
}
338
391
339
392
for ( const derivedSetStateCall of effectDerivedSetStateCalls ) {
340
- errors . push ( {
341
- category : ErrorCategory . EffectDerivationsOfState ,
342
- reason :
343
- 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)' ,
344
- description : null ,
345
- loc : derivedSetStateCall . value . callee . loc ,
346
- suggestions : null ,
347
- } ) ;
393
+ if (
394
+ derivedSetStateCall . loc !== GeneratedSource &&
395
+ effectSetStateCache . has ( derivedSetStateCall . loc . identifierName ) &&
396
+ setStateCache . has ( derivedSetStateCall . loc . identifierName ) &&
397
+ effectSetStateCache . get ( derivedSetStateCall . loc . identifierName ) !
398
+ . length ===
399
+ setStateCache . get ( derivedSetStateCall . loc . identifierName ) ! . length
400
+ ) {
401
+ errors . push ( {
402
+ category : ErrorCategory . EffectDerivationsOfState ,
403
+ reason :
404
+ 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)' ,
405
+ description : null ,
406
+ loc : derivedSetStateCall . value . callee . loc ,
407
+ suggestions : null ,
408
+ } ) ;
409
+ }
348
410
}
349
411
}
0 commit comments