Compare commits

...

1 Commits

Author SHA1 Message Date
Joe Savona
2283eb9183 [compiler] Improve more error messages
This PR uses the new diagnostic type for most of the error messages produced in our explicit validation passes (`Validation/` directory). One of the validations produced multiple errors as a hack to showing multiple related locations, which we can now consolidate into a single diagnostic.
2025-07-24 15:38:22 -07:00
41 changed files with 324 additions and 262 deletions

View File

@@ -59,7 +59,7 @@ export type CompilerDiagnosticDetail =
*/
{
kind: 'error';
loc: SourceLocation;
loc: SourceLocation | null;
message: string;
};
@@ -100,6 +100,12 @@ export class CompilerDiagnostic {
this.options = options;
}
static create(
options: Omit<CompilerDiagnosticOptions, 'details'>,
): CompilerDiagnostic {
return new CompilerDiagnostic({...options, details: []});
}
get category(): CompilerDiagnosticOptions['category'] {
return this.options.category;
}
@@ -113,6 +119,11 @@ export class CompilerDiagnostic {
return this.options.suggestions;
}
withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic {
this.options.details.push(detail);
return this;
}
primaryLocation(): SourceLocation | null {
return this.options.details.filter(d => d.kind === 'error')[0]?.loc ?? null;
}
@@ -127,7 +138,7 @@ export class CompilerDiagnostic {
switch (detail.kind) {
case 'error': {
const loc = detail.loc;
if (typeof loc === 'symbol') {
if (loc == null || typeof loc === 'symbol') {
continue;
}
let codeFrame: string;

View File

@@ -9,6 +9,7 @@ import {NodePath, Scope} from '@babel/traverse';
import * as t from '@babel/types';
import invariant from 'invariant';
import {
CompilerDiagnostic,
CompilerError,
CompilerSuggestionOperation,
ErrorSeverity,
@@ -104,12 +105,18 @@ export function lower(
if (param.isIdentifier()) {
const binding = builder.resolveIdentifier(param);
if (binding.kind !== 'Identifier') {
builder.errors.push({
reason: `(BuildHIR::lower) Could not find binding for param \`${param.node.name}\``,
severity: ErrorSeverity.Invariant,
loc: param.node.loc ?? null,
suggestions: null,
});
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
category: 'Could not find binding',
description: `[BuildHIR] Could not find binding for param \`${param.node.name}\``,
severity: ErrorSeverity.Invariant,
suggestions: null,
}).withDetail({
kind: 'error',
loc: param.node.loc ?? null,
message: 'Could not find binding',
}),
);
return;
}
const place: Place = {
@@ -163,12 +170,18 @@ export function lower(
'Assignment',
);
} else {
builder.errors.push({
reason: `(BuildHIR::lower) Handle ${param.node.type} params`,
severity: ErrorSeverity.Todo,
loc: param.node.loc ?? null,
suggestions: null,
});
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
category: `Handle ${param.node.type} parameters`,
description: `[BuildHIR] Add support for ${param.node.type} parameters`,
severity: ErrorSeverity.Todo,
suggestions: null,
}).withDetail({
kind: 'error',
loc: param.node.loc ?? null,
message: 'Unsupported parameter type',
}),
);
}
});
@@ -188,13 +201,18 @@ export function lower(
lowerStatement(builder, body);
directives = body.get('directives').map(d => d.node.value.value);
} else {
builder.errors.push({
severity: ErrorSeverity.InvalidJS,
reason: `Unexpected function body kind`,
description: `Expected function body to be an expression or a block statement, got \`${body.type}\``,
loc: body.node.loc ?? null,
suggestions: null,
});
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidJS,
category: `Unexpected function body kind`,
description: `Expected function body to be an expression or a block statement, got \`${body.type}\``,
suggestions: null,
}).withDetail({
kind: 'error',
loc: body.node.loc ?? null,
message: 'Expected a block statement or expression',
}),
);
}
if (builder.errors.hasErrors()) {

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, Effect} from '..';
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
import {HIRFunction, IdentifierId, Place} from '../HIR';
import {
eachInstructionLValue,
@@ -28,16 +28,19 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void {
false,
);
if (reassignment !== null) {
CompilerError.throwInvalidReact({
reason:
'Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead',
description:
reassignment.identifier.name !== null &&
reassignment.identifier.name.kind === 'named'
? `Variable \`${reassignment.identifier.name.value}\` cannot be reassigned after render`
: '',
loc: reassignment.loc,
});
const errors = new CompilerError();
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot reassign a variable after render completes',
description: `Reassigning ${reassignment.identifier.name != null && reassignment.identifier.name.kind === 'named' ? `variable \`${reassignment.identifier.name.value}\`` : 'a variable'} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead`,
}).withDetail({
kind: 'error',
loc: reassignment.loc,
message: 'Cannot reassign variable after render completes',
}),
);
throw errors;
}
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, Effect, ErrorSeverity} from '..';
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
import {
FunctionEffect,
HIRFunction,
@@ -57,16 +57,24 @@ export function validateNoFreezingKnownMutableFunctions(
if (operand.effect === Effect.Freeze) {
const effect = contextMutationEffects.get(operand.identifier.id);
if (effect != null) {
errors.push({
reason: `This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead`,
loc: operand.loc,
severity: ErrorSeverity.InvalidReact,
});
errors.push({
reason: `The function modifies a local variable here`,
loc: effect.loc,
severity: ErrorSeverity.InvalidReact,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot modify local variables after render completes',
description: `This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead`,
})
.withDetail({
kind: 'error',
loc: operand.loc,
message:
'This function may (indirectly) reassign or modify local variables after render',
})
.withDetail({
kind: 'error',
loc: effect.loc,
message: 'This modifies a local variable',
}),
);
}
}
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '..';
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
import {HIRFunction} from '../HIR';
import {getFunctionCallSignature} from '../Inference/InferReferenceEffects';
import {Result} from '../Utils/Result';
@@ -34,17 +34,22 @@ export function validateNoImpureFunctionsInRender(
callee.identifier.type,
);
if (signature != null && signature.impure === true) {
errors.push({
reason:
'Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
description:
signature.canonicalName != null
? `\`${signature.canonicalName}\` is an impure function whose results may change on every call`
: null,
severity: ErrorSeverity.InvalidReact,
loc: callee.loc,
suggestions: null,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
category: 'Cannot call impure function during render',
description:
(signature.canonicalName != null
? `\`${signature.canonicalName}\` is an impure function. `
: '') +
'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}).withDetail({
kind: 'error',
loc: callee.loc,
message: 'Cannot call impure function',
}),
);
}
}
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '..';
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
import {BlockId, HIRFunction} from '../HIR';
import {Result} from '../Utils/Result';
import {retainWhere} from '../Utils/utils';
@@ -34,11 +34,17 @@ export function validateNoJSXInTryStatement(
switch (value.kind) {
case 'JsxExpression':
case 'JsxFragment': {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason: `Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`,
loc: value.loc,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Avoid constructing JSX within try/catch',
description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`,
}).withDetail({
kind: 'error',
loc: value.loc,
message: 'Avoid constructing JSX within try/catch',
}),
);
break;
}
}

View File

@@ -5,7 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {
HIRFunction,
IdentifierId,
@@ -90,14 +94,21 @@ export function validateNoSetStateInEffects(
if (arg !== undefined && arg.kind === 'Identifier') {
const setState = setStateFunctions.get(arg.identifier.id);
if (setState !== undefined) {
errors.push({
reason:
'Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)',
description: null,
severity: ErrorSeverity.InvalidReact,
loc: setState.loc,
suggestions: null,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
category:
'Calling setState within an effect can trigger cascading renders',
description:
'Calling setState directly within a useEffect causes cascading renders that can hurt performance, and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}).withDetail({
kind: 'error',
loc: setState.loc,
message:
'Avoid calling setState() in the top-level of an effect',
}),
);
}
}
}

View File

@@ -5,7 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {HIRFunction, IdentifierId, isSetStateType} from '../HIR';
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
import {eachInstructionValueOperand} from '../HIR/visitors';
@@ -122,23 +126,35 @@ function validateNoSetStateInRenderImpl(
unconditionalSetStateFunctions.has(callee.identifier.id)
) {
if (activeManualMemoId !== null) {
errors.push({
reason:
'Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)',
description: null,
severity: ErrorSeverity.InvalidReact,
loc: callee.loc,
suggestions: null,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
category:
'Calling setState from useMemo may trigger an infinite loop',
description:
'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}).withDetail({
kind: 'error',
loc: callee.loc,
message: 'Found setState() within useMemo()',
}),
);
} else if (unconditionalBlocks.has(block.id)) {
errors.push({
reason:
'This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)',
description: null,
severity: ErrorSeverity.InvalidReact,
loc: callee.loc,
suggestions: null,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
category:
'Calling setState during render may trigger an infinite loop',
description:
'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}).withDetail({
kind: 'error',
loc: callee.loc,
message: 'Found setState() within useMemo()',
}),
);
}
}
break;

View File

@@ -5,7 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '..';
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
import {Result} from '../Utils/Result';
@@ -63,24 +67,41 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
}
if (body.loweredFunc.func.params.length > 0) {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason: 'useMemo callbacks may not accept any arguments',
description: null,
loc: body.loc,
suggestions: null,
});
const firstParam = body.loweredFunc.func.params[0];
const loc =
firstParam.kind === 'Identifier'
? firstParam.loc
: firstParam.place.loc;
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'useMemo() callbacks may not accept parameters',
description:
'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.',
suggestions: null,
}).withDetail({
kind: 'error',
loc,
message: '',
}),
);
}
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason:
'useMemo callbacks may not be async or generator functions',
description: null,
loc: body.loc,
suggestions: null,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category:
'useMemo callbacks may not be async or generator functions',
description:
'useMemo() callbacks are called once and must synchronously return a value',
suggestions: null,
}).withDetail({
kind: 'error',
loc: body.loc,
message: 'Async and generator functions are not supported',
}),
);
}
break;

View File

@@ -36,8 +36,10 @@ function Component() {
## Error
```
Found 2 errors:
Error: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
error.bug-old-inference-false-positive-ref-validation-in-use-effect.ts:20:12
18 | );
@@ -51,24 +53,19 @@ error.bug-old-inference-false-positive-ref-validation-in-use-effect.ts:20:12
> 23 | }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 24 | }, [update]);
| ^^^^ This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^^^ This function may (indirectly) reassign or modify local variables after render
25 |
26 | return 'ok';
27 | }
Error: The function modifies a local variable here
error.bug-old-inference-false-positive-ref-validation-in-use-effect.ts:14:6
12 | ...partialParams,
13 | };
> 14 | nextParams.param = 'value';
| ^^^^^^^^^^ The function modifies a local variable here
| ^^^^^^^^^^ This modifies a local variable
15 | console.log(nextParams);
16 | },
17 | [params]
```

View File

@@ -29,20 +29,18 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
Error: Cannot reassign a variable after render completes
Variable `x` cannot be reassigned after render.
Reassigning variable `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.context-variable-only-chained-assign.ts:10:19
8 | };
9 | const fn2 = () => {
> 10 | const copy2 = (x = 4);
| ^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^ Cannot reassign variable after render completes
11 | return [invoke(fn1), copy2, identity(copy2)];
12 | };
13 | return invoke(fn2);
```

View File

@@ -18,20 +18,18 @@ function Component() {
```
Found 1 error:
Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
Error: Cannot reassign a variable after render completes
Variable `x` cannot be reassigned after render.
Reassigning variable `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.declare-reassign-variable-in-function-declaration.ts:4:4
2 | let x = null;
3 | function foo() {
> 4 | x = 9;
| ^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^ Cannot reassign variable after render completes
5 | }
6 | const y = bar(foo);
7 | return <Child y={y} />;
```

