Skip to content

Commit 689e3a6

Browse files
committed
[compiler] Added validation for local state and refined error messages
1 parent ac6de69 commit 689e3a6

File tree

1 file changed

+40
-57
lines changed

1 file changed

+40
-57
lines changed

compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts

Lines changed: 40 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ import {
3434
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
3535
import {assertExhaustive} from '../Utils/utils';
3636

37+
// TODO: Maybe I can consolidate some types
3738
type SetStateCall = {
3839
loc: SourceLocation;
39-
invalidDeps: Map<Identifier, Place[]> | undefined;
40+
invalidDeps: DerivationMetadata;
4041
setStateId: IdentifierId;
4142
};
43+
4244
type TypeOfValue = 'ignored' | 'fromProps' | 'fromState' | 'fromPropsOrState';
4345

4446
type SetStateName = string | undefined | null;
@@ -52,9 +54,8 @@ type DerivationMetadata = {
5254

5355
// TODO: This needs refining
5456
type ErrorMetadata = {
55-
errorType: 'HoistState' | 'CalculateInRender';
56-
propInfo: string | undefined;
57-
localStateInfo: string | undefined;
57+
errorType: TypeOfValue;
58+
invalidDepInfo: string | undefined;
5859
loc: SourceLocation;
5960
setStateName: SetStateName;
6061
};
@@ -102,7 +103,7 @@ function parseInstr(
102103
// console.log(instr);
103104
let typeOfValue: TypeOfValue = 'ignored';
104105

105-
// If the instruction is destructuring a useState hook call
106+
// TODO: Not sure if this will catch every time we create a new useState
106107
if (
107108
instr.value.kind === 'Destructure' &&
108109
instr.value.lvalue.pattern.kind === 'ArrayPattern' &&
@@ -118,7 +119,6 @@ function parseInstr(
118119
}
119120
}
120121

121-
// If the instruction is calling a setState
122122
if (
123123
instr.value.kind === 'CallExpression' &&
124124
isSetStateType(instr.value.callee.identifier) &&
@@ -298,7 +298,6 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
298298
}
299299
}
300300

301-
// Maybe this should run for every instruction being parsed
302301
if (value.kind === 'LoadLocal') {
303302
locals.set(lvalue.identifier.id, value.place.identifier.id);
304303
} else if (value.kind === 'ArrayExpression') {
@@ -357,7 +356,8 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
357356
*/
358357
if (
359358
setStateCalls.get(error.setStateName)?.length !=
360-
effectSetStates.get(error.setStateName)?.length
359+
effectSetStates.get(error.setStateName)?.length &&
360+
error.errorType !== 'fromState'
361361
) {
362362
reason =
363363
'Consider lifting state up to the parent component to make this a controlled component. (https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes)';
@@ -366,17 +366,9 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
366366
'You may not need this effect. Values derived from 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)';
367367
}
368368

369-
if (error.propInfo !== undefined) {
370-
description += error.propInfo;
371-
}
372-
373-
if (error.localStateInfo !== undefined) {
374-
description += error.localStateInfo;
375-
}
376-
377369
throwableErrors.push({
378370
reason: reason,
379-
description: description,
371+
description: `You are using invalid dependencies: \n\n${error.invalidDepInfo}`,
380372
severity: ErrorSeverity.InvalidReact,
381373
loc: error.loc,
382374
});
@@ -411,7 +403,7 @@ function validateEffect(
411403
}
412404
}
413405

414-
// This might be wrong gotta double check
406+
// TODO: This might be wrong gotta double check
415407
let hasInvalidDep = false;
416408
for (const dep of effectDeps) {
417409
const depMetadata = derivedTuple.get(dep);
@@ -513,23 +505,15 @@ function validateEffect(
513505
instr.value.args.length === 1 &&
514506
instr.value.args[0].kind === 'Identifier'
515507
) {
516-
const propSources = derivedTuple.get(
508+
const invalidDeps = derivedTuple.get(
517509
instr.value.args[0].identifier.id,
518510
);
519511

520-
if (propSources !== undefined) {
512+
if (invalidDeps !== undefined) {
521513
setStateCallsInEffect.push({
522514
loc: instr.value.callee.loc,
523515
setStateId: instr.value.callee.identifier.id,
524-
invalidDeps: new Map([
525-
[instr.value.args[0].identifier, propSources.sources],
526-
]),
527-
});
528-
} else {
529-
setStateCallsInEffect.push({
530-
loc: instr.value.callee.loc,
531-
setStateId: instr.value.callee.identifier.id,
532-
invalidDeps: undefined,
516+
invalidDeps: invalidDeps,
533517
});
534518
}
535519
}
@@ -551,34 +535,33 @@ function validateEffect(
551535
}
552536

553537
for (const call of setStateCallsInEffect) {
554-
if (call.invalidDeps != null) {
555-
let propNames = '';
556-
for (const [, places] of call.invalidDeps.entries()) {
557-
const placeNames = places
558-
.map(place => place.identifier.name?.value)
559-
.join(', ');
560-
propNames += `[${placeNames}], `;
561-
}
562-
propNames = propNames.slice(0, -2);
563-
const propInfo = propNames ? ` (from props '${propNames}')` : '';
564-
565-
errors.push({
566-
errorType: 'HoistState',
567-
propInfo: propInfo,
568-
localStateInfo: undefined,
569-
loc: call.loc,
570-
setStateName:
571-
call.loc !== GeneratedSource ? call.loc.identifierName : undefined,
572-
});
573-
} else {
574-
errors.push({
575-
errorType: 'CalculateInRender',
576-
propInfo: undefined,
577-
localStateInfo: undefined,
578-
loc: call.loc,
579-
setStateName:
580-
call.loc !== GeneratedSource ? call.loc.identifierName : undefined,
581-
});
538+
const placeNames = call.invalidDeps.sources
539+
.map(place => place.identifier.name?.value)
540+
.join(', ');
541+
542+
let sourceNames = '';
543+
let invalidDepInfo = '';
544+
console.log(call.invalidDeps);
545+
if (call.invalidDeps.typeOfValue === 'fromProps') {
546+
sourceNames += `[${placeNames}], `;
547+
sourceNames = sourceNames.slice(0, -2);
548+
invalidDepInfo = sourceNames
549+
? `Invalid deps from props ${sourceNames}`
550+
: '';
551+
} else if (call.invalidDeps.typeOfValue === 'fromState') {
552+
sourceNames += `[${placeNames}], `;
553+
sourceNames = sourceNames.slice(0, -2);
554+
invalidDepInfo = sourceNames
555+
? `Invalid deps from local state: ${sourceNames}`
556+
: '';
582557
}
558+
559+
errors.push({
560+
errorType: call.invalidDeps.typeOfValue,
561+
invalidDepInfo: invalidDepInfo,
562+
loc: call.loc,
563+
setStateName:
564+
call.loc !== GeneratedSource ? call.loc.identifierName : undefined,
565+
});
583566
}
584567
}

0 commit comments

Comments
 (0)