@@ -19,8 +19,9 @@ import {
1919  Instruction , 
2020  isUseStateType , 
2121  isUseRefType , 
22+   GeneratedSource , 
23+   SourceLocation , 
2224}  from  '../HIR' ; 
23- import  { printInstruction }  from  '../HIR/PrintHIR' ; 
2425import  { eachInstructionLValue ,  eachInstructionOperand }  from  '../HIR/visitors' ; 
2526import  { isMutable }  from  '../ReactiveScopes/InferReactiveScopeVariables' ; 
2627import  { assertExhaustive }  from  '../Utils/utils' ; 
@@ -60,6 +61,10 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
6061  const  functions : Map < IdentifierId ,  FunctionExpression >  =  new  Map ( ) ; 
6162
6263  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+ 
6368  if  ( fn . fnType  ===  'Hook' )  { 
6469    for  ( const  param  of  fn . params )  { 
6570      if  ( param . kind  ===  'Identifier' )  { 
@@ -128,11 +133,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
128133          )  { 
129134            const  effectFunction  =  functions . get ( value . args [ 0 ] . identifier . id ) ; 
130135            if  ( effectFunction  !=  null )  { 
131-               validateEffect ( 
132-                 effectFunction . loweredFunc . func , 
133-                 errors , 
134-                 derivationCache , 
135-               ) ; 
136+               effects . push ( effectFunction . loweredFunc . func ) ; 
136137            } 
137138          }  else  if  ( isUseStateType ( lvalue . identifier ) )  { 
138139            const  stateValueSource  =  value . args [ 0 ] ; 
@@ -144,6 +145,25 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
144145        } 
145146
146147        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+ 
147167          const  operandMetadata  =  derivationCache . get ( operand . identifier . id ) ; 
148168
149169          if  ( operandMetadata  ===  undefined )  { 
@@ -212,6 +232,10 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
212232    } 
213233  } 
214234
235+   for  ( const  effect  of  effects )  { 
236+     validateEffect ( effect ,  errors ,  derivationCache ,  setStateCache ) ; 
237+   } 
238+ 
215239  if  ( errors . hasAnyErrors ( ) )  { 
216240    throw  errors ; 
217241  } 
@@ -269,11 +293,17 @@ function validateEffect(
269293  effectFunction : HIRFunction , 
270294  errors : CompilerError , 
271295  derivationCache : Map < IdentifierId ,  DerivationMetadata > , 
296+   setStateCache : Map < string  |  undefined  |  null ,  Array < Place > > , 
272297) : void   { 
298+   const  effectSetStateCache : Map < 
299+     string  |  undefined  |  null , 
300+     Array < Place > 
301+   >  =  new  Map ( ) ; 
273302  const  seenBlocks : Set < BlockId >  =  new  Set ( ) ; 
274303
275304  const  effectDerivedSetStateCalls : Array < { 
276305    value : CallExpression ; 
306+     loc : SourceLocation ; 
277307    sourceIds : Set < IdentifierId > ; 
278308  } >  =  [ ] ; 
279309
@@ -292,6 +322,28 @@ function validateEffect(
292322        return ; 
293323      } 
294324
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+ 
295347      if  ( 
296348        instr . value . kind  ===  'CallExpression'  && 
297349        isSetStateType ( instr . value . callee . identifier )  && 
@@ -305,6 +357,7 @@ function validateEffect(
305357        if  ( argMetadata  !==  undefined )  { 
306358          effectDerivedSetStateCalls . push ( { 
307359            value : instr . value , 
360+             loc : instr . value . callee . loc , 
308361            sourceIds : argMetadata . sourcesIds , 
309362          } ) ; 
310363        } 
@@ -337,13 +390,22 @@ function validateEffect(
337390  } 
338391
339392  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+     } 
348410  } 
349411} 
0 commit comments