View File

@@ -16,20 +16,18 @@ function Component() {
```
Found 1 error:
Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
Error: Cannot reassign a variable after render completes
Variable `callback` cannot be reassigned after render.
Reassigning variable `callback` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.function-expression-references-variable-its-assigned-to.ts:3:4
1 | function Component() {
2 | let callback = () => {
> 3 | callback = null;
| ^^^^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^^^^^^^ Cannot reassign variable after render completes
4 | };
5 | return <div onClick={callback} />;
6 | }
```

View File

@@ -18,6 +18,8 @@ function component(a, b) {
Found 1 error:
Error: useMemo callbacks may not be async or generator functions
useMemo() callbacks are called once and must synchronously return a value
error.invalid-ReactUseMemo-async-callback.ts:2:24
1 | function component(a, b) {
> 2 | let x = React.useMemo(async () => {
@@ -25,12 +27,10 @@ error.invalid-ReactUseMemo-async-callback.ts:2:24
> 3 | await a;
| ^^^^^^^^^^^^
> 4 | }, []);
| ^^^^ useMemo callbacks may not be async or generator functions
| ^^^^ Async and generator functions are not supported
5 | return x;
6 | }
7 |
```

View File

@@ -23,30 +23,30 @@ function Component({item, cond}) {
```
Found 2 errors:
Error: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)
Error: Calling setState from useMemo may trigger an infinite loop
Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)
error.invalid-conditional-setState-in-useMemo.ts:7:6
5 | useMemo(() => {
6 | if (cond) {
> 7 | setPrevItem(item);
| ^^^^^^^^^^^ Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^^^^^^^^^^ Found setState() within useMemo()
8 | setState(0);
9 | }
10 | }, [cond, key, init]);
Error: Calling setState from useMemo may trigger an infinite loop
Error: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)
Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)
error.invalid-conditional-setState-in-useMemo.ts:8:6
6 | if (cond) {
7 | setPrevItem(item);
> 8 | setState(0);
| ^^^^^^^^ Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^^^^^^^ Found setState() within useMemo()
9 | }
10 | }, [cond, key, init]);
11 |
```

View File

@@ -17,8 +17,10 @@ function useFoo() {
## Error
```
Found 2 errors:
Error: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-hook-function-argument-mutates-local-variable.ts:5:10
3 | function useFoo() {
@@ -28,23 +30,18 @@ error.invalid-hook-function-argument-mutates-local-variable.ts:5:10
> 6 | cache.set('key', 'value');
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 7 | });
| ^^^^ This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^^^ This function may (indirectly) reassign or modify local variables after render
8 | }
9 |
Error: The function modifies a local variable here
error.invalid-hook-function-argument-mutates-local-variable.ts:6:4
4 | const cache = new Map();
5 | useHook(() => {
> 6 | cache.set('key', 'value');
| ^^^^^ The function modifies a local variable here
| ^^^^^ This modifies a local variable
7 | });
8 | }
9 |
```

View File

@@ -47,20 +47,18 @@ function Component() {
```
Found 1 error:
Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
Error: Cannot reassign a variable after render completes
Variable `local` cannot be reassigned after render.
Reassigning variable `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-nested-function-reassign-local-variable-in-effect.ts:7:6
5 | // Create the reassignment function inside another function, then return it
6 | const reassignLocal = newValue => {
> 7 | local = newValue;
| ^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^^^^ Cannot reassign variable after render completes
8 | };
9 | return reassignLocal;
10 | };
```

View File

@@ -17,30 +17,27 @@ function Component() {
## Error
```
Found 2 errors:
Error: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-pass-mutable-function-as-prop.ts:7:18
5 | cache.set('key', 'value');
6 | };
> 7 | return <Foo fn={fn} />;
| ^^ This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^ This function may (indirectly) reassign or modify local variables after render
8 | }
9 |
Error: The function modifies a local variable here
error.invalid-pass-mutable-function-as-prop.ts:5:4
3 | const cache = new Map();
4 | const fn = () => {
> 5 | cache.set('key', 'value');
| ^^^^^ The function modifies a local variable here
| ^^^^^ This modifies a local variable
6 | };
7 | return <Foo fn={fn} />;
8 | }
```

View File

@@ -16,20 +16,18 @@ function useFoo() {
```
Found 1 error:
Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
Error: Cannot reassign a variable after render completes
Variable `x` cannot be reassigned after render.
Reassigning variable `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-reassign-local-in-hook-return-value.ts:4:4
2 | let x = 0;
3 | return value => {
> 4 | x = value;
| ^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^ Cannot reassign variable after render completes
5 | };
6 | }
7 |
```

View File

@@ -48,20 +48,18 @@ function Component() {
```
Found 1 error:
Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
Error: Cannot reassign a variable after render completes
Variable `local` cannot be reassigned after render.
Reassigning variable `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-reassign-local-variable-in-effect.ts:7:4
5 |
6 | const reassignLocal = newValue => {
> 7 | local = newValue;
| ^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^^^^ Cannot reassign variable after render completes
8 | };
9 |
10 | const onMount = newValue => {
```

View File

@@ -49,20 +49,18 @@ function Component() {
```
Found 1 error:
Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
Error: Cannot reassign a variable after render completes
Variable `local` cannot be reassigned after render.
Reassigning variable `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-reassign-local-variable-in-hook-argument.ts:8:4
6 |
7 | const reassignLocal = newValue => {
> 8 | local = newValue;
| ^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^^^^ Cannot reassign variable after render completes
9 | };
10 |
11 | const callback = newValue => {
```

View File

@@ -42,20 +42,18 @@ function Component() {
```
Found 1 error:
Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
Error: Cannot reassign a variable after render completes
Variable `local` cannot be reassigned after render.
Reassigning variable `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-reassign-local-variable-in-jsx-callback.ts:5:4
3 |
4 | const reassignLocal = newValue => {
> 5 | local = newValue;
| ^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^^^^ Cannot reassign variable after render completes
6 | };
7 |
8 | const onClick = newValue => {
```

View File

@@ -19,8 +19,10 @@ function useFoo() {
## Error
```
Found 2 errors:
Error: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-return-mutable-function-from-hook.ts:7:9
5 | useHook(); // for inference to kick in
@@ -30,23 +32,18 @@ error.invalid-return-mutable-function-from-hook.ts:7:9
> 8 | cache.set('key', 'value');
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 9 | };
| ^^^^ This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^^^ This function may (indirectly) reassign or modify local variables after render
10 | }
11 |
Error: The function modifies a local variable here
error.invalid-return-mutable-function-from-hook.ts:8:4
6 | const cache = new Map();
7 | return () => {
> 8 | cache.set('key', 'value');
| ^^^^^ The function modifies a local variable here
| ^^^^^ This modifies a local variable
9 | };
10 | }
11 |
```

View File

@@ -27,18 +27,18 @@ function useKeyedState({key, init}) {
```
Found 1 error:
Error: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)
Error: Calling setState from useMemo may trigger an infinite loop
Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)
error.invalid-setState-in-useMemo-indirect-useCallback.ts:13:4
11 |
12 | useMemo(() => {
> 13 | fn();
| ^^ Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^ Found setState() within useMemo()
14 | }, [key, init]);
15 |
16 | return state;
```

View File

@@ -21,30 +21,30 @@ function useKeyedState({key, init}) {
```
Found 2 errors:
Error: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)
Error: Calling setState from useMemo may trigger an infinite loop
Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)
error.invalid-setState-in-useMemo.ts:6:4
4 |
5 | useMemo(() => {
> 6 | setPrevKey(key);
| ^^^^^^^^^^ Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^^^^^^^^^ Found setState() within useMemo()
7 | setState(init);
8 | }, [key, init]);
9 |
Error: Calling setState from useMemo may trigger an infinite loop
Error: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)
Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)
error.invalid-setState-in-useMemo.ts:7:4
5 | useMemo(() => {
6 | setPrevKey(key);
> 7 | setState(init);
| ^^^^^^^^ Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^^^^^^^ Found setState() within useMemo()
8 | }, [key, init]);
9 |
10 | return state;
```

View File

@@ -47,8 +47,10 @@ hook useMemoMap<TInput: interface {}, TOutput>(
## Error
```
Found 2 errors:
Error: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
undefined:21:9
19 | map: TInput => TOutput
@@ -86,23 +88,18 @@ undefined:21:9
> 36 | };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 37 | }, [map]);
| ^^^^^^^^^^^^ This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^^^^^^^^^^^ This function may (indirectly) reassign or modify local variables after render
38 | }
39 |
Error: The function modifies a local variable here
undefined:33:8
31 | if (output == null) {
32 | output = map(input);
> 33 | cache.set(input, output);
| ^^^^^ The function modifies a local variable here
| ^^^^^ This modifies a local variable
34 | }
35 | return output;
36 | };
```

View File

@@ -20,30 +20,30 @@ function Component(props) {
```
Found 2 errors:
Error: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
Error: Calling setState during render may trigger an infinite loop
Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)
error.invalid-unconditional-set-state-in-render.ts:6:2
4 | const aliased = setX;
5 |
> 6 | setX(1);
| ^^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^^^ Found setState() within useMemo()
7 | aliased(2);
8 |
9 | return x;
Error: Calling setState during render may trigger an infinite loop
Error: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)
error.invalid-unconditional-set-state-in-render.ts:7:2
5 |
6 | setX(1);
> 7 | aliased(2);
| ^^^^^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^^^^^^ Found setState() within useMemo()
8 |
9 | return x;
10 | }
```

View File

@@ -18,6 +18,8 @@ function component(a, b) {
Found 1 error:
Error: useMemo callbacks may not be async or generator functions
useMemo() callbacks are called once and must synchronously return a value
error.invalid-useMemo-async-callback.ts:2:18
1 | function component(a, b) {
> 2 | let x = useMemo(async () => {
@@ -25,12 +27,10 @@ error.invalid-useMemo-async-callback.ts:2:18
> 3 | await a;
| ^^^^^^^^^^^^
> 4 | }, []);
| ^^^^ useMemo callbacks may not be async or generator functions
| ^^^^ Async and generator functions are not supported
5 | return x;
6 | }
7 |
```

View File

@@ -14,17 +14,17 @@ function component(a, b) {
```
Found 1 error:
Error: useMemo callbacks may not accept any arguments
Error: useMemo() callbacks may not accept parameters
useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.
error.invalid-useMemo-callback-args.ts:2:18
1 | function component(a, b) {
> 2 | let x = useMemo(c => a, []);
| ^^^^^^ useMemo callbacks may not accept any arguments
| ^
3 | return x;
4 | }
5 |
```

View File

@@ -33,20 +33,18 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
Error: Cannot reassign a variable after render completes
Variable `a` cannot be reassigned after render.
Reassigning variable `a` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.mutable-range-shared-inner-outer-function.ts:8:6
6 | const f = () => {
7 | if (cond) {
> 8 | a = {};
| ^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^ Cannot reassign variable after render completes
9 | b = [];
10 | } else {
11 | a = {};
```

View File

@@ -18,20 +18,18 @@ function Component() {
```
Found 1 error:
Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
Error: Cannot reassign a variable after render completes
Variable `onClick` cannot be reassigned after render.
Reassigning variable `onClick` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.todo-function-expression-references-later-variable-declaration.ts:3:4
1 | function Component() {
2 | let callback = () => {
> 3 | onClick = () => {};
| ^^^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^^^^^^ Cannot reassign variable after render completes
4 | };
5 | let onClick;
6 |
```

View File

@@ -23,18 +23,18 @@ function Component(props) {
```
Found 1 error:
Error: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
Error: Calling setState during render may trigger an infinite loop
Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)
error.unconditional-set-state-in-render-after-loop-break.ts:11:2
9 | }
10 | }
> 11 | setState(true);
| ^^^^^^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^^^^^^^ Found setState() within useMemo()
12 | return state;
13 | }
14 |
```

View File

@@ -18,18 +18,18 @@ function Component(props) {
```
Found 1 error:
Error: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
Error: Calling setState during render may trigger an infinite loop
Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)
error.unconditional-set-state-in-render-after-loop.ts:6:2
4 | for (const _ of props) {
5 | }
> 6 | setState(true);
| ^^^^^^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^^^^^^^ Found setState() within useMemo()
7 | return state;
8 | }
9 |
```

View File

@@ -23,18 +23,18 @@ function Component(props) {
```
Found 1 error:
Error: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
Error: Calling setState during render may trigger an infinite loop
Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)
error.unconditional-set-state-in-render-with-loop-throw.ts:11:2
9 | }
10 | }
> 11 | setState(true);
| ^^^^^^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^^^^^^^ Found setState() within useMemo()
12 | return state;
13 | }
14 |
```

View File

@@ -21,18 +21,18 @@ function Component(props) {
```
Found 1 error:
Error: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
Error: Calling setState during render may trigger an infinite loop
Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)
error.unconditional-set-state-lambda.ts:8:2
6 | setX(1);
7 | };
> 8 | foo();
| ^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^^ Found setState() within useMemo()
9 |
10 | return [x];
11 | }
```

View File

@@ -29,18 +29,18 @@ function Component(props) {
```
Found 1 error:
Error: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
Error: Calling setState during render may trigger an infinite loop
Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)
error.unconditional-set-state-nested-function-expressions.ts:16:2
14 | bar();
15 | };
> 16 | baz();
| ^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)
| ^^^ Found setState() within useMemo()
17 |
18 | return [x];
19 | }
```

View File

@@ -65,7 +65,7 @@ function Component(props) {
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"}}},"fnLoc":null}
{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","details":[{"kind":"error","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":17,"column":1,"index":298},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0}
```

View File

@@ -42,7 +42,7 @@ function Component(props) {
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"}}},"fnLoc":null}
{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","details":[{"kind":"error","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":49},"end":{"line":10,"column":1,"index":160},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
```

View File

@@ -65,7 +65,7 @@ function _temp(s) {
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null}
{"kind":"CompileError","detail":{"options":{"category":"Calling setState within an effect can trigger cascading renders","description":"Calling setState directly within a useEffect causes cascading renders that can hurt performance, and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() in the top-level of an effect"}]}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":16,"column":1,"index":293},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0}
```

View File

@@ -45,7 +45,7 @@ function _temp(s) {
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null}
{"kind":"CompileError","detail":{"options":{"category":"Calling setState within an effect can trigger cascading renders","description":"Calling setState directly within a useEffect causes cascading renders that can hurt performance, and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() in the top-level of an effect"}]}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":10,"column":1,"index":225},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
```

View File

@@ -43,20 +43,18 @@ function Component() {
```
Found 1 error:
Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
Error: Cannot reassign a variable after render completes
Variable `local` cannot be reassigned after render.
Reassigning variable `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-reassign-local-variable-in-jsx-callback.ts:6:4
4 |
5 | const reassignLocal = newValue => {
> 6 | local = newValue;
| ^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
| ^^^^^ Cannot reassign variable after render completes
7 | };
8 |
9 | const onClick = newValue => {
```