Compare commits

...

20 Commits

Author SHA1 Message Date
Joe Savona
f98a62f89c [compiler] cleanup lints 2025-08-11 15:25:52 -07:00
Mofei Zhang
8419fbcda3 [compiler] Standarize CompilerDiagnostic descriptions (trailing periods)
Separated this out into its own PR since it's really a cosmetic change.

CompilerErrorDetail and CompilerDiagnostic now both expect `reason`/`category` and `description` to *not* have trailing periods.
2025-08-11 15:25:50 -07:00
Mofei Zhang
8c0829bda4 [compiler] Aggregate error reporting, separate eslint rules
All error messages that are not invariants, todos, or config validation errors are now moved to a registry.  This helps us check that error messages and linked documentation is uniform and up to date. This also lets us link error codes to linter reporting categories and break the single `react-compiler` rule up into many smaller rules.


Changes to error reporting in React Compiler:
- instead of writing raw error message, add a new ErrorCode to `CompilerErrorCodes`
- create errors by constructing a `CompilerErrorDetailOptions` with the correct error code, or calling `CompilerErrorDetail.fromCode()` / `CompilerDiagnostic.fromCode()`.

Changes to linter:
- `react-compiler` lint rule is now broken up into many smaller lint rules, each with their own test cases.
- linting no longer fails early when a non-fatal error is find. Instead, we now log and move on to lint for more errors!

Todos:
- Codebases previously using the eslint plugin may already have `react-compiler` eslint suppressions. Since this PR removes this rule, these codebases may get new eslint warnings on already-suppressed ranges. Some options I can think of:
  - Force everyone to add the new suppressions
  - Release a script to rewrite previous suppressions to the new suppressions. Codebases that had incomplete suppressions / waited to update may benefit here, as this still surfaces errors on previously-unseen suppressions
  - Hard code logic in our eslint plugin to avoid reporting if we see a `eslint-disable-next-line react-compiler`. This doesn't feel ideal
- Align on eslint plugin rule names and error code mappings
  - I aggregated a few error codes here. For example, reassigning and modifying local variables after render now have the same error code + message. See changes fixtures for more examples
- More test cases for linter rules. Will add these once lint rules are more finalized
2025-08-11 13:48:58 -07:00
Sebastian "Sebbie" Silbermann
ac7820a99e Create fresh Offscreen instance when replaying (#34127) 2025-08-11 20:55:48 +02:00
Sebastian Markbåge
3c67bbe5f9 [DevTools] Track suspensey CSS on "suspended by" (#34166)
We need to track that Suspensey CSS (Host Resources) can contribute to
the loading state. We can pick up the start/end time from the
Performance Observer API since we know which resource was loaded.

If DOM nodes are not filtered there's a link to the `<link>` instance.
The `"awaited by"` stack is the callsite of the JSX creating the
`<link>`.

<img width="591" height="447" alt="Screenshot 2025-08-11 at 1 35 21 AM"
src="https://github.com/user-attachments/assets/63af0ca9-de8d-4c74-a797-af0a009b5d73"
/>

Inspecting the link itself:

<img width="592" height="344" alt="Screenshot 2025-08-11 at 1 31 43 AM"
src="https://github.com/user-attachments/assets/89603dbc-6721-4bbf-8b58-6010719b29e3"
/>

In this approach I only include it if the page currently matches the
media query. It might contribute in some other scenario but we're not
showing every possible state but every possible scenario that might
suspend if timing changes in the current state.
2025-08-11 12:28:32 -04:00
Sebastian Markbåge
2c9a42dfd7 [DevTools] If the await doesn't have a stack use the stack from use() if any (#34162)
Stacked on #34148.

This picks up the stack for the await from the `use()` Hook if one was
used to get this async info.

When you select a component that used hooks, we already collect this
information.

If you select a Suspense boundary, this lazily invokes the first
component that awaited this data to inspects its hooks and produce a
stack trace for the use().

When all we have for the name is "Promise" I also use the name of the
first callsite in the stack trace if there's more than one. Which in
practice will be the name of the custom Hook that called it. Ideally
we'd use source mapping and ignore listing for this but that would
require suspending the display. We could maybe make the SuspendedByRow
wrapped in a Suspense boundary for this case.

<img width="438" height="401" alt="Screenshot 2025-08-10 at 10 07 55 PM"
src="https://github.com/user-attachments/assets/2a68917d-c27b-4c00-84aa-0ceb51c4e541"
/>
2025-08-11 12:28:10 -04:00
Jan Kassens
f1e70b5e0a [easy] remove leftover reference to disableDefaultPropsExceptForClasses (#34169)
Noticed that I missed this in some earlier cleanup diff.

Test Plan:
grep for disableDefaultPropsExceptForClasses
2025-08-11 12:13:33 -04:00
Sebastian Markbåge
d587434c35 [DevTools] Pick up suspended by info from use() (#34148)
Similar to #34144 but for `use()`.

`use()` dependencies don't get added to the `fiber._debugInfo` set
because that just models the things blocking the children, and not the
Fiber component itself. This picks up any debug info from the thenable
state that we stashed onto `_debugThenableState` so that we know it used
`use()`.

<img width="593" height="425" alt="Screenshot 2025-08-09 at 4 03 40 PM"
src="https://github.com/user-attachments/assets/c7e06884-4efd-47fa-a76b-132935db6ddc"
/>

Without #34146 this doesn't pick up uninstrumented promises but after
it, it'll pick those up as well. An instrumented promise that doesn't
have anything in its debug info is not picked up. For example, if it
didn't depend on any I/O on the server.

This doesn't yet pick up the stack trace of the `use()` call. That
information is in the Hooks information but needs a follow up to extract
it.
2025-08-11 12:10:05 -04:00
Sebastian Markbåge
ca292f7a57 [DevTools] Don't show "awaited by" if there's nothing to show (#34163)
E.g. if the owner is null or the same as current component and no stack.
This happens for example when you return a plain Promise in the child
position and inspect the component it was returned in since there's no
hook stack and the owner is the same as the instance itself so there's
nothing new to link to.

Before:

<img width="267" height="99" alt="Screenshot 2025-08-10 at 10 28 32 PM"
src="https://github.com/user-attachments/assets/23341ab2-2888-457d-a1d1-128f3e0bd5ec"
/>

After:

<img width="253" height="91" alt="Screenshot 2025-08-10 at 10 29 04 PM"
src="https://github.com/user-attachments/assets/b33bb38b-891a-4f46-bc16-15604b033cdb"
/>
2025-08-11 11:48:09 -04:00
Sebastian Markbåge
62a634b972 [DebugTools] Use thenables from the _debugThenableState if available (#34161)
In the case where a Promise is not cached, then the thenable state might
contain an older version. This version is the one that was actually
observed by the committed render, so that's the version we'll want to
inspect.

We used to not store the thenable state but now we have it on
`_debugThenableState` in DEV.

<img width="593" height="359" alt="Screenshot 2025-08-10 at 8 26 04 PM"
src="https://github.com/user-attachments/assets/51ee53f3-a31a-4e3f-a4cf-bb20b6efe0cb"
/>
2025-08-11 11:46:27 -04:00
Sebastian Markbåge
53d07944df [Fiber] Assign implicit debug info to used thenables (#34146)
Similar to #34137 but for Promises.

This lets us pick up the debug info from a raw Promise as a child which
is not covered by `_debugThenables`. Currently ChildFiber doesn't stash
its thenables so we can't pick them up from devtools after the fact
without some debug info added to the parent.

It also lets us track some approximate start/end time of use():ed
promises based on the first time we saw this particular Promise.
2025-08-11 11:44:05 -04:00
Sebastian Markbåge
34ce3acafd [DevTools] Pick up suspended by info from React.lazy in type position (#34144)
Normally, we pick up debug info from instrumented Promise or React.Lazy
while we're reconciling in ReactChildFiber when they appear in the child
position. We add those to the `_debugInfo` of the Fiber.

However, we don't do that for for Lazy in the Component type position.
Instead, we have to pick up the debug info from it explicitly in
DevTools. Likely this is the info added by #34137. Older versions
wouldn't be covered by this particular mechanism but more generally from
throwing a Promise.


<img width="592" height="449" alt="Screenshot 2025-08-08 at 11 32 33 PM"
src="https://github.com/user-attachments/assets/87211c64-a7df-47b7-a784-5cdc7c5fae16"
/>
2025-08-11 11:42:59 -04:00
Sebastian Markbåge
6445b3154e [Fiber] Add additional debugInfo to React.lazy constructors in DEV (#34137)
This creates a debug info object for the React.lazy call when it's
called on the client. We have some additional information we can track
for these since they're created by React earlier.

We can track the stack trace where `React.lazy` was called to associate
it back to something useful. We can track the start time when we
initialized it for the first time and the end time when it resolves. The
name from the promise if available.

This data is currently only picked up in child position and not
component position. The component position is in a follow up.

<img width="592" height="451" alt="Screenshot 2025-08-08 at 2 49 33 PM"
src="https://github.com/user-attachments/assets/913d2629-6df5-40f6-b036-ae13631379b9"
/>

This begs for ignore listing in the front end since these stacks aren't
filtered on the server.
2025-08-11 11:42:23 -04:00
Sebastian Markbåge
ab5238d5a4 [DevTools] Show name prop of Suspense / Activity in the Components Tree view (#34135)
The name prop will be used in the Suspense tab to help identity a
boundary. Activity will also allow names. A custom component can be
identified by the name of the component but built-ins doesn't have that.

This PR adds it to the Components Tree View as well since otherwise you
only have the key to go on. Normally we don't add all the props to avoid
making this view too noisy but this is an exception along with key to
help identify a boundary quickly in the tree.

Unlike the SuspenseNode store, this wouldn't ever have a name inferred
by owner since that kind of context already exists in this view.

<img width="600" height="161" alt="Screenshot 2025-08-08 at 1 20 36 PM"
src="https://github.com/user-attachments/assets/fe50d624-887a-4b9d-9186-75f131f83195"
/>

I also made both the key and name prop searchable.

<img width="608" height="206" alt="Screenshot 2025-08-08 at 1 32 27 PM"
src="https://github.com/user-attachments/assets/d3502d9c-7614-45fc-b973-57f06dd9cddc"
/>
2025-08-11 11:41:46 -04:00
Sebastian Markbåge
7a934a16b8 [DevTools] Show Owner Stacks in "rendered by" View (#34130)
This shows the stack trace of the JSX at each level so now you can also
jump to the code location for the JSX callsite. The visual is similar to
the owner stacks with `createTask` except when you click the `<...>` you
jump to the Instance in the Components panel.

<img width="593" height="450" alt="Screenshot 2025-08-08 at 12 19 21 AM"
src="https://github.com/user-attachments/assets/dac35faf-9d99-46ce-8b41-7c6fe24625d2"
/>

I'm not sure it's really necessary to have all the JSX stacks of every
owner. We could just have it for the current component and then the rest
of the owners you could get to if you just click that owner instance.

As a bonus, I also use the JSX callsite as the fallback for the "View
Source" button. This is primarily useful for built-ins like `<div>` and
`<Suspense>` that don't have any implementation to jump to anyway. It's
useful to be able to jump to where a boundary was defined.
2025-08-11 11:41:30 -04:00
Sebastian Markbåge
59ef3c4baf [DevTools] Allow Introspection of React Elements and React.lazy (#34129)
With RSC it's common to get React.lazy objects in the children position.
This first formats them nicely.

Then it adds introspection support for both lazy and elements.

Unfortunately because of quirks with the hydration mechanism we have to
expose it under the name `_payload` instead of something direct. Also
because the name "type" is taken we can't expose the type field on an
element neither. That whole algorithm could use a rewrite.

<img width="422" height="137" alt="Screenshot 2025-08-07 at 11 37 03 PM"
src="https://github.com/user-attachments/assets/a6f65f58-dbc4-4b8f-928b-d7f629fc51b2"
/>

<img width="516" height="275" alt="Screenshot 2025-08-07 at 11 36 36 PM"
src="https://github.com/user-attachments/assets/650bafdb-a633-4d78-9487-a750a18074ce"
/>

For JSX an alternative or additional feature might be instead to jump to
the first Instance that was rendered using that JSX. We know that based
on the equality of the memoizedProps on the Fiber. It's just a matter of
whether we do that eagerly or more lazily when you click but you may not
have a match so would be nice to indicate that before you click.
2025-08-11 11:41:14 -04:00
Sebastian "Sebbie" Silbermann
72965f3615 [DevTools] Restore reconciling Suspense stack after fallback was reconciled (#34168) 2025-08-11 17:12:39 +02:00
Sebastian Markbåge
594fb5e9ab [DevTools] Always skip 1 frame (#34167)
Follow up to #34093.

There's an issue where the skipFrames argument isn't part of the cache
key so the other parsers that expect skipping one frame might skip zero
and show the internal `fakeJSXDEV` callsite. Ideally we should include
the skipFrames as part of the cache key but we can also always just skip
one.
2025-08-11 01:50:26 -04:00
Sebastian "Sebbie" Silbermann
98286cf8e3 [DevTools] Send suspense nodes to frontend store (#34070) 2025-08-10 10:12:20 +02:00
Sophie Alpert
cf6e502ed2 Hot reloading: Avoid stack overflow on wide trees (#34145)
Every sibling added to the stack here. Not sure this needs to be
recursive at all but certainly for siblings this can just be a loop.
2025-08-09 08:02:22 -07:00
219 changed files with 5843 additions and 1942 deletions

View File

@@ -10,48 +10,22 @@ import {codeFrameColumns} from '@babel/code-frame';
import type {SourceLocation} from './HIR';
import {Err, Ok, Result} from './Utils/Result';
import {assertExhaustive} from './Utils/utils';
export enum ErrorSeverity {
/**
* Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some
* misunderstanding on the users part.
*/
InvalidJS = 'InvalidJS',
/**
* JS syntax that is not supported and which we do not plan to support. Developers should
* rewrite to use supported forms.
*/
UnsupportedJS = 'UnsupportedJS',
/**
* Code that breaks the rules of React.
*/
InvalidReact = 'InvalidReact',
/**
* Incorrect configuration of the compiler.
*/
InvalidConfig = 'InvalidConfig',
/**
* Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve
* memoization.
*/
CannotPreserveMemoization = 'CannotPreserveMemoization',
/**
* Unhandled syntax that we don't support yet.
*/
Todo = 'Todo',
/**
* An unexpected internal error in the compiler that indicates critical issues that can panic
* the compiler.
*/
Invariant = 'Invariant',
}
import {ErrorSeverity} from './Utils/CompilerErrorSeverity';
import {
ErrorCode,
ErrorCodeDetails,
LinterCategory,
} from './Utils/CompilerErrorCodes';
export {ErrorSeverity};
export {ErrorCode, ErrorCodeDetails, LinterCategory};
export type CompilerDiagnosticOptions = {
severity: ErrorSeverity;
category: string;
description: string;
description?: string | null | undefined;
details: Array<CompilerDiagnosticDetail>;
suggestions?: Array<CompilerSuggestion> | null | undefined;
linterCategory?: LinterCategory | null | undefined;
};
export type CompilerDiagnosticDetail =
@@ -86,13 +60,28 @@ export type CompilerSuggestion =
description: string;
};
export type CompilerErrorDetailOptions = {
export type PlainCompilerErrorDetailOptions = {
errorCode?: void;
reason: string;
description?: string | null | undefined;
severity: ErrorSeverity;
severity:
| ErrorSeverity.Invariant
| ErrorSeverity.Todo
| ErrorSeverity.InvalidConfig;
loc: SourceLocation | null;
suggestions?: Array<CompilerSuggestion> | null | undefined;
};
export type CodedCompilerErrorDetailOptions = {
errorCode: ErrorCode;
description?: string | null | undefined;
loc: SourceLocation | null;
suggestions?: Array<CompilerSuggestion> | null | undefined;
linterCategory?: LinterCategory | null | undefined;
};
export type CompilerErrorDetailOptions =
| PlainCompilerErrorDetailOptions
| CodedCompilerErrorDetailOptions;
export type PrintErrorMessageOptions = {
/**
@@ -102,19 +91,72 @@ export type PrintErrorMessageOptions = {
eslint: boolean;
};
export function makeCompilerDiagnostic(
code: ErrorCode,
options?: {
description?: string;
suggestions?: Array<CompilerSuggestion> | null | undefined;
},
): CompilerDiagnostic {
return makeCompilerDiagnostic(code, options);
}
export class CompilerDiagnostic {
options: CompilerDiagnosticOptions;
constructor(options: CompilerDiagnosticOptions) {
/**
* Constructor is private to enforce that we either only create invariant diagnostics
* or use ErrorCodes
*/
private constructor(options: CompilerDiagnosticOptions) {
this.options = options;
}
static create(
options: Omit<CompilerDiagnosticOptions, 'details'>,
): CompilerDiagnostic {
static create<
T extends CompilerDiagnosticOptions & {severity: ErrorSeverity.Invariant},
>(options: Omit<T, 'details'>): CompilerDiagnostic {
return new CompilerDiagnostic({...options, details: []});
}
static fromCode(
code: ErrorCode,
options?: {
description?: string;
suggestions?: Array<CompilerSuggestion> | null | undefined;
details?: Array<CompilerDiagnosticDetail> | null | undefined;
},
): CompilerDiagnostic {
const errorEntry = ErrorCodeDetails[code];
let description = undefined;
if (errorEntry.description != null) {
description = endWithPeriod(errorEntry.description);
}
if (options?.description != null && options.description.length > 0) {
if (description != null && description.length > 0) {
description += ' ';
} else {
description = '';
}
description += endWithPeriod(options.description);
}
const diagnosticOptions: CompilerDiagnosticOptions = {
severity: errorEntry.severity,
category: errorEntry.reason,
description,
linterCategory: errorEntry.linterCategory,
suggestions: options?.suggestions,
details: options?.details ?? [],
};
return new CompilerDiagnostic(diagnosticOptions);
}
// TODO: remove after converting test fixtures to use printErrorMessage
serialize(): unknown {
return {options: {...this.options, linterCategory: undefined}};
}
get category(): CompilerDiagnosticOptions['category'] {
return this.options.category;
}
@@ -127,6 +169,9 @@ export class CompilerDiagnostic {
get suggestions(): CompilerDiagnosticOptions['suggestions'] {
return this.options.suggestions;
}
get linterCategory(): CompilerDiagnosticOptions['linterCategory'] {
return this.options.linterCategory;
}
withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic {
this.options.details.push(detail);
@@ -138,11 +183,10 @@ export class CompilerDiagnostic {
}
printErrorMessage(source: string, options: PrintErrorMessageOptions): string {
const buffer = [
printErrorSummary(this.severity, this.category),
'\n\n',
this.description,
];
const buffer = [printErrorSummary(this.severity, this.category)];
if (this.description != null) {
buffer.push(`\n\n${endWithPeriod(this.description)}`);
}
for (const detail of this.options.details) {
switch (detail.kind) {
case 'error': {
@@ -181,7 +225,7 @@ export class CompilerDiagnostic {
toString(): string {
const buffer = [printErrorSummary(this.severity, this.category)];
if (this.description != null) {
buffer.push(`. ${this.description}.`);
buffer.push(`. ${endWithPeriod(this.description)}`);
}
const loc = this.primaryLocation();
if (loc != null && typeof loc !== 'symbol') {
@@ -202,13 +246,68 @@ export class CompilerErrorDetail {
this.options = options;
}
get reason(): CompilerErrorDetailOptions['reason'] {
return this.options.reason;
static fromCode(
code: ErrorCode,
details?: {
description?: string | null;
loc?: SourceLocation | null;
suggestions?: Array<CompilerSuggestion> | null | undefined;
},
): CompilerErrorDetail {
return new CompilerErrorDetail({
...details,
errorCode: code,
} as CodedCompilerErrorDetailOptions);
}
get description(): CompilerErrorDetailOptions['description'] {
return this.options.description;
// TODO: remove after converting test fixtures to use printErrorMessage
serialize(): unknown {
return {
options: {
reason: this.reason,
description: this.description,
severity: this.severity,
loc: this.loc,
suggestions: this.suggestions,
},
};
}
get severity(): CompilerErrorDetailOptions['severity'] {
get reason(): string {
if (this.options.errorCode != null) {
return ErrorCodeDetails[this.options.errorCode].reason;
} else {
return this.options.reason;
}
}
get description(): string | null | undefined {
if (this.options.errorCode != null) {
let description = undefined;
const errorEntry = ErrorCodeDetails[this.options.errorCode];
if (errorEntry.description != null) {
description = endWithPeriod(errorEntry.description);
}
if (
this.options.description != null &&
this.options.description.length > 0
) {
if (description != null && description.length > 0) {
description += ' ';
} else {
description = '';
}
description += endWithPeriod(this.options.description);
}
return description;
}
return this.options.description != null
? endWithPeriod(this.options.description)
: this.options.description;
}
get severity(): ErrorSeverity {
if (this.options.errorCode != null) {
return ErrorCodeDetails[this.options.errorCode].severity;
}
return this.options.severity;
}
get loc(): CompilerErrorDetailOptions['loc'] {
@@ -217,6 +316,12 @@ export class CompilerErrorDetail {
get suggestions(): CompilerErrorDetailOptions['suggestions'] {
return this.options.suggestions;
}
get linterCategory(): LinterCategory | null | undefined {
if (this.options.errorCode != null) {
return ErrorCodeDetails[this.options.errorCode].linterCategory;
}
return undefined;
}
primaryLocation(): SourceLocation | null {
return this.loc;
@@ -225,7 +330,7 @@ export class CompilerErrorDetail {
printErrorMessage(source: string, options: PrintErrorMessageOptions): string {
const buffer = [printErrorSummary(this.severity, this.reason)];
if (this.description != null) {
buffer.push(`\n\n${this.description}.`);
buffer.push(`\n\n${endWithPeriod(this.description)}`);
}
const loc = this.loc;
if (loc != null && typeof loc !== 'symbol') {
@@ -250,7 +355,7 @@ export class CompilerErrorDetail {
toString(): string {
const buffer = [printErrorSummary(this.severity, this.reason)];
if (this.description != null) {
buffer.push(`. ${this.description}.`);
buffer.push(`. ${endWithPeriod(this.description)}`);
}
const loc = this.loc;
if (loc != null && typeof loc !== 'symbol') {
@@ -266,7 +371,7 @@ export class CompilerError extends Error {
static invariant(
condition: unknown,
options: Omit<CompilerErrorDetailOptions, 'severity'>,
options: Omit<PlainCompilerErrorDetailOptions, 'severity'>,
): asserts condition {
if (!condition) {
const errors = new CompilerError();
@@ -280,14 +385,14 @@ export class CompilerError extends Error {
}
}
static throwDiagnostic(options: CompilerDiagnosticOptions): never {
static throwDiagnostic(diagnostic: CompilerDiagnostic): never {
const errors = new CompilerError();
errors.pushDiagnostic(new CompilerDiagnostic(options));
errors.pushDiagnostic(diagnostic);
throw errors;
}
static throwTodo(
options: Omit<CompilerErrorDetailOptions, 'severity'>,
options: Omit<PlainCompilerErrorDetailOptions, 'severity'>,
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
@@ -296,34 +401,21 @@ export class CompilerError extends Error {
throw errors;
}
static throwInvalidJS(
options: Omit<CompilerErrorDetailOptions, 'severity'>,
static throwFromCode(
code: ErrorCode,
options?: {
description?: string | null;
loc?: SourceLocation | null;
suggestions?: Array<CompilerSuggestion> | null | undefined;
},
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
...options,
severity: ErrorSeverity.InvalidJS,
}),
);
throw errors;
}
static throwInvalidReact(
options: Omit<CompilerErrorDetailOptions, 'severity'>,
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
...options,
severity: ErrorSeverity.InvalidReact,
}),
);
errors.pushErrorDetail(CompilerErrorDetail.fromCode(code, options));
throw errors;
}
static throwInvalidConfig(
options: Omit<CompilerErrorDetailOptions, 'severity'>,
options: Omit<PlainCompilerErrorDetailOptions, 'severity'>,
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
@@ -392,13 +484,10 @@ export class CompilerError extends Error {
}
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
const detail = new CompilerErrorDetail({
reason: options.reason,
description: options.description ?? null,
severity: options.severity,
suggestions: options.suggestions,
loc: typeof options.loc === 'symbol' ? null : options.loc,
});
if (options instanceof CompilerErrorDetail) {
return this.pushErrorDetail(options);
}
const detail = new CompilerErrorDetail(options);
return this.pushErrorDetail(detail);
}
@@ -407,6 +496,19 @@ export class CompilerError extends Error {
return detail;
}
pushErrorCode(
code: ErrorCode,
details?: {
description?: string | null;
loc?: SourceLocation | null;
suggestions?: Array<CompilerSuggestion> | null | undefined;
},
): CompilerErrorDetail {
const detail = CompilerErrorDetail.fromCode(code, details);
this.details.push(detail);
return detail;
}
hasErrors(): boolean {
return this.details.length > 0;
}
@@ -493,3 +595,10 @@ function printErrorSummary(severity: ErrorSeverity, message: string): string {
}
return `${severityCategory}: ${message}`;
}
function endWithPeriod(s: string): string {
if (s === '' || s.endsWith('.')) {
return s;
}
return `${s.replace(/\.+$/, '')}.`;
}

View File

@@ -38,10 +38,10 @@ export function validateRestrictedImports(
ImportDeclaration(importDeclPath) {
if (restrictedImports.has(importDeclPath.node.source.value)) {
error.push({
severity: ErrorSeverity.Todo,
reason: 'Bailing out due to blocklisted import',
description: `Import from module ${importDeclPath.node.source.value}`,
loc: importDeclPath.node.loc ?? null,
severity: ErrorSeverity.Todo,
});
}
},
@@ -205,10 +205,10 @@ export class ProgramContext {
}
const error = new CompilerError();
error.push({
severity: ErrorSeverity.Todo,
reason: 'Encountered conflicting global in generated program',
description: `Conflict from local binding ${name}`,
loc: scope.getBinding(name)?.path.node.loc ?? null,
severity: ErrorSeverity.Todo,
suggestions: null,
});
return Err(error);

View File

@@ -11,7 +11,7 @@ import {
CompilerDiagnostic,
CompilerError,
CompilerErrorDetail,
CompilerErrorDetailOptions,
PlainCompilerErrorDetailOptions,
} from '../CompilerError';
import {
EnvironmentConfig,
@@ -107,6 +107,8 @@ export type PluginOptions = {
* passes.
*
* Defaults to false
*
* TODO: rename this to lintOnly or something similar
*/
noEmit: boolean;
@@ -234,7 +236,10 @@ export type CompileErrorEvent = {
export type CompileDiagnosticEvent = {
kind: 'CompileDiagnostic';
fnLoc: t.SourceLocation | null;
detail: Omit<Omit<CompilerErrorDetailOptions, 'severity'>, 'suggestions'>;
detail: Omit<
Omit<PlainCompilerErrorDetailOptions, 'severity'>,
'suggestions'
>;
};
export type CompileSuccessEvent = {
kind: 'CompileSuccess';

View File

@@ -100,7 +100,6 @@ import {outlineJSX} from '../Optimization/OutlineJsx';
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
import {transformFire} from '../Transform';
import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender';
import {CompilerError} from '..';
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions';
import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
@@ -174,7 +173,7 @@ function runWithEnvironment(
!env.config.disableMemoizationForDebugging &&
!env.config.enableChangeDetectionForDebugging
) {
dropManualMemoization(hir).unwrap();
env.logOrThrowErrors(dropManualMemoization(hir));
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
}
@@ -207,10 +206,10 @@ function runWithEnvironment(
if (env.isInferredMemoEnabled) {
if (env.config.validateHooksUsage) {
validateHooksUsage(hir).unwrap();
env.logOrThrowErrors(validateHooksUsage(hir));
}
if (env.config.validateNoCapitalizedCalls) {
validateNoCapitalizedCalls(hir).unwrap();
if (env.config.validateNoCapitalizedCalls != null) {
env.logOrThrowErrors(validateNoCapitalizedCalls(hir));
}
}
@@ -230,20 +229,17 @@ function runWithEnvironment(
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
if (!env.config.enableNewMutationAliasingModel) {
const fnEffectErrors = inferReferenceEffects(hir);
const fnEffectResult = inferReferenceEffects(hir);
if (env.isInferredMemoEnabled) {
if (fnEffectErrors.length > 0) {
CompilerError.throw(fnEffectErrors[0]);
}
env.logOrThrowErrors(fnEffectResult);
}
log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
} else {
const mutabilityAliasingErrors = inferMutationAliasingEffects(hir);
log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
if (env.isInferredMemoEnabled) {
if (mutabilityAliasingErrors.isErr()) {
throw mutabilityAliasingErrors.unwrapErr();
}
env.logOrThrowErrors(mutabilityAliasingErrors);
}
}
@@ -272,10 +268,8 @@ function runWithEnvironment(
});
log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
if (env.isInferredMemoEnabled) {
if (mutabilityAliasingErrors.isErr()) {
throw mutabilityAliasingErrors.unwrapErr();
}
validateLocalsNotReassignedAfterRender(hir);
env.logOrThrowErrors(mutabilityAliasingErrors.map(() => undefined));
env.logOrThrowErrors(validateLocalsNotReassignedAfterRender(hir));
}
}
@@ -285,15 +279,15 @@ function runWithEnvironment(
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir).unwrap();
env.logOrThrowErrors(validateNoRefAccessInRender(hir));
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir).unwrap();
env.logOrThrowErrors(validateNoSetStateInRender(hir));
}
if (env.config.validateNoDerivedComputationsInEffects) {
validateNoDerivedComputationsInEffects(hir);
env.logOrThrowErrors(validateNoDerivedComputationsInEffects(hir));
}
if (env.config.validateNoSetStateInEffects) {
@@ -303,16 +297,18 @@ function runWithEnvironment(
if (env.config.validateNoJSXInTryStatements) {
env.logErrors(validateNoJSXInTryStatement(hir));
}
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir).unwrap();
if (
env.config.validateNoImpureFunctionsInRender &&
!env.config.enableNewMutationAliasingModel
) {
env.logOrThrowErrors(validateNoImpureFunctionsInRender(hir));
}
if (
env.config.validateNoFreezingKnownMutableFunctions ||
env.config.enableNewMutationAliasingModel
) {
validateNoFreezingKnownMutableFunctions(hir).unwrap();
env.logOrThrowErrors(validateNoFreezingKnownMutableFunctions(hir));
}
}

View File

@@ -7,11 +7,7 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {
CompilerError,
CompilerErrorDetail,
ErrorSeverity,
} from '../CompilerError';
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {ExternalFunction, ReactFunctionType} from '../HIR/Environment';
import {CodegenFunction} from '../ReactiveScopes';
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
@@ -32,6 +28,7 @@ import {
} from './Suppression';
import {GeneratedSource} from '../HIR';
import {Err, Ok, Result} from '../Utils/Result';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
export type CompilerPass = {
opts: PluginOptions;
@@ -101,12 +98,9 @@ function findDirectivesDynamicGating(
if (t.isValidIdentifier(maybeMatch[1])) {
result.push({directive, match: maybeMatch[1]});
} else {
errors.push({
reason: `Dynamic gating directive is not a valid JavaScript identifier`,
errors.pushErrorCode(ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, {
description: `Found '${directive.value.value}'`,
severity: ErrorSeverity.InvalidReact,
loc: directive.loc ?? null,
suggestions: null,
});
}
}
@@ -115,14 +109,11 @@ function findDirectivesDynamicGating(
return Err(errors);
} else if (result.length > 1) {
const error = new CompilerError();
error.push({
reason: `Multiple dynamic gating directives found`,
error.pushErrorCode(ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, {
description: `Expected a single directive but found [${result
.map(r => r.directive.value.value)
.join(', ')}]`,
severity: ErrorSeverity.InvalidReact,
loc: result[0].directive.loc ?? null,
suggestions: null,
});
return Err(error);
} else if (result.length === 1) {
@@ -451,14 +442,12 @@ export function compileProgram(
if (programContext.hasModuleScopeOptOut) {
if (compiledFns.length > 0) {
const error = new CompilerError();
error.pushErrorDetail(
new CompilerErrorDetail({
reason:
'Unexpected compiled functions when module scope opt-out is present',
severity: ErrorSeverity.Invariant,
loc: null,
}),
);
error.push({
reason:
'Unexpected compiled functions when module scope opt-out is present',
severity: ErrorSeverity.Invariant,
loc: null,
});
handleError(error, programContext, null);
}
return null;
@@ -591,9 +580,7 @@ function processFn(
let compiledFn: CodegenFunction;
const compileResult = tryCompileFunction(fn, fnType, programContext);
if (compileResult.kind === 'error') {
if (directives.optOut != null) {
logError(compileResult.error, programContext, fn.node.loc ?? null);
} else {
if (directives.optOut == null) {
handleError(compileResult.error, programContext, fn.node.loc ?? null);
}
const retryResult = retryCompileFunction(fn, fnType, programContext);
@@ -692,7 +679,7 @@ function tryCompileFunction(
fn,
programContext.opts.environment,
fnType,
'all_features',
programContext.opts.noEmit ? 'lint_only' : 'all_features',
programContext,
programContext.opts.logger,
programContext.filename,
@@ -805,15 +792,7 @@ function shouldSkipCompilation(
if (pass.opts.sources) {
if (pass.filename === null) {
const error = new CompilerError();
error.pushErrorDetail(
new CompilerErrorDetail({
reason: `Expected a filename but found none.`,
description:
"When the 'sources' config options is specified, the React compiler will only compile files with a name",
severity: ErrorSeverity.InvalidConfig,
loc: null,
}),
);
error.pushErrorCode(ErrorCode.FILENAME_NOT_SET);
handleError(error, pass, null);
return true;
}

View File

@@ -11,7 +11,7 @@ import {
CompilerDiagnostic,
CompilerError,
CompilerSuggestionOperation,
ErrorSeverity,
ErrorCode,
} from '../CompilerError';
import {assertExhaustive} from '../Utils/utils';
import {GeneratedSource} from '../HIR';
@@ -165,14 +165,12 @@ export function suppressionsToCompilerError(
let reason, suggestion;
switch (suppressionRange.source) {
case 'Eslint':
reason =
'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled';
reason = ErrorCode.BAILOUT_ESLINT_SUPPRESSION;
suggestion =
'Remove the ESLint suppression and address the React error';
break;
case 'Flow':
reason =
'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow';
reason = ErrorCode.BAILOUT_FLOW_SUPPRESSION;
suggestion = 'Remove the Flow suppression and address the React error';
break;
default:
@@ -182,10 +180,8 @@ export function suppressionsToCompilerError(
);
}
error.pushDiagnostic(
CompilerDiagnostic.create({
category: reason,
description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``,
severity: ErrorSeverity.InvalidReact,
CompilerDiagnostic.fromCode(reason, {
description: `Found suppression \`${suppressionRange.disableComment.value.trim()}\``,
suggestions: [
{
description: suggestion,

View File

@@ -8,27 +8,24 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {CompilerError, EnvironmentConfig, ErrorSeverity, Logger} from '..';
import {CompilerError, EnvironmentConfig, Logger} from '..';
import {getOrInsertWith} from '../Utils/utils';
import {Environment, GeneratedSource} from '../HIR';
import {DEFAULT_EXPORT} from '../HIR/Environment';
import {CompileProgramMetadata} from './Program';
import {CompilerDiagnostic, CompilerDiagnosticOptions} from '../CompilerError';
import {CompilerDiagnostic} from '../CompilerError';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
function throwInvalidReact(
options: Omit<CompilerDiagnosticOptions, 'severity'>,
function logAndThrowDiagnostic(
diagnostic: CompilerDiagnostic,
{logger, filename}: TraversalState,
): never {
const detail: CompilerDiagnosticOptions = {
severity: ErrorSeverity.InvalidReact,
...options,
};
logger?.logEvent(filename, {
kind: 'CompileError',
fnLoc: null,
detail: new CompilerDiagnostic(detail),
detail: diagnostic,
});
CompilerError.throwDiagnostic(detail);
CompilerError.throwDiagnostic(diagnostic);
}
function isAutodepsSigil(
@@ -90,13 +87,9 @@ function assertValidEffectImportReference(
* as it may have already been transformed by the compiler (and not
* memoized).
*/
throwInvalidReact(
{
category:
'Cannot infer dependencies of this effect. This will break your build!',
description:
'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' +
(maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''),
logAndThrowDiagnostic(
CompilerDiagnostic.fromCode(ErrorCode.DID_NOT_INFER_DEPS, {
description: maybeErrorDiagnostic ? `${maybeErrorDiagnostic}` : '',
details: [
{
kind: 'error',
@@ -104,7 +97,7 @@ function assertValidEffectImportReference(
loc: parent.node.loc ?? GeneratedSource,
},
],
},
}),
context,
);
}
@@ -121,10 +114,8 @@ function assertValidFireImportReference(
paths[0],
context.transformErrors,
);
throwInvalidReact(
{
category:
'[Fire] Untransformed reference to compiler-required feature.',
logAndThrowDiagnostic(
CompilerDiagnostic.fromCode(ErrorCode.CANNOT_COMPILE_FIRE, {
description:
'Either remove this `fire` call or ensure it is successfully transformed by the compiler' +
maybeErrorDiagnostic
@@ -137,7 +128,7 @@ function assertValidFireImportReference(
loc: paths[0].node.loc ?? GeneratedSource,
},
],
},
}),
context,
);
}

View File

@@ -1,4 +1,5 @@
import {CompilerError, SourceLocation} from '..';
import {ErrorCode} from '../CompilerError';
import {
ConcreteType,
printConcrete,
@@ -12,8 +13,8 @@ export function unsupportedLanguageFeature(
desc: string,
loc: SourceLocation,
): never {
CompilerError.throwInvalidJS({
reason: `Typedchecker does not currently support language feature: ${desc}`,
CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, {
description: `Typedchecker does not currently support language feature: ${desc}`,
loc,
});
}
@@ -49,16 +50,16 @@ export function raiseUnificationErrors(
loc,
});
} else if (errs.length === 1) {
CompilerError.throwInvalidJS({
reason: `Unable to unify types because ${printUnificationError(errs[0])}`,
CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, {
description: `Unable to unify types because ${printUnificationError(errs[0])}`,
loc,
});
} else {
const messages = errs
.map(err => `\t* ${printUnificationError(err)}`)
.join('\n');
CompilerError.throwInvalidJS({
reason: `Unable to unify types because:\n${messages}`,
CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, {
description: `Unable to unify types because:\n${messages}`,
loc,
});
}
@@ -69,21 +70,21 @@ export function unresolvableTypeVariable(
id: VariableId,
loc: SourceLocation,
): never {
CompilerError.throwInvalidJS({
reason: `Unable to resolve free variable ${id} to a concrete type`,
CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, {
description: `Unable to resolve free variable ${id} to a concrete type`,
loc,
});
}
export function cannotAddVoid(explicit: boolean, loc: SourceLocation): never {
if (explicit) {
CompilerError.throwInvalidJS({
reason: `Undefined is not a valid operand of \`+\``,
CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, {
description: `Undefined is not a valid operand of \`+\``,
loc,
});
} else {
CompilerError.throwInvalidJS({
reason: `Value may be undefined, which is not a valid operand of \`+\``,
CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, {
description: `Value may be undefined, which is not a valid operand of \`+\``,
loc,
});
}
@@ -93,8 +94,8 @@ export function unsupportedTypeAnnotation(
desc: string,
loc: SourceLocation,
): never {
CompilerError.throwInvalidJS({
reason: `Typedchecker does not currently support type annotation: ${desc}`,
CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, {
description: `Typedchecker does not currently support type annotation: ${desc}`,
loc,
});
}
@@ -106,16 +107,16 @@ export function checkTypeArgumentArity(
loc: SourceLocation,
): void {
if (expected !== actual) {
CompilerError.throwInvalidJS({
reason: `Expected ${desc} to have ${expected} type parameters, got ${actual}`,
CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, {
description: `Expected ${desc} to have ${expected} type parameters, got ${actual}`,
loc,
});
}
}
export function notAFunction(desc: string, loc: SourceLocation): void {
CompilerError.throwInvalidJS({
reason: `Cannot call ${desc} because it is not a function`,
CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, {
description: `Cannot call ${desc} because it is not a function`,
loc,
});
}
@@ -124,8 +125,8 @@ export function notAPolymorphicFunction(
desc: string,
loc: SourceLocation,
): void {
CompilerError.throwInvalidJS({
reason: `Cannot call ${desc} with type arguments because it is not a polymorphic function`,
CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, {
description: `Cannot call ${desc} with type arguments because it is not a polymorphic function`,
loc,
});
}

View File

@@ -12,6 +12,7 @@ import {
CompilerDiagnostic,
CompilerError,
CompilerSuggestionOperation,
ErrorCode,
ErrorSeverity,
} from '../CompilerError';
import {Err, Ok, Result} from '../Utils/Result';
@@ -170,14 +171,12 @@ export function lower(
);
} else {
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.Todo,
category: `Handle ${param.node.type} parameters`,
CompilerDiagnostic.fromCode(ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, {
description: `[BuildHIR] Add support for ${param.node.type} parameters.`,
}).withDetail({
kind: 'error',
loc: param.node.loc ?? null,
message: 'Unsupported parameter type',
message: `Unsupported parameter type: ${param.node.type}`,
}),
);
}
@@ -201,14 +200,12 @@ export function lower(
directives = body.get('directives').map(d => d.node.value.value);
} else {
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidJS,
category: `Unexpected function body kind`,
CompilerDiagnostic.fromCode(ErrorCode.INVALID_JAVASCRIPT_AST, {
description: `Expected function body to be an expression or a block statement, got \`${body.type}\`.`,
}).withDetail({
kind: 'error',
loc: body.node.loc ?? null,
message: 'Expected a block statement or expression',
message: 'Change this to a block statement or expression',
}),
);
}
@@ -769,12 +766,12 @@ function lowerStatement(
const testExpr = case_.get('test');
if (testExpr.node == null) {
if (hasDefault) {
builder.errors.push({
reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`,
severity: ErrorSeverity.InvalidJS,
loc: case_.node.loc ?? null,
suggestions: null,
});
builder.errors.pushErrorCode(
ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS,
{
loc: case_.node.loc ?? null,
},
);
break;
}
hasDefault = true;
@@ -886,19 +883,20 @@ function lowerStatement(
if (builder.isContextIdentifier(id)) {
if (kind === InstructionKind.Const) {
const declRangeStart = declaration.parentPath.node.start!;
builder.errors.push({
reason: `Expect \`const\` declaration not to be reassigned`,
severity: ErrorSeverity.InvalidJS,
loc: id.node.loc ?? null,
suggestions: [
{
description: 'Change to a `let` declaration',
op: CompilerSuggestionOperation.Replace,
range: [declRangeStart, declRangeStart + 5], // "const".length
text: 'let',
},
],
});
builder.errors.pushErrorCode(
ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST,
{
loc: id.node.loc ?? null,
suggestions: [
{
description: 'Change to a `let` declaration',
op: CompilerSuggestionOperation.Replace,
range: [declRangeStart, declRangeStart + 5], // "const".length
text: 'let',
},
],
},
);
}
lowerValueToTemporary(builder, {
kind: 'DeclareContext',
@@ -932,13 +930,13 @@ function lowerStatement(
}
}
} else {
builder.errors.push({
reason: `Expected variable declaration to be an identifier if no initializer was provided`,
description: `Got a \`${id.type}\``,
severity: ErrorSeverity.InvalidJS,
loc: stmt.node.loc ?? null,
suggestions: null,
});
builder.errors.pushErrorCode(
ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL,
{
description: `Got a \`${id.type}\``,
loc: stmt.node.loc ?? null,
},
);
}
}
return;
@@ -1374,12 +1372,8 @@ function lowerStatement(
return;
}
case 'WithStatement': {
builder.errors.push({
reason: `JavaScript 'with' syntax is not supported`,
description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`,
severity: ErrorSeverity.UnsupportedJS,
builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_WITH, {
loc: stmtPath.node.loc ?? null,
suggestions: null,
});
lowerValueToTemporary(builder, {
kind: 'UnsupportedNode',
@@ -1394,12 +1388,8 @@ function lowerStatement(
* and complex enough to support that we don't anticipate supporting anytime soon. Developers
* are encouraged to lift classes out of component/hook declarations.
*/
builder.errors.push({
reason: 'Inline `class` declarations are not supported',
description: `Move class declarations outside of components/hooks`,
severity: ErrorSeverity.UnsupportedJS,
builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_INNER_CLASS, {
loc: stmtPath.node.loc ?? null,
suggestions: null,
});
lowerValueToTemporary(builder, {
kind: 'UnsupportedNode',
@@ -1423,12 +1413,8 @@ function lowerStatement(
case 'ImportDeclaration':
case 'TSExportAssignment':
case 'TSImportEqualsDeclaration': {
builder.errors.push({
reason:
'JavaScript `import` and `export` statements may only appear at the top level of a module',
severity: ErrorSeverity.InvalidJS,
builder.errors.pushErrorCode(ErrorCode.INVALID_IMPORT_EXPORT, {
loc: stmtPath.node.loc ?? null,
suggestions: null,
});
lowerValueToTemporary(builder, {
kind: 'UnsupportedNode',
@@ -1438,12 +1424,8 @@ function lowerStatement(
return;
}
case 'TSNamespaceExportDeclaration': {
builder.errors.push({
reason:
'TypeScript `namespace` statements may only appear at the top level of a module',
severity: ErrorSeverity.InvalidJS,
builder.errors.pushErrorCode(ErrorCode.INVALID_TS_NAMESPACE, {
loc: stmtPath.node.loc ?? null,
suggestions: null,
});
lowerValueToTemporary(builder, {
kind: 'UnsupportedNode',
@@ -1698,12 +1680,8 @@ function lowerExpression(
const expr = exprPath as NodePath<t.NewExpression>;
const calleePath = expr.get('callee');
if (!calleePath.isExpression()) {
builder.errors.push({
reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`,
description: `Got a \`${calleePath.node.type}\``,
severity: ErrorSeverity.InvalidJS,
builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_NEW_EXPRESSION, {
loc: calleePath.node.loc ?? null,
suggestions: null,
});
return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc};
}
@@ -1800,12 +1778,12 @@ function lowerExpression(
last = lowerExpressionToTemporary(builder, item);
}
if (last === null) {
builder.errors.push({
reason: `Expected sequence expression to have at least one expression`,
severity: ErrorSeverity.InvalidJS,
loc: expr.node.loc ?? null,
suggestions: null,
});
builder.errors.pushErrorCode(
ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION,
{
loc: expr.node.loc ?? null,
},
);
} else {
lowerValueToTemporary(builder, {
kind: 'StoreLocal',
@@ -2289,18 +2267,18 @@ function lowerExpression(
});
for (const [name, locations] of Object.entries(fbtLocations)) {
if (locations.length > 1) {
CompilerError.throwDiagnostic({
severity: ErrorSeverity.Todo,
category: 'Support duplicate fbt tags',
description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`,
details: locations.map(loc => {
return {
kind: 'error',
message: `Multiple \`<${tagName}:${name}>\` tags found`,
loc,
};
CompilerError.throwDiagnostic(
CompilerDiagnostic.fromCode(ErrorCode.TODO_DUPLICATE_FBT_TAGS, {
description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`,
details: locations.map(loc => {
return {
kind: 'error',
message: `Multiple \`<${tagName}:${name}>\` tags found`,
loc,
};
}),
}),
});
);
}
}
}
@@ -2389,11 +2367,8 @@ function lowerExpression(
const quasis = expr.get('quasis');
if (subexprs.length !== quasis.length - 1) {
builder.errors.push({
reason: `Unexpected quasi and subexpression lengths in template literal`,
severity: ErrorSeverity.InvalidJS,
builder.errors.pushErrorCode(ErrorCode.INVALID_QUASI_LENGTHS, {
loc: exprPath.node.loc ?? null,
suggestions: null,
});
return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc};
}
@@ -2441,24 +2416,23 @@ function lowerExpression(
};
}
} else {
builder.errors.push({
reason: `Only object properties can be deleted`,
severity: ErrorSeverity.InvalidJS,
loc: expr.node.loc ?? null,
suggestions: [
{
description: 'Remove this line',
range: [expr.node.start!, expr.node.end!],
op: CompilerSuggestionOperation.Remove,
},
],
});
builder.errors.pushErrorCode(
ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION,
{
loc: expr.node.loc ?? null,
suggestions: [
{
description: 'Remove this line',
range: [expr.node.start!, expr.node.end!],
op: CompilerSuggestionOperation.Remove,
},
],
},
);
return {kind: 'UnsupportedNode', node: expr.node, loc: exprLoc};
}
} else if (expr.node.operator === 'throw') {
builder.errors.push({
reason: `Throw expressions are not supported`,
severity: ErrorSeverity.InvalidJS,
builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_THROW_EXPRESSION, {
loc: expr.node.loc ?? null,
suggestions: [
{
@@ -3285,10 +3259,8 @@ function lowerJsxElementName(
const name = exprPath.node.name.name;
const tag = `${namespace}:${name}`;
if (namespace.indexOf(':') !== -1 || name.indexOf(':') !== -1) {
builder.errors.push({
reason: `Expected JSXNamespacedName to have no colons in the namespace or name`,
builder.errors.pushErrorCode(ErrorCode.INVALID_JSX_NAMESPACED_NAME, {
description: `Got \`${namespace}\` : \`${name}\``,
severity: ErrorSeverity.InvalidJS,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
@@ -3583,11 +3555,7 @@ function lowerIdentifier(
}
default: {
if (binding.kind === 'Global' && binding.name === 'eval') {
builder.errors.push({
reason: `The 'eval' function is not supported`,
description:
'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler',
severity: ErrorSeverity.UnsupportedJS,
builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_EVAL, {
loc: exprPath.node.loc ?? null,
suggestions: null,
});
@@ -3653,9 +3621,7 @@ function lowerIdentifierForAssignment(
binding.bindingKind === 'const' &&
kind === InstructionKind.Reassign
) {
builder.errors.push({
reason: `Cannot reassign a \`const\` variable`,
severity: ErrorSeverity.InvalidJS,
builder.errors.pushErrorCode(ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, {
loc: path.node.loc ?? null,
description:
binding.identifier.name != null
@@ -3710,12 +3676,13 @@ function lowerAssignment(
let temporary;
if (builder.isContextIdentifier(lvalue)) {
if (kind === InstructionKind.Const && !isHoistedIdentifier) {
builder.errors.push({
reason: `Expected \`const\` declaration not to be reassigned`,
severity: ErrorSeverity.InvalidJS,
loc: lvalue.node.loc ?? null,
suggestions: null,
});
builder.errors.pushErrorCode(
ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST,
{
loc: lvalue.node.loc ?? null,
suggestions: null,
},
);
}
if (
@@ -3726,7 +3693,8 @@ function lowerAssignment(
) {
builder.errors.push({
reason: `Unexpected context variable kind`,
severity: ErrorSeverity.InvalidJS,
description: `Expected one of Const, Reassign, Let, Function, got ${kind}`,
severity: ErrorSeverity.Invariant,
loc: lvalue.node.loc ?? null,
suggestions: null,
});

View File

@@ -93,7 +93,7 @@ export const MacroSchema = z.union([
z.tuple([z.string(), z.array(MacroMethodSchema)]),
]);
export type CompilerMode = 'all_features' | 'no_inferred_memo';
export type CompilerMode = 'all_features' | 'no_inferred_memo' | 'lint_only';
export type Macro = z.infer<typeof MacroSchema>;
export type MacroMethod = z.infer<typeof MacroMethodSchema>;
@@ -829,6 +829,14 @@ export class Environment {
}
}
logOrThrowErrors(errors: Result<void, CompilerError>): void {
if (this.compilerMode === 'lint_only') {
this.logErrors(errors);
} else {
errors.unwrap();
}
}
isContextIdentifier(node: t.Identifier): boolean {
return this.#contextIdentifiers.has(node);
}

View File

@@ -15,6 +15,7 @@ import {Type, makeType} from './Types';
import {z} from 'zod';
import type {AliasingEffect} from '../Inference/AliasingEffects';
import {isReservedWord} from '../Utils/Keyword';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
/*
* *******************************************************************************************
@@ -1322,18 +1323,18 @@ export function forkTemporaryIdentifier(
*/
export function makeIdentifierName(name: string): ValidatedIdentifier {
if (isReservedWord(name)) {
CompilerError.throwInvalidJS({
reason: 'Expected a non-reserved identifier name',
loc: GeneratedSource,
description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`,
suggestions: null,
});
CompilerError.throwFromCode(
ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME,
{
loc: GeneratedSource,
description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`,
},
);
} else {
CompilerError.invariant(t.isValidIdentifier(name), {
reason: `Expected a valid identifier name`,
loc: GeneratedSource,
description: `\`${name}\` is not a valid JavaScript identifier`,
suggestions: null,
});
}
return {

View File

@@ -7,7 +7,7 @@
import {Binding, NodePath} from '@babel/traverse';
import * as t from '@babel/types';
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {CompilerDiagnostic, CompilerError} from '../CompilerError';
import {Environment} from './Environment';
import {
BasicBlock,
@@ -37,6 +37,7 @@ import {
mapTerminalSuccessors,
terminalFallthrough,
} from './visitors';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
/*
* *******************************************************************************************
@@ -308,19 +309,17 @@ export default class HIRBuilder {
resolveBinding(node: t.Identifier): Identifier {
if (node.name === 'fbt') {
CompilerError.throwDiagnostic({
severity: ErrorSeverity.Todo,
category: 'Support local variables named `fbt`',
description:
'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported',
details: [
{
kind: 'error',
message: 'Rename to avoid conflict with fbt plugin',
loc: node.loc ?? GeneratedSource,
},
],
});
CompilerError.throwDiagnostic(
CompilerDiagnostic.fromCode(ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, {
details: [
{
kind: 'error',
message: 'Rename to avoid conflict with fbt plugin',
loc: node.loc ?? GeneratedSource,
},
],
}),
);
}
const originalName = node.name;
let name = originalName;

View File

@@ -5,12 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
SourceLocation,
} from '..';
import {CompilerDiagnostic, CompilerError, SourceLocation} from '..';
import {
CallExpression,
Effect,
@@ -35,6 +30,7 @@ import {
makeInstructionId,
} from '../HIR';
import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Result} from '../Utils/Result';
type ManualMemoCallee = {
@@ -299,12 +295,11 @@ function extractManualMemoizationArgs(
>;
if (fnPlace == null) {
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: `Expected a callback function to be passed to ${kind}`,
description: `Expected a callback function to be passed to ${kind}`,
suggestions: null,
}).withDetail({
CompilerDiagnostic.fromCode(
kind === 'useMemo'
? ErrorCode.INVALID_USE_MEMO_NO_ARG0
: ErrorCode.INVALID_USE_CALLBACK_NO_ARG0,
).withDetail({
kind: 'error',
loc: instr.value.loc,
message: `Expected a callback function to be passed to ${kind}`,
@@ -314,12 +309,11 @@ function extractManualMemoizationArgs(
}
if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') {
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: `Unexpected spread argument to ${kind}`,
description: `Unexpected spread argument to ${kind}`,
suggestions: null,
}).withDetail({
CompilerDiagnostic.fromCode(
fnPlace.kind === 'Spread'
? ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT
: ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT,
).withDetail({
kind: 'error',
loc: instr.value.loc,
message: `Unexpected spread argument to ${kind}`,
@@ -334,12 +328,9 @@ function extractManualMemoizationArgs(
);
if (maybeDepsList == null) {
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: `Expected the dependency list for ${kind} to be an array literal`,
description: `Expected the dependency list for ${kind} to be an array literal`,
suggestions: null,
}).withDetail({
CompilerDiagnostic.fromCode(
ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST,
).withDetail({
kind: 'error',
loc: depsListPlace.loc,
message: `Expected the dependency list for ${kind} to be an array literal`,
@@ -352,12 +343,9 @@ function extractManualMemoizationArgs(
const maybeDep = sidemap.maybeDeps.get(dep.identifier.id);
if (maybeDep == null) {
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
suggestions: null,
}).withDetail({
CompilerDiagnostic.fromCode(
ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY,
).withDetail({
kind: 'error',
loc: dep.loc,
message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
@@ -457,16 +445,16 @@ export function dropManualMemoization(
if (funcToCheck !== undefined && funcToCheck.loweredFunc.func) {
if (!hasNonVoidReturn(funcToCheck.loweredFunc.func)) {
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'useMemo() callbacks must return a value',
description: `This ${
manualMemo.loadInstr.value.kind === 'PropertyLoad'
? 'React.useMemo'
: 'useMemo'
} callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`,
suggestions: null,
}).withDetail({
CompilerDiagnostic.fromCode(
ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN,
{
description: `This ${
manualMemo.loadInstr.value.kind === 'PropertyLoad'
? 'React.useMemo'
: 'useMemo'
} callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`,
},
).withDetail({
kind: 'error',
loc: instr.value.loc,
message: 'useMemo() callbacks must return a value',
@@ -497,12 +485,9 @@ export function dropManualMemoization(
*/
if (!sidemap.functions.has(fnPlace.identifier.id)) {
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: `Expected the first argument to be an inline function expression`,
description: `Expected the first argument to be an inline function expression`,
suggestions: [],
}).withDetail({
CompilerDiagnostic.fromCode(
ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK,
).withDetail({
kind: 'error',
loc: fnPlace.loc,
message: `Expected the first argument to be an inline function expression`,

View File

@@ -25,6 +25,7 @@ import {
isRefOrRefValue,
} from '../HIR';
import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {assertExhaustive} from '../Utils/utils';
interface State {
@@ -62,22 +63,20 @@ function inferOperandEffect(state: State, place: Place): null | FunctionEffect {
// We ignore mutations of primitives since this is not a React-specific problem
value.kind !== ValueKind.Primitive
) {
let reason = getWriteErrorReason(value);
let errorCode = getWriteErrorReason(value);
return {
kind:
value.reason.size === 1 && value.reason.has(ValueReason.Global)
? 'GlobalMutation'
: 'ReactMutation',
error: {
reason,
errorCode,
description:
place.identifier.name !== null &&
place.identifier.name.kind === 'named'
? `Found mutation of \`${place.identifier.name.value}\``
: null,
loc: place.loc,
suggestions: null,
severity: ErrorSeverity.InvalidReact,
},
};
}
@@ -266,11 +265,8 @@ export function inferInstructionFunctionEffects(
functionEffects.push({
kind: 'GlobalMutation',
error: {
reason:
'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)',
errorCode: ErrorCode.INVALID_WRITE_GLOBAL,
loc: instr.loc,
suggestions: null,
severity: ErrorSeverity.InvalidReact,
},
});
break;
@@ -324,28 +320,28 @@ function isEffectSafeOutsideRender(effect: FunctionEffect): boolean {
return effect.kind === 'GlobalMutation';
}
export function getWriteErrorReason(abstractValue: AbstractValue): string {
export function getWriteErrorReason(abstractValue: AbstractValue): ErrorCode {
if (abstractValue.reason.has(ValueReason.Global)) {
return 'Modifying a variable defined outside a component or hook is not allowed. Consider using an effect';
return ErrorCode.INVALID_WRITE_GLOBAL;
} else if (abstractValue.reason.has(ValueReason.JsxCaptured)) {
return 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX';
return ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX;
} else if (abstractValue.reason.has(ValueReason.Context)) {
return `Modifying a value returned from 'useContext()' is not allowed.`;
return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT;
} else if (abstractValue.reason.has(ValueReason.KnownReturnSignature)) {
return 'Modifying a value returned from a function whose return value should not be mutated';
return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE;
} else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) {
return 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead';
return ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS;
} else if (abstractValue.reason.has(ValueReason.State)) {
return "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead";
return ErrorCode.INVALID_WRITE_STATE;
} else if (abstractValue.reason.has(ValueReason.ReducerState)) {
return "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead";
return ErrorCode.INVALID_WRITE_REDUCER_STATE;
} else if (abstractValue.reason.has(ValueReason.Effect)) {
return 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()';
return ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY;
} else if (abstractValue.reason.has(ValueReason.HookCaptured)) {
return 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook';
return ErrorCode.INVALID_WRITE_HOOK_CAPTURED;
} else if (abstractValue.reason.has(ValueReason.HookReturn)) {
return 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed';
return ErrorCode.INVALID_WRITE_HOOK_RETURN;
} else {
return 'This modifies a variable that React considers immutable';
return ErrorCode.INVALID_WRITE_GENERIC;
}
}

View File

@@ -9,7 +9,6 @@ import {
CompilerDiagnostic,
CompilerError,
Effect,
ErrorSeverity,
SourceLocation,
ValueKind,
} from '..';
@@ -69,6 +68,7 @@ import {getWriteErrorReason} from './InferFunctionEffects';
import prettyFormat from 'pretty-format';
import {createTemporaryPlace} from '../HIR/HIRBuilder';
import {AliasingEffect, AliasingSignature, hashEffect} from './AliasingEffects';
import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes';
const DEBUG = false;
@@ -442,7 +442,7 @@ function applySignature(
const value = state.kind(effect.value);
switch (value.kind) {
case ValueKind.Frozen: {
const reason = getWriteErrorReason({
const errorCode = getWriteErrorReason({
kind: value.kind,
reason: value.reason,
context: new Set(),
@@ -455,10 +455,9 @@ function applySignature(
effects.push({
kind: 'MutateFrozen',
place: effect.value,
error: CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'This value cannot be modified',
description: `${reason}.`,
// TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures
error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, {
description: ErrorCodeDetails[errorCode].reason,
}).withDetail({
kind: 'error',
loc: effect.value.loc,
@@ -1026,22 +1025,20 @@ function applyEffect(
const hoistedAccess = context.hoistedContextDeclarations.get(
effect.value.identifier.declarationId,
);
const diagnostic = CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot access variable before it is declared',
description: `${variable ?? 'This variable'} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time.`,
});
const diagnostic = CompilerDiagnostic.fromCode(
ErrorCode.INVALID_ACCESS_BEFORE_INIT,
);
if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) {
diagnostic.withDetail({
kind: 'error',
loc: hoistedAccess.loc,
message: `${variable ?? 'variable'} accessed before it is declared`,
message: `${variable ?? 'This variable'} is accessed before it is declared`,
});
}
diagnostic.withDetail({
kind: 'error',
loc: effect.value.loc,
message: `${variable ?? 'variable'} is declared here`,
message: `${variable ?? 'This variable'} is declared here`,
});
applyEffect(
@@ -1056,7 +1053,7 @@ function applyEffect(
effects,
);
} else {
const reason = getWriteErrorReason({
const errorCode = getWriteErrorReason({
kind: value.kind,
reason: value.reason,
context: new Set(),
@@ -1075,10 +1072,9 @@ function applyEffect(
? 'MutateFrozen'
: 'MutateGlobal',
place: effect.value,
error: CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'This value cannot be modified',
description: `${reason}.`,
// TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures
error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, {
description: ErrorCodeDetails[errorCode].reason,
}).withDetail({
kind: 'error',
loc: effect.value.loc,
@@ -2006,15 +2002,12 @@ function computeSignatureForInstruction(
effects.push({
kind: 'MutateGlobal',
place: value.value,
error: CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category:
'Cannot reassign variables declared outside of the component/hook',
description: `Variable ${variable} is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)`,
}).withDetail({
error: CompilerDiagnostic.fromCode(
ErrorCode.INVALID_WRITE_GLOBAL,
).withDetail({
kind: 'error',
loc: instr.loc,
message: `${variable} cannot be reassigned`,
message: `${variable} should not be reassigned`,
}),
});
effects.push({kind: 'Assign', from: value.value, into: lvalue});
@@ -2105,19 +2098,16 @@ function computeEffectsForLegacySignature(
effects.push({
kind: 'Impure',
place: receiver,
error: CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
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)',
}).withDetail({
kind: 'error',
loc,
message: 'Cannot call impure function',
}),
error: CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail(
{
kind: 'error',
loc,
message:
signature.canonicalName != null
? `\`${signature.canonicalName}\` is an impure function. `
: 'This is an impure function.',
},
),
});
}
const stores: Array<Place> = [];

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
import {CompilerError} from '../CompilerError';
import {Environment} from '../HIR';
import {
AbstractValue,
@@ -48,6 +48,7 @@ import {
eachTerminalOperand,
eachTerminalSuccessor,
} from '../HIR/visitors';
import {Err, Ok, Result} from '../Utils/Result';
import {assertExhaustive, Set_isSuperset} from '../Utils/utils';
import {
inferTerminalFunctionEffects,
@@ -106,7 +107,7 @@ const UndefinedValue: InstructionValue = {
export default function inferReferenceEffects(
fn: HIRFunction,
options: {isFunctionExpression: boolean} = {isFunctionExpression: false},
): Array<CompilerErrorDetailOptions> {
): Result<void, CompilerError> {
/*
* Initial state contains function params
* TODO: include module declarations here as well
@@ -247,10 +248,17 @@ export default function inferReferenceEffects(
if (options.isFunctionExpression) {
fn.effects = functionEffects;
return [];
} else {
return transformFunctionEffectErrors(functionEffects);
const errors = transformFunctionEffectErrors(functionEffects);
if (errors.length > 0) {
const compilerError = new CompilerError();
for (const detail of errors) {
compilerError.push(detail);
}
return Err(compilerError);
}
}
return Ok(void 0);
}
type FreezeAction = {values: Set<InstructionValue>; reason: Set<ValueReason>};

View File

@@ -42,6 +42,7 @@ import {
import {eachInstructionOperand} from '../HIR/visitors';
import {printSourceLocationLine} from '../HIR/PrintHIR';
import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
/*
* TODO(jmbrown):
@@ -50,8 +51,6 @@ import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment';
* - React.useEffect calls
*/
const CANNOT_COMPILE_FIRE = 'Cannot compile `fire`';
export function transformFire(fn: HIRFunction): void {
const context = new Context(fn.env);
replaceFireFunctions(fn, context);
@@ -178,9 +177,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
loc: value.args[1].loc,
description:
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
severity: ErrorSeverity.Invariant,
reason: CANNOT_COMPILE_FIRE,
suggestions: null,
errorCode: ErrorCode.CANNOT_COMPILE_FIRE,
});
}
} else if (value.args.length > 1 && value.args[1].kind === 'Spread') {
@@ -188,9 +185,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
loc: value.args[1].place.loc,
description:
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
severity: ErrorSeverity.Invariant,
reason: CANNOT_COMPILE_FIRE,
suggestions: null,
errorCode: ErrorCode.CANNOT_COMPILE_FIRE,
});
}
}
@@ -243,11 +238,9 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
} else {
context.pushError({
loc: value.loc,
errorCode: ErrorCode.CANNOT_COMPILE_FIRE,
description:
'`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed',
severity: ErrorSeverity.InvalidReact,
reason: CANNOT_COMPILE_FIRE,
suggestions: null,
});
}
} else {
@@ -262,10 +255,8 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
}
context.pushError({
loc: value.loc,
errorCode: ErrorCode.CANNOT_COMPILE_FIRE,
description,
severity: ErrorSeverity.InvalidReact,
reason: CANNOT_COMPILE_FIRE,
suggestions: null,
});
}
} else if (value.kind === 'CallExpression') {
@@ -394,9 +385,7 @@ function ensureNoRemainingCalleeCaptures(
description: `All uses of ${calleeName} must be either used with a fire() call in \
this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \
${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`,
severity: ErrorSeverity.InvalidReact,
reason: CANNOT_COMPILE_FIRE,
suggestions: null,
errorCode: ErrorCode.CANNOT_COMPILE_FIRE,
});
}
}
@@ -411,9 +400,7 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void {
context.pushError({
loc: place.identifier.loc,
description: 'Cannot use `fire` outside of a useEffect function',
severity: ErrorSeverity.Invariant,
reason: CANNOT_COMPILE_FIRE,
suggestions: null,
errorCode: ErrorCode.CANNOT_COMPILE_FIRE,
});
}
}

View File

@@ -0,0 +1,624 @@
import {ErrorSeverity} from './CompilerErrorSeverity';
export enum LinterCategory {
RULES_OF_HOOKS = 'rules-of-hooks',
IMPURE_FUNCTIONS = 'impure-functions',
JSX_IN_TRY = 'jsx-in-try',
NO_REF_ACCESS_IN_RENDER = 'no-ref-access-in-render',
VALIDATE_MANUAL_MEMO = 'validate-manual-memo',
DYNAMIC_MANUAL_MEMO = 'dynamic-manual-memo',
EXHAUSTIVE_DEPS = 'exhaustive-deps',
MEMOIZED_DEPENDENCIES = 'memoized-dependencies', // not real!
INVALID_WRITE = 'invalid-write',
CAPITALIZED_CALLS = 'capitalized-calls',
STATIC_COMPONENTS = 'static-components',
NO_SET_STATE_IN_RENDER = 'no-set-state-in-render',
NO_SET_STATE_IN_EFFECTS = 'no-set-state-in-effects',
UNNECESSARY_EFFECTS = 'unnecessary-effects',
UNSUPPORTED_SYNTAX = 'unsupported-syntax',
TODO_SYNTAX = 'todo-syntax',
COMPILER_CONFIG = 'compiler-config',
}
export enum ErrorCode {
HOOK_CALL_STATIC,
HOOK_INVALID_REFERENCE,
HOOK_CALL_REACTIVE,
HOOK_CALL_NOT_TOP_LEVEL,
IMPURE_FUNCTIONS,
JSX_IN_TRY,
NO_REF_ACCESS_IN_RENDER,
WRITE_AFTER_RENDER,
REASSIGN_AFTER_RENDER,
REASSIGN_IN_ASYNC,
INVALID_WRITE,
CAPITALIZED_CALLS,
STATIC_COMPONENTS,
INVALID_USE_MEMO_CALLBACK_RETURN,
INVALID_USE_MEMO_CALLBACK_PARAMETERS,
INVALID_USE_MEMO_CALLBACK_ASYNC,
INVALID_USE_MEMO_NO_ARG0,
INVALID_USE_CALLBACK_NO_ARG0,
DYNAMIC_MANUAL_MEMO_CALLBACK,
DYNAMIC_USE_MEMO_SPREAD_ARGUMENT,
DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT,
DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST,
COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY,
INVALID_SET_STATE_IN_RENDER,
INVALID_SET_STATE_IN_MEMO,
INVALID_SET_STATE_IN_EFFECTS,
NO_DERIVED_COMPUTATIONS_IN_EFFECTS,
DYNAMIC_GATING_IS_NOT_IDENTIFIER,
DYNAMIC_GATING_MULTIPLE_DIRECTIVES,
FILENAME_NOT_SET,
INVALID_WRITE_GLOBAL,
INVALID_WRITE_FROZEN_VALUE_JSX,
INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT,
INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE,
INVALID_WRITE_IMMUTABLE_ARGS,
INVALID_WRITE_STATE,
INVALID_WRITE_REDUCER_STATE,
INVALID_WRITE_EFFECT_DEPENDENCY,
INVALID_WRITE_HOOK_CAPTURED,
INVALID_WRITE_HOOK_RETURN,
INVALID_ACCESS_BEFORE_INIT,
INVALID_WRITE_GENERIC,
MEMOIZED_EFFECT_DEPENDENCIES,
INVALID_SYNTAX_MULTIPLE_DEFAULTS,
INVALID_SYNTAX_REASSIGNED_CONST,
INVALID_SYNTAX_BAD_VARIABLE_DECL,
UNSUPPORTED_WITH,
UNSUPPORTED_INNER_CLASS,
INVALID_IMPORT_EXPORT,
INVALID_TS_NAMESPACE,
UNSUPPORTED_NEW_EXPRESSION,
UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION,
INVALID_QUASI_LENGTHS,
INVALID_SYNTAX_DELETE_EXPRESSION,
UNSUPPORTED_THROW_EXPRESSION,
INVALID_JSX_NAMESPACED_NAME,
UNSUPPORTED_EVAL,
INVALID_SYNTAX_RESERVED_VARIABLE_NAME,
INVALID_JAVASCRIPT_AST,
BAILOUT_ESLINT_SUPPRESSION,
BAILOUT_FLOW_SUPPRESSION,
MANUAL_MEMO_MUTATED_LATER,
MANUAL_MEMO_REMOVED,
MANUAL_MEMO_DEPENDENCIES_CONFLICT,
CANNOT_COMPILE_FIRE,
DID_NOT_INFER_DEPS,
/** Todo syntax */
TODO_CONFLICTING_FBT_IDENTIFIER,
TODO_DUPLICATE_FBT_TAGS,
UNKNOWN_FUNCTION_PARAMETERS,
}
type ErrorCodeType = {
code: ErrorCode;
description?: string;
severity: ErrorSeverity;
reason: string;
linterCategory: LinterCategory | null;
};
export const ErrorCodeDetails: Record<ErrorCode, ErrorCodeType> = {
[ErrorCode.HOOK_CALL_STATIC]: {
code: ErrorCode.HOOK_CALL_STATIC,
severity: ErrorSeverity.InvalidReact,
reason:
'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)',
linterCategory: LinterCategory.RULES_OF_HOOKS,
},
[ErrorCode.HOOK_INVALID_REFERENCE]: {
code: ErrorCode.HOOK_INVALID_REFERENCE,
severity: ErrorSeverity.InvalidReact,
reason:
'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values',
linterCategory: LinterCategory.RULES_OF_HOOKS,
},
[ErrorCode.HOOK_CALL_REACTIVE]: {
code: ErrorCode.HOOK_CALL_REACTIVE,
severity: ErrorSeverity.InvalidReact,
reason:
'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks',
linterCategory: LinterCategory.RULES_OF_HOOKS,
},
[ErrorCode.HOOK_CALL_NOT_TOP_LEVEL]: {
code: ErrorCode.HOOK_CALL_NOT_TOP_LEVEL,
severity: ErrorSeverity.InvalidReact,
reason:
'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)',
linterCategory: LinterCategory.RULES_OF_HOOKS,
},
[ErrorCode.IMPURE_FUNCTIONS]: {
code: ErrorCode.IMPURE_FUNCTIONS,
severity: ErrorSeverity.InvalidReact,
reason: 'Cannot call impure functions during render',
description:
'Calling an impure function can produce unstable results that change 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)',
linterCategory: LinterCategory.IMPURE_FUNCTIONS,
},
[ErrorCode.JSX_IN_TRY]: {
code: ErrorCode.JSX_IN_TRY,
severity: ErrorSeverity.InvalidReact,
reason: '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)`,
linterCategory: LinterCategory.JSX_IN_TRY,
},
[ErrorCode.NO_REF_ACCESS_IN_RENDER]: {
code: ErrorCode.NO_REF_ACCESS_IN_RENDER,
severity: ErrorSeverity.InvalidReact,
reason: 'Cannot access refs during render',
description:
'React refs are values that are not needed for rendering. Refs should only be accessed ' +
'outside of render, such as in event handlers or effects. ' +
'Accessing a ref value (the `current` property) during render can cause your component ' +
'not to update as expected (https://react.dev/reference/react/useRef)',
linterCategory: LinterCategory.NO_REF_ACCESS_IN_RENDER,
},
[ErrorCode.INVALID_SET_STATE_IN_EFFECTS]: {
code: ErrorCode.INVALID_SET_STATE_IN_EFFECTS,
severity: ErrorSeverity.InvalidReact,
reason:
'Calling setState synchronously within an effect can trigger cascading renders',
description:
'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' +
'In general, the body of an effect should do one or both of the following:\n' +
'* Update external systems with the latest state from React.\n' +
'* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' +
'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' +
'(https://react.dev/learn/you-might-not-need-an-effect)',
linterCategory: LinterCategory.NO_SET_STATE_IN_EFFECTS,
},
[ErrorCode.WRITE_AFTER_RENDER]: {
code: ErrorCode.WRITE_AFTER_RENDER,
severity: ErrorSeverity.InvalidReact,
reason: 'Cannot modify local variables after render completes',
description: `This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead`,
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.REASSIGN_AFTER_RENDER]: {
code: ErrorCode.REASSIGN_AFTER_RENDER,
severity: ErrorSeverity.InvalidReact,
reason: 'Cannot reassign local variables after render completes',
description: `Reassigning local variables after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead`,
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.REASSIGN_IN_ASYNC]: {
code: ErrorCode.REASSIGN_IN_ASYNC,
severity: ErrorSeverity.InvalidReact,
reason: 'Cannot reassign variable in async function',
description:
'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead',
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_WRITE]: {
code: ErrorCode.INVALID_WRITE,
severity: ErrorSeverity.InvalidReact,
reason: 'This value cannot be modified',
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.CAPITALIZED_CALLS]: {
code: ErrorCode.CAPITALIZED_CALLS,
severity: ErrorSeverity.InvalidReact,
reason:
'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config',
linterCategory: LinterCategory.CAPITALIZED_CALLS,
},
[ErrorCode.STATIC_COMPONENTS]: {
code: ErrorCode.STATIC_COMPONENTS,
severity: ErrorSeverity.InvalidReact,
reason: 'Cannot create components during render',
linterCategory: LinterCategory.STATIC_COMPONENTS,
},
[ErrorCode.INVALID_USE_MEMO_NO_ARG0]: {
code: ErrorCode.INVALID_USE_MEMO_NO_ARG0,
severity: ErrorSeverity.InvalidReact,
reason: `Expected a callback function to be passed to useMemo`,
linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO,
},
[ErrorCode.INVALID_USE_CALLBACK_NO_ARG0]: {
code: ErrorCode.INVALID_USE_CALLBACK_NO_ARG0,
severity: ErrorSeverity.InvalidReact,
reason: `Expected a callback function to be passed to useCallback`,
linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO,
},
[ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT]: {
code: ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT,
severity: ErrorSeverity.InvalidReact,
reason: 'Unexpected spread argument to useMemo',
linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO,
},
[ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT]: {
code: ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT,
severity: ErrorSeverity.InvalidReact,
reason: 'Unexpected spread argument to useCallback',
linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO,
},
[ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS]: {
code: ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS,
severity: ErrorSeverity.InvalidReact,
reason: 'useMemo() callbacks may not accept parameters',
description:
'useMemo() callbacks are called by React to cache calculations across re-renders. They cannot accept parameters. Instead, directly reference the props, state, or local variables needed for the computation',
linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO,
},
[ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC]: {
code: ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC,
severity: ErrorSeverity.InvalidReact,
reason: 'useMemo() callbacks may not be async or generator functions',
description:
'useMemo() callbacks are called once and must synchronously return a value',
linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO,
},
[ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN]: {
code: ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN,
severity: ErrorSeverity.InvalidReact,
reason: 'useMemo() callbacks must return a value',
linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO,
},
[ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK]: {
code: ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK,
severity: ErrorSeverity.InvalidReact,
reason: `Expected the first argument to be an inline function expression`,
linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO,
},
[ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST]: {
code: ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST,
severity: ErrorSeverity.InvalidReact,
reason: `Expected the dependency list of useMemo or useCallback to be an array literal`,
linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO,
},
[ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY]: {
code: ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY,
severity: ErrorSeverity.InvalidReact,
reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO,
},
[ErrorCode.INVALID_SET_STATE_IN_RENDER]: {
code: ErrorCode.INVALID_SET_STATE_IN_RENDER,
severity: ErrorSeverity.InvalidReact,
reason: '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)',
linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER,
},
[ErrorCode.INVALID_SET_STATE_IN_MEMO]: {
code: ErrorCode.INVALID_SET_STATE_IN_MEMO,
severity: ErrorSeverity.InvalidReact,
reason: '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)',
linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER,
},
[ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS]: {
code: ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS,
severity: ErrorSeverity.InvalidReact,
reason:
'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)',
linterCategory: LinterCategory.UNNECESSARY_EFFECTS,
},
/** Invalid writes */
[ErrorCode.INVALID_WRITE_GLOBAL]: {
code: ErrorCode.INVALID_WRITE_GLOBAL,
severity: ErrorSeverity.InvalidReact,
reason: 'Cannot reassign variables declared outside of the component/hook',
description:
'Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)',
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX]: {
code: ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX,
severity: ErrorSeverity.InvalidReact,
reason:
'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX',
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT]: {
code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT,
severity: ErrorSeverity.InvalidReact,
reason: `Modifying a value returned from 'useContext()' is not allowed`,
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE]: {
code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE,
severity: ErrorSeverity.InvalidReact,
reason:
'Modifying a value returned from a function whose return value cannot be mutated',
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS]: {
code: ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS,
severity: ErrorSeverity.InvalidReact,
reason:
'Modifying component props or hook arguments is not allowed. Consider using a local variable instead',
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_WRITE_STATE]: {
code: ErrorCode.INVALID_WRITE_STATE,
severity: ErrorSeverity.InvalidReact,
reason:
"Modifying a value returned from 'useState()', which cannot be modified directly. Use the setter function to update instead",
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_WRITE_REDUCER_STATE]: {
code: ErrorCode.INVALID_WRITE_REDUCER_STATE,
severity: ErrorSeverity.InvalidReact,
reason:
"Modifying a value returned from 'useReducer()', which cannot be modified directly. Use the dispatch function to update instead",
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY]: {
code: ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY,
severity: ErrorSeverity.InvalidReact,
reason:
'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()',
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_WRITE_HOOK_CAPTURED]: {
code: ErrorCode.INVALID_WRITE_HOOK_CAPTURED,
severity: ErrorSeverity.InvalidReact,
reason:
'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook',
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_WRITE_HOOK_RETURN]: {
code: ErrorCode.INVALID_WRITE_HOOK_RETURN,
severity: ErrorSeverity.InvalidReact,
reason:
'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed',
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_ACCESS_BEFORE_INIT]: {
code: ErrorCode.INVALID_ACCESS_BEFORE_INIT,
severity: ErrorSeverity.InvalidReact,
reason: 'Cannot access variable before it is declared',
description: `Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized`,
linterCategory: LinterCategory.INVALID_WRITE,
},
[ErrorCode.INVALID_WRITE_GENERIC]: {
code: ErrorCode.INVALID_WRITE_GENERIC,
severity: ErrorSeverity.InvalidReact,
reason: 'This modifies a variable that React considers immutable',
linterCategory: LinterCategory.INVALID_WRITE,
},
/** Compiler Config */
[ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER]: {
code: ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER,
severity: ErrorSeverity.InvalidReact,
reason: 'Dynamic gating directive is not a valid JavaScript identifier',
linterCategory: LinterCategory.COMPILER_CONFIG,
},
[ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES]: {
code: ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES,
severity: ErrorSeverity.InvalidReact,
reason: 'Expected a single dynamic gating directive',
linterCategory: LinterCategory.COMPILER_CONFIG,
},
[ErrorCode.FILENAME_NOT_SET]: {
code: ErrorCode.FILENAME_NOT_SET,
severity: ErrorSeverity.InvalidConfig,
reason: `Expected a filename but found none`,
description:
"When the 'sources' config options is specified, the React compiler will only compile files with a name",
linterCategory: LinterCategory.COMPILER_CONFIG,
},
/** Effect dependencies */
[ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES]: {
code: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES,
reason:
'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior',
severity: ErrorSeverity.CannotPreserveMemoization,
linterCategory: LinterCategory.MEMOIZED_DEPENDENCIES,
},
/** Invalid / unsupported syntax */
[ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS]: {
code: ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS,
reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`,
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST]: {
code: ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST,
reason: `Expect \`const\` declaration not to be reassigned`,
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL]: {
code: ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL,
reason: `Expected variable declaration to be an identifier if no initializer was provided`,
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.UNSUPPORTED_WITH]: {
code: ErrorCode.UNSUPPORTED_WITH,
reason: `JavaScript 'with' syntax is not supported`,
description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`,
severity: ErrorSeverity.UnsupportedJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.UNSUPPORTED_INNER_CLASS]: {
code: ErrorCode.UNSUPPORTED_INNER_CLASS,
reason: 'Inline `class` declarations are not supported',
description: `Move class declarations outside of components/hooks`,
severity: ErrorSeverity.UnsupportedJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.INVALID_IMPORT_EXPORT]: {
code: ErrorCode.INVALID_IMPORT_EXPORT,
reason:
'JavaScript `import` and `export` statements may only appear at the top level of a module',
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.INVALID_TS_NAMESPACE]: {
code: ErrorCode.INVALID_TS_NAMESPACE,
reason:
'TypeScript `namespace` statements may only appear at the top level of a module',
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.UNSUPPORTED_NEW_EXPRESSION]: {
code: ErrorCode.UNSUPPORTED_NEW_EXPRESSION,
reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`,
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION]: {
code: ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION,
reason: `Expected sequence expression to have at least one expression`,
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.INVALID_QUASI_LENGTHS]: {
code: ErrorCode.INVALID_QUASI_LENGTHS,
reason: `Unexpected quasi and subexpression lengths in template literal`,
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION]: {
code: ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION,
reason: `Only object properties can be deleted`,
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.UNSUPPORTED_THROW_EXPRESSION]: {
code: ErrorCode.UNSUPPORTED_THROW_EXPRESSION,
reason: `Throw expressions are not supported`,
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.INVALID_JSX_NAMESPACED_NAME]: {
code: ErrorCode.INVALID_JSX_NAMESPACED_NAME,
reason: `Expected JSXNamespacedName to have no colons in the namespace or name`,
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.UNSUPPORTED_EVAL]: {
code: ErrorCode.UNSUPPORTED_EVAL,
reason: `The 'eval' function is not supported`,
description:
'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler',
severity: ErrorSeverity.UnsupportedJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME]: {
code: ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME,
reason: 'Expected a non-reserved identifier name',
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.INVALID_JAVASCRIPT_AST]: {
code: ErrorCode.INVALID_JAVASCRIPT_AST,
reason: 'Encountered invalid JavaScript',
severity: ErrorSeverity.InvalidJS,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.BAILOUT_ESLINT_SUPPRESSION]: {
code: ErrorCode.BAILOUT_ESLINT_SUPPRESSION,
reason:
'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled',
description:
'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior',
severity: ErrorSeverity.InvalidReact,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.MANUAL_MEMO_REMOVED]: {
code: ErrorCode.MANUAL_MEMO_REMOVED,
reason:
'Compilation skipped because existing memoization could not be preserved',
description:
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output',
severity: ErrorSeverity.CannotPreserveMemoization,
linterCategory: LinterCategory.TODO_SYNTAX,
},
/**
* This is left vague as fire is very experimental
*/
[ErrorCode.CANNOT_COMPILE_FIRE]: {
code: ErrorCode.CANNOT_COMPILE_FIRE,
reason: 'Cannot compile `fire`',
severity: ErrorSeverity.InvalidReact,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.DID_NOT_INFER_DEPS]: {
code: ErrorCode.DID_NOT_INFER_DEPS,
reason:
'Cannot infer dependencies of this effect. This will break your build!',
description:
'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics',
severity: ErrorSeverity.InvalidReact,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.BAILOUT_FLOW_SUPPRESSION]: {
code: ErrorCode.BAILOUT_FLOW_SUPPRESSION,
reason:
'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow',
description:
'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior',
severity: ErrorSeverity.InvalidReact,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
/** Syntax React Compiler may eventually support */
[ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER]: {
code: ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER,
reason: 'Support local variables named `fbt`',
description:
'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported',
severity: ErrorSeverity.Todo,
linterCategory: LinterCategory.TODO_SYNTAX,
},
[ErrorCode.TODO_DUPLICATE_FBT_TAGS]: {
code: ErrorCode.TODO_DUPLICATE_FBT_TAGS,
reason: 'Support duplicate fbt tags',
severity: ErrorSeverity.Todo,
linterCategory: LinterCategory.TODO_SYNTAX,
},
[ErrorCode.UNKNOWN_FUNCTION_PARAMETERS]: {
code: ErrorCode.UNKNOWN_FUNCTION_PARAMETERS,
severity: ErrorSeverity.Todo,
reason: 'Currently unsupported function parameter syntax',
linterCategory: LinterCategory.TODO_SYNTAX,
},
[ErrorCode.MANUAL_MEMO_MUTATED_LATER]: {
code: ErrorCode.MANUAL_MEMO_MUTATED_LATER,
reason:
'Compilation skipped because existing memoization could not be preserved',
description: [
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ',
'This dependency may be mutated later, which could cause the value to change unexpectedly',
].join(''),
severity: ErrorSeverity.CannotPreserveMemoization,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
[ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT]: {
code: ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT,
reason:
'Compilation skipped because existing memoization could not be preserved',
description:
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ' +
'The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected',
severity: ErrorSeverity.CannotPreserveMemoization,
linterCategory: LinterCategory.UNSUPPORTED_SYNTAX,
},
};

View File

@@ -0,0 +1,34 @@
export enum ErrorSeverity {
/**
* Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some
* misunderstanding on the users part.
*/
InvalidJS = 'InvalidJS',
/**
* JS syntax that is not supported and which we do not plan to support. Developers should
* rewrite to use supported forms.
*/
UnsupportedJS = 'UnsupportedJS',
/**
* Code that breaks the rules of React.
*/
InvalidReact = 'InvalidReact',
/**
* Incorrect configuration of the compiler.
*/
InvalidConfig = 'InvalidConfig',
/**
* Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve
* memoization.
*/
CannotPreserveMemoization = 'CannotPreserveMemoization',
/**
* Unhandled syntax that we don't support yet.
*/
Todo = 'Todo',
/**
* An unexpected internal error in the compiler that indicates critical issues that can panic
* the compiler.
*/
Invariant = 'Invariant',
}

View File

@@ -6,11 +6,7 @@
*/
import * as t from '@babel/types';
import {
CompilerError,
CompilerErrorDetail,
ErrorSeverity,
} from '../CompilerError';
import {CompilerError, CompilerErrorDetail} from '../CompilerError';
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
import {isHookName} from '../HIR/Environment';
import {
@@ -27,6 +23,7 @@ import {
} from '../HIR/visitors';
import {assertExhaustive} from '../Utils/utils';
import {Result} from '../Utils/Result';
import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes';
/**
* Represents the possible kinds of value which may be stored at a given Place during
@@ -111,8 +108,6 @@ export function validateHooksUsage(
// Once a particular hook has a conditional call error, don't report any further issues for this hook
setKind(place, Kind.Error);
const reason =
'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)';
const previousError =
typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined;
@@ -120,15 +115,15 @@ export function validateHooksUsage(
* In some circumstances such as optional calls, we may first encounter a "hook may not be referenced as normal values" error.
* If that same place is also used as a conditional call, upgrade the error to a conditonal hook error
*/
if (previousError === undefined || previousError.reason !== reason) {
if (
previousError === undefined ||
previousError.reason !==
ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason
) {
recordError(
place.loc,
new CompilerErrorDetail({
description: null,
reason,
CompilerErrorDetail.fromCode(ErrorCode.HOOK_CALL_STATIC, {
loc: place.loc,
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}),
);
}
@@ -139,13 +134,8 @@ export function validateHooksUsage(
if (previousError === undefined) {
recordError(
place.loc,
new CompilerErrorDetail({
description: null,
reason:
'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values',
CompilerErrorDetail.fromCode(ErrorCode.HOOK_INVALID_REFERENCE, {
loc: place.loc,
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}),
);
}
@@ -156,14 +146,12 @@ export function validateHooksUsage(
if (previousError === undefined) {
recordError(
place.loc,
new CompilerErrorDetail({
description: null,
reason:
'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks',
loc: place.loc,
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}),
CompilerErrorDetail.fromCode(
ErrorCodeDetails[ErrorCode.HOOK_CALL_REACTIVE].code,
{
loc: place.loc,
},
),
);
}
}
@@ -424,7 +412,7 @@ export function validateHooksUsage(
}
for (const [, error] of errorsByPlace) {
errors.push(error);
errors.pushErrorDetail(error);
}
return errors.asResult();
}
@@ -446,16 +434,10 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
: instr.value.property;
const hookKind = getHookKind(fn.env, callee.identifier);
if (hookKind != null) {
errors.pushErrorDetail(
new CompilerErrorDetail({
severity: ErrorSeverity.InvalidReact,
reason:
'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)',
loc: callee.loc,
description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`,
suggestions: null,
}),
);
errors.pushErrorCode(ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, {
loc: callee.loc,
description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`,
});
}
break;
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
import {CompilerDiagnostic, CompilerError, Effect} from '..';
import {HIRFunction, IdentifierId, Place} from '../HIR';
import {
eachInstructionLValue,
@@ -13,13 +13,17 @@ import {
eachTerminalOperand,
} from '../HIR/visitors';
import {getFunctionCallSignature} from '../Inference/InferReferenceEffects';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Ok, Result} from '../Utils/Result';
/**
* Validates that local variables cannot be reassigned after render.
* This prevents a category of bugs in which a closure captures a
* binding from one render but does not update
*/
export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void {
export function validateLocalsNotReassignedAfterRender(
fn: HIRFunction,
): Result<void, CompilerError> {
const contextVariables = new Set<IdentifierId>();
const reassignment = getContextReassignment(
fn,
@@ -35,18 +39,15 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void {
? `\`${reassignment.identifier.name.value}\``
: 'variable';
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot reassign variable after render completes',
description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`,
}).withDetail({
CompilerDiagnostic.fromCode(ErrorCode.REASSIGN_AFTER_RENDER).withDetail({
kind: 'error',
loc: reassignment.loc,
message: `Cannot reassign ${variable} after render completes`,
}),
);
throw errors;
return errors.asResult();
}
return Ok(undefined);
}
function getContextReassignment(
@@ -90,12 +91,9 @@ function getContextReassignment(
? `\`${reassignment.identifier.name.value}\``
: 'variable';
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot reassign variable in async function',
description:
'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead',
}).withDetail({
CompilerDiagnostic.fromCode(
ErrorCode.REASSIGN_IN_ASYNC,
).withDetail({
kind: 'error',
loc: reassignment.loc,
message: `Cannot reassign ${variable}`,

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '..';
import {CompilerError} from '..';
import {
Identifier,
Instruction,
@@ -22,6 +22,7 @@ import {
ReactiveFunctionVisitor,
visitReactiveFunction,
} from '../ReactiveScopes/visitors';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Result} from '../Utils/Result';
/**
@@ -108,12 +109,8 @@ class Visitor extends ReactiveFunctionVisitor<CompilerError> {
isUnmemoized(deps.identifier, this.scopes))
) {
state.push({
reason:
'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior',
description: null,
severity: ErrorSeverity.CannotPreserveMemoization,
errorCode: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES,
loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null,
suggestions: null,
});
}
}

View File

@@ -5,9 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..';
import {CompilerError, EnvironmentConfig} from '..';
import {HIRFunction, IdentifierId} from '../HIR';
import {DEFAULT_GLOBALS} from '../HIR/Globals';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Result} from '../Utils/Result';
export function validateNoCapitalizedCalls(
@@ -33,8 +34,6 @@ export function validateNoCapitalizedCalls(
const errors = new CompilerError();
const capitalLoadGlobals = new Map<IdentifierId, string>();
const capitalizedProperties = new Map<IdentifierId, string>();
const reason =
'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config';
for (const [, block] of fn.body.blocks) {
for (const {lvalue, value} of block.instructions) {
switch (value.kind) {
@@ -55,11 +54,9 @@ export function validateNoCapitalizedCalls(
const calleeIdentifier = value.callee.identifier.id;
const calleeName = capitalLoadGlobals.get(calleeIdentifier);
if (calleeName != null) {
CompilerError.throwInvalidReact({
reason,
errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, {
description: `${calleeName} may be a component.`,
loc: value.loc,
suggestions: null,
});
}
break;
@@ -78,12 +75,9 @@ export function validateNoCapitalizedCalls(
const propertyIdentifier = value.property.identifier.id;
const propertyName = capitalizedProperties.get(propertyIdentifier);
if (propertyName != null) {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason,
errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, {
description: `${propertyName} may be a component.`,
loc: value.loc,
suggestions: null,
});
}
break;

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity, SourceLocation} from '..';
import {CompilerError, SourceLocation} from '..';
import {
ArrayExpression,
BlockId,
@@ -19,6 +19,8 @@ import {
eachInstructionValueOperand,
eachTerminalOperand,
} from '../HIR/visitors';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Result} from '../Utils/Result';
/**
* Validates that useEffect is not used for derived computations which could/should
@@ -43,7 +45,9 @@ import {
* const fullName = firstName + ' ' + lastName;
* ```
*/
export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
export function validateNoDerivedComputationsInEffects(
fn: HIRFunction,
): Result<void, CompilerError> {
const candidateDependencies: Map<IdentifierId, ArrayExpression> = new Map();
const functions: Map<IdentifierId, FunctionExpression> = new Map();
const locals: Map<IdentifierId, IdentifierId> = new Map();
@@ -96,9 +100,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
}
}
}
if (errors.hasErrors()) {
throw errors;
}
return errors.asResult();
}
function validateEffect(
@@ -218,13 +220,6 @@ function validateEffect(
}
for (const loc of setStateLocations) {
errors.push({
reason:
'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)',
description: null,
severity: ErrorSeverity.InvalidReact,
loc,
suggestions: null,
});
errors.pushErrorCode(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, {loc});
}
}

View File

@@ -5,7 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
import {CompilerDiagnostic, CompilerError, Effect} from '..';
import {ErrorCode} from '../CompilerError';
import {
FunctionEffect,
HIRFunction,
@@ -65,11 +66,7 @@ export function validateNoFreezingKnownMutableFunctions(
? `\`${place.identifier.name.value}\``
: 'a local variable';
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 ${variable} after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.`,
})
CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER)
.withDetail({
kind: 'error',
loc: operand.loc,

View File

@@ -5,9 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
import {CompilerDiagnostic, CompilerError} from '..';
import {HIRFunction} from '../HIR';
import {getFunctionCallSignature} from '../Inference/InferReferenceEffects';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Result} from '../Utils/Result';
/**
@@ -35,19 +36,13 @@ export function validateNoImpureFunctionsInRender(
);
if (signature != null && signature.impure === true) {
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({
CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail({
kind: 'error',
loc: callee.loc,
message: 'Cannot call impure function',
message:
signature.canonicalName != null
? `\`${signature.canonicalName}\` is an impure function. `
: 'This is an impure function.',
}),
);
}

View File

@@ -5,8 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
import {CompilerDiagnostic, CompilerError} from '..';
import {BlockId, HIRFunction} from '../HIR';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Result} from '../Utils/Result';
import {retainWhere} from '../Utils/utils';
@@ -35,11 +36,7 @@ export function validateNoJSXInTryStatement(
case 'JsxExpression':
case 'JsxFragment': {
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({
CompilerDiagnostic.fromCode(ErrorCode.JSX_IN_TRY).withDetail({
kind: 'error',
loc: value.loc,
message: 'Avoid constructing JSX within try/catch',

View File

@@ -5,11 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {CompilerDiagnostic, CompilerError} from '../CompilerError';
import {
BlockId,
HIRFunction,
@@ -26,6 +22,7 @@ import {
eachPatternOperand,
eachTerminalOperand,
} from '../HIR/visitors';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Err, Ok, Result} from '../Utils/Result';
import {retainWhere} from '../Utils/utils';
@@ -467,11 +464,9 @@ function validateNoRefAccessInRenderImpl(
if (fnType.fn.readRefEffect) {
didError = true;
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot access refs during render',
description: ERROR_DESCRIPTION,
}).withDetail({
CompilerDiagnostic.fromCode(
ErrorCode.NO_REF_ACCESS_IN_RENDER,
).withDetail({
kind: 'error',
loc: callee.loc,
message: `This function accesses a ref value`,
@@ -730,15 +725,13 @@ function destructure(
function guardCheck(errors: CompilerError, operand: Place, env: Env): void {
if (env.get(operand.identifier.id)?.kind === 'Guard') {
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot access refs during render',
description: ERROR_DESCRIPTION,
}).withDetail({
kind: 'error',
loc: operand.loc,
message: `Cannot access ref value during render`,
}),
CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail(
{
kind: 'error',
loc: operand.loc,
message: `Cannot access ref value during render`,
},
),
);
}
}
@@ -754,15 +747,13 @@ function validateNoRefValueAccess(
(type?.kind === 'Structure' && type.fn?.readRefEffect)
) {
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot access refs during render',
description: ERROR_DESCRIPTION,
}).withDetail({
kind: 'error',
loc: (type.kind === 'RefValue' && type.loc) || operand.loc,
message: `Cannot access ref value during render`,
}),
CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail(
{
kind: 'error',
loc: (type.kind === 'RefValue' && type.loc) || operand.loc,
message: `Cannot access ref value during render`,
},
),
);
}
}
@@ -780,15 +771,13 @@ function validateNoRefPassedToFunction(
(type?.kind === 'Structure' && type.fn?.readRefEffect)
) {
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot access refs during render',
description: ERROR_DESCRIPTION,
}).withDetail({
kind: 'error',
loc: (type.kind === 'RefValue' && type.loc) || loc,
message: `Passing a ref to a function may read its value during render`,
}),
CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail(
{
kind: 'error',
loc: (type.kind === 'RefValue' && type.loc) || loc,
message: `Passing a ref to a function may read its value during render`,
},
),
);
}
}
@@ -802,15 +791,13 @@ function validateNoRefUpdate(
const type = destructure(env.get(operand.identifier.id));
if (type?.kind === 'Ref' || type?.kind === 'RefValue') {
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot access refs during render',
description: ERROR_DESCRIPTION,
}).withDetail({
kind: 'error',
loc: (type.kind === 'RefValue' && type.loc) || loc,
message: `Cannot update ref during render`,
}),
CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail(
{
kind: 'error',
loc: (type.kind === 'RefValue' && type.loc) || loc,
message: `Cannot update ref during render`,
},
),
);
}
}
@@ -823,21 +810,13 @@ function validateNoDirectRefValueAccess(
const type = destructure(env.get(operand.identifier.id));
if (type?.kind === 'RefValue') {
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot access refs during render',
description: ERROR_DESCRIPTION,
}).withDetail({
kind: 'error',
loc: type.loc ?? operand.loc,
message: `Cannot access ref value during render`,
}),
CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail(
{
kind: 'error',
loc: type.loc ?? operand.loc,
message: `Cannot access ref value during render`,
},
),
);
}
}
const ERROR_DESCRIPTION =
'React refs are values that are not needed for rendering. Refs should only be accessed ' +
'outside of render, such as in event handlers or effects. ' +
'Accessing a ref value (the `current` property) during render can cause your component ' +
'not to update as expected (https://react.dev/reference/react/useRef)';

View File

@@ -5,11 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {CompilerDiagnostic, CompilerError} from '../CompilerError';
import {
HIRFunction,
IdentifierId,
@@ -20,6 +16,7 @@ import {
Place,
} from '../HIR';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Result} from '../Utils/Result';
/**
@@ -95,19 +92,9 @@ export function validateNoSetStateInEffects(
const setState = setStateFunctions.get(arg.identifier.id);
if (setState !== undefined) {
errors.pushDiagnostic(
CompilerDiagnostic.create({
category:
'Calling setState synchronously within an effect can trigger cascading renders',
description:
'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' +
'In general, the body of an effect should do one or both of the following:\n' +
'* Update external systems with the latest state from React.\n' +
'* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' +
'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' +
'(https://react.dev/learn/you-might-not-need-an-effect)',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}).withDetail({
CompilerDiagnostic.fromCode(
ErrorCode.INVALID_SET_STATE_IN_EFFECTS,
).withDetail({
kind: 'error',
loc: setState.loc,
message:

View File

@@ -5,14 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {CompilerDiagnostic, CompilerError} from '../CompilerError';
import {HIRFunction, IdentifierId, isSetStateType} from '../HIR';
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Result} from '../Utils/Result';
/**
@@ -127,14 +124,9 @@ function validateNoSetStateInRenderImpl(
) {
if (activeManualMemoId !== 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({
CompilerDiagnostic.fromCode(
ErrorCode.INVALID_SET_STATE_IN_MEMO,
).withDetail({
kind: 'error',
loc: callee.loc,
message: 'Found setState() within useMemo()',
@@ -142,17 +134,12 @@ function validateNoSetStateInRenderImpl(
);
} else if (unconditionalBlocks.has(block.id)) {
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({
CompilerDiagnostic.fromCode(
ErrorCode.INVALID_SET_STATE_IN_RENDER,
).withDetail({
kind: 'error',
loc: callee.loc,
message: 'Found setState() within useMemo()',
message: 'Found setState() during render',
}),
);
}

View File

@@ -5,11 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {CompilerDiagnostic, CompilerError, ErrorCode} from '../CompilerError';
import {
DeclarationId,
Effect,
@@ -280,13 +276,8 @@ function validateInferredDep(
}
}
errorState.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.CannotPreserveMemoization,
category:
'Compilation skipped because existing memoization could not be preserved',
description: [
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ',
'The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. ',
CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, {
description:
DEBUG ||
// If the dependency is a named variable then we can report it. Otherwise only print in debug mode
(dep.identifier.name != null && dep.identifier.name.kind === 'named')
@@ -298,11 +289,8 @@ function validateInferredDep(
errorDiagnostic
? getCompareDependencyResultDescription(errorDiagnostic)
: 'Inferred dependency not present in source'
}.`
}`
: '',
]
.join('')
.trim(),
suggestions: null,
}).withDetail({
kind: 'error',
@@ -534,15 +522,9 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
!this.prunedScopes.has(identifier.scope.id)
) {
state.errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.CannotPreserveMemoization,
category:
'Compilation skipped because existing memoization could not be preserved',
description: [
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ',
'This dependency may be mutated later, which could cause the value to change unexpectedly.',
].join(''),
}).withDetail({
CompilerDiagnostic.fromCode(
ErrorCode.MANUAL_MEMO_MUTATED_LATER,
).withDetail({
kind: 'error',
loc,
message: 'This dependency may be modified later',
@@ -582,18 +564,10 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
for (const identifier of decls) {
if (isUnmemoized(identifier, this.scopes)) {
state.errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.CannotPreserveMemoization,
category:
'Compilation skipped because existing memoization could not be preserved',
description: [
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. ',
DEBUG
? `${printIdentifier(identifier)} was not memoized.`
: '',
]
.join('')
.trim(),
CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_REMOVED, {
description: DEBUG
? `${printIdentifier(identifier)} was not memoized`
: '',
}).withDetail({
kind: 'error',
loc,

View File

@@ -5,12 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {CompilerDiagnostic, CompilerError} from '../CompilerError';
import {HIRFunction, IdentifierId, SourceLocation} from '../HIR';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Result} from '../Utils/Result';
/**
@@ -64,9 +61,7 @@ export function validateStaticComponents(
);
if (location != null) {
error.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot create components during render',
CompilerDiagnostic.fromCode(ErrorCode.STATIC_COMPONENTS, {
description: `Components created during render will reset their state each time they are created. Declare components outside of render. `,
})
.withDetail({

View File

@@ -5,12 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {CompilerDiagnostic, CompilerError} from '../CompilerError';
import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
import {ErrorCode} from '../Utils/CompilerErrorCodes';
import {Result} from '../Utils/Result';
export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
@@ -73,13 +70,9 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
? 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({
CompilerDiagnostic.fromCode(
ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS,
).withDetail({
kind: 'error',
loc,
message: 'Callbacks with parameters are not supported',
@@ -89,14 +82,9 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
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({
CompilerDiagnostic.fromCode(
ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC,
).withDetail({
kind: 'error',
loc: body.loc,
message: 'Async and generator functions are not supported',

View File

@@ -19,13 +19,13 @@ Found 1 error:
Error: Cannot reassign variables declared outside of the component/hook
Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render).
error.assign-global-in-component-tag-function.ts:3:4
1 | function Component() {
2 | const Foo = () => {
> 3 | someGlobal = true;
| ^^^^^^^^^^ `someGlobal` cannot be reassigned
| ^^^^^^^^^^ `someGlobal` should not be reassigned
4 | };
5 | return <Foo />;
6 | }

View File

@@ -22,13 +22,13 @@ Found 1 error:
Error: Cannot reassign variables declared outside of the component/hook
Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render).
error.assign-global-in-jsx-children.ts:3:4
1 | function Component() {
2 | const foo = () => {
> 3 | someGlobal = true;
| ^^^^^^^^^^ `someGlobal` cannot be reassigned
| ^^^^^^^^^^ `someGlobal` should not be reassigned
4 | };
5 | // Children are generally access/called during render, so
6 | // modifying a global in a children function is almost

View File

@@ -18,13 +18,15 @@ function Component() {
```
Found 1 error:
Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
Error: Cannot reassign variables declared outside of the component/hook
Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render).
error.assign-global-in-jsx-spread-attribute.ts:4:4
2 | function Component() {
3 | const foo = () => {
> 4 | someGlobal = true;
| ^^^^^^^^^^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
| ^^^^^^^^^^ Cannot reassign variables declared outside of the component/hook
5 | };
6 | return <div {...foo} />;
7 | }

View File

@@ -20,7 +20,7 @@ Found 1 error:
Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `$FlowFixMe[react-rule-hook]`
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `$FlowFixMe[react-rule-hook]`.
error.bailout-on-flow-suppression.ts:4:2
2 |

View File

@@ -23,7 +23,7 @@ Found 2 errors:
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable my-app/react-rule`
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable my-app/react-rule`.
error.bailout-on-suppression-of-custom-rule.ts:3:0
1 | // @eslintSuppressionRules:["my-app","react-rule"]
@@ -36,7 +36,7 @@ error.bailout-on-suppression-of-custom-rule.ts:3:0
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line my-app/react-rule`
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line my-app/react-rule`.
error.bailout-on-suppression-of-custom-rule.ts:7:2
5 | 'use forget';

View File

@@ -40,7 +40,7 @@ Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate a local variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
This argument is a function which may reassign or mutate a variable 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 | );

View File

@@ -18,7 +18,7 @@ Found 1 error:
Error: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config
Bar may be a component..
Bar may be a component.
error.capitalized-function-call-aliased.ts:4:2
2 | function Foo() {

View File

@@ -19,7 +19,7 @@ Found 1 error:
Error: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config
SomeFunc may be a component..
SomeFunc may be a component.
error.capitalized-function-call.ts:3:12
1 | // @validateNoCapitalizedCalls

View File

@@ -19,7 +19,7 @@ Found 1 error:
Error: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config
SomeFunc may be a component..
SomeFunc may be a component.
error.capitalized-method-call.ts:3:12
1 | // @validateNoCapitalizedCalls

View File

@@ -36,7 +36,7 @@ Found 2 errors:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.capture-ref-for-mutation.ts:12:13
10 | };
@@ -49,7 +49,7 @@ error.capture-ref-for-mutation.ts:12:13
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.capture-ref-for-mutation.ts:15:13
13 | };

View File

@@ -30,9 +30,9 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Error: Cannot reassign variable after render completes
Error: Cannot reassign local variables after render completes
Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.
Reassigning local variables 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 | };

View File

@@ -19,9 +19,9 @@ function Component() {
```
Found 1 error:
Error: Cannot reassign variable after render completes
Error: Cannot reassign local variables after render completes
Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.
Reassigning local variables 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;

View File

@@ -17,9 +17,9 @@ function Component() {
```
Found 1 error:
Error: Cannot reassign variable after render completes
Error: Cannot reassign local variables after render completes
Reassigning `callback` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.
Reassigning local variables 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() {

View File

@@ -24,7 +24,7 @@ Found 2 errors:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.hook-ref-value.ts:5:23
3 | function Component(props) {
@@ -37,7 +37,7 @@ error.hook-ref-value.ts:5:23
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.hook-ref-value.ts:5:23
3 | function Component(props) {

View File

@@ -19,7 +19,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-access-ref-during-render.ts:4:16
2 | function Component(props) {

View File

@@ -30,7 +30,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-access-ref-in-reducer-init.ts:8:4
6 | (state, action) => state + action,

View File

@@ -26,7 +26,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-access-ref-in-reducer.ts:5:29
3 | function Component(props) {

View File

@@ -22,7 +22,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-access-ref-in-render-mutate-object-with-ref-function.ts:7:19
5 | const object = {};

View File

@@ -26,7 +26,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-access-ref-in-state-initializer.ts:5:27
3 | function Component(props) {

View File

@@ -23,7 +23,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-aliased-ref-in-callback-invoked-during-render-.ts:9:33
7 | return <Foo item={item} current={current} />;

View File

@@ -22,7 +22,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
4 | component Example() {
5 | const fooRef = makeObject_Primitives();

View File

@@ -26,7 +26,7 @@ Found 2 errors:
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)
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(() => {
@@ -39,7 +39,7 @@ error.invalid-conditional-setState-in-useMemo.ts:7:6
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)
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) {

View File

@@ -17,12 +17,12 @@ Found 1 error:
Error: Cannot reassign variables declared outside of the component/hook
Variable `x` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render).
error.invalid-destructure-assignment-to-global.ts:2:3
1 | function useFoo(props) {
> 2 | [x] = props;
| ^ `x` cannot be reassigned
| ^ `x` should not be reassigned
3 | return {x};
4 | }
5 |

View File

@@ -19,13 +19,13 @@ Found 1 error:
Error: Cannot reassign variables declared outside of the component/hook
Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render).
error.invalid-destructure-to-local-global-variables.ts:3:6
1 | function Component(props) {
2 | let a;
> 3 | [a, b] = props.value;
| ^ `b` cannot be reassigned
| ^ `b` should not be reassigned
4 |
5 | return [a, b];
6 | }

View File

@@ -20,7 +20,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-disallow-mutating-ref-in-render.ts:4:2
2 | function Component() {

View File

@@ -25,7 +25,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-disallow-mutating-refs-in-render-transitive.ts:9:2
7 | };

View File

@@ -22,7 +22,7 @@ Found 1 error:
Error: This value cannot be modified
Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead.
Modifying a value returned from 'useState()', which cannot be modified directly. Use the setter function to update instead.
error.invalid-function-expression-mutates-immutable-value.ts:5:4
3 | const onChange = e => {

View File

@@ -39,13 +39,13 @@ Found 1 error:
Error: Cannot reassign variables declared outside of the component/hook
Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render).
error.invalid-global-reassignment-indirect.ts:9:4
7 |
8 | const setGlobal = () => {
> 9 | someGlobal = true;
| ^^^^^^^^^^ `someGlobal` cannot be reassigned
| ^^^^^^^^^^ `someGlobal` should not be reassigned
10 | };
11 | const indirectSetGlobal = () => {
12 | setGlobal();

View File

@@ -42,13 +42,13 @@ Found 1 error:
Error: Cannot access variable before it is declared
`setState` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time.
Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized.
error.invalid-hoisting-setstate.ts:19:18
17 | * $2 = Function context=setState
18 | */
> 19 | useEffect(() => setState(2), []);
| ^^^^^^^^ `setState` accessed before it is declared
| ^^^^^^^^ `setState` is accessed before it is declared
20 |
21 | const [state, setState] = useState(0);
22 | return <Stringify state={state} />;

View File

@@ -21,7 +21,7 @@ Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `cache` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
This argument is a function which may reassign or mutate a variable 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() {

View File

@@ -19,41 +19,41 @@ function Component() {
```
Found 3 errors:
Error: Cannot call impure function during render
Error: Cannot call impure functions during render
`Date.now` 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)
Calling an impure function can produce unstable results that change 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).
error.invalid-impure-functions-in-render.ts:4:15
2 |
3 | function Component() {
> 4 | const date = Date.now();
| ^^^^^^^^^^ Cannot call impure function
| ^^^^^^^^^^ `Date.now` is an impure function.
5 | const now = performance.now();
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
Error: Cannot call impure function during render
Error: Cannot call impure functions during render
`performance.now` 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)
Calling an impure function can produce unstable results that change 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).
error.invalid-impure-functions-in-render.ts:5:14
3 | function Component() {
4 | const date = Date.now();
> 5 | const now = performance.now();
| ^^^^^^^^^^^^^^^^^ Cannot call impure function
| ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function.
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
Error: Cannot call impure function during render
Error: Cannot call impure functions during render
`Math.random` 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)
Calling an impure function can produce unstable results that change 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).
error.invalid-impure-functions-in-render.ts:6:15
4 | const date = Date.now();
5 | const now = performance.now();
> 6 | const rand = Math.random();
| ^^^^^^^^^^^^^ Cannot call impure function
| ^^^^^^^^^^^^^ `Math.random` is an impure function.
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
9 |

View File

@@ -28,7 +28,7 @@ Found 1 error:
Error: This value cannot be modified
Modifying a value returned from 'useContext()' is not allowed..
Modifying a value returned from 'useContext()' is not allowed.
error.invalid-mutate-context-in-callback.ts:12:4
10 | // independently

View File

@@ -18,7 +18,7 @@ Found 1 error:
Error: This value cannot be modified
Modifying a value returned from 'useContext()' is not allowed..
Modifying a value returned from 'useContext()' is not allowed.
error.invalid-mutate-context.ts:3:2
1 | function Component(props) {

View File

@@ -23,7 +23,7 @@ Found 1 error:
Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
Cannot reassign variables declared outside of the component/hook.
error.invalid-mutation-of-possible-props-phi-indirect.ts:4:4
2 | let x = cond ? someGlobal : props.foo;

View File

@@ -48,9 +48,9 @@ function Component() {
```
Found 1 error:
Error: Cannot reassign variable after render completes
Error: Cannot reassign local variables after render completes
Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.
Reassigning local variables 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

View File

@@ -21,7 +21,7 @@ Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `cache` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
This argument is a function which may reassign or mutate a variable 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');

View File

@@ -19,7 +19,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-pass-ref-to-function.ts:4:16
2 | function Component(props) {

View File

@@ -18,7 +18,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-read-ref-prop-in-render-destructure.ts:3:16
1 | // @validateRefAccessDuringRender @compilationMode:"infer"

View File

@@ -18,7 +18,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-read-ref-prop-in-render-property-load.ts:3:16
1 | // @validateRefAccessDuringRender @compilationMode:"infer"

View File

@@ -15,7 +15,7 @@ function Component() {
```
Found 1 error:
Error: Cannot reassign a `const` variable
Error: Expect `const` declaration not to be reassigned
`x` is declared as const.
@@ -23,7 +23,7 @@ error.invalid-reassign-const.ts:3:2
1 | function Component() {
2 | const x = 0;
> 3 | x = 1;
| ^ Cannot reassign a `const` variable
| ^ Expect `const` declaration not to be reassigned
4 | }
5 |
```

View File

@@ -17,9 +17,9 @@ function useFoo() {
```
Found 1 error:
Error: Cannot reassign variable after render completes
Error: Cannot reassign local variables after render completes
Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.
Reassigning local variables 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;

View File

@@ -29,7 +29,7 @@ Found 1 error:
Error: Cannot reassign variable in async function
Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead
Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead.
error.invalid-reassign-local-variable-in-async-callback.ts:8:6
6 | // after render, so this should error regardless of where this ends up

View File

@@ -49,9 +49,9 @@ function Component() {
```
Found 1 error:
Error: Cannot reassign variable after render completes
Error: Cannot reassign local variables after render completes
Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.
Reassigning local variables 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 |

View File

@@ -50,9 +50,9 @@ function Component() {
```
Found 1 error:
Error: Cannot reassign variable after render completes
Error: Cannot reassign local variables after render completes
Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.
Reassigning local variables 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 |

View File

@@ -43,9 +43,9 @@ function Component() {
```
Found 1 error:
Error: Cannot reassign variable after render completes
Error: Cannot reassign local variables after render completes
Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.
Reassigning local variables 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 |

View File

@@ -22,7 +22,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-ref-in-callback-invoked-during-render.ts:8:33
6 | return <Foo item={item} current={current} />;

View File

@@ -18,7 +18,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-ref-value-as-props.ts:4:19
2 | function Component(props) {

View File

@@ -23,7 +23,7 @@ Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `cache` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
This argument is a function which may reassign or mutate a variable 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

View File

@@ -19,7 +19,7 @@ Found 2 errors:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-set-and-read-ref-during-render.ts:4:2
2 | function Component(props) {
@@ -32,7 +32,7 @@ error.invalid-set-and-read-ref-during-render.ts:4:2
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-set-and-read-ref-during-render.ts:5:9
3 | const ref = useRef(null);

View File

@@ -19,7 +19,7 @@ Found 2 errors:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-set-and-read-ref-nested-property-during-render.ts:4:2
2 | function Component(props) {
@@ -32,7 +32,7 @@ error.invalid-set-and-read-ref-nested-property-during-render.ts:4:2
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-set-and-read-ref-nested-property-during-render.ts:5:9
3 | const ref = useRef({inner: null});

View File

@@ -30,7 +30,7 @@ Found 1 error:
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)
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 |

View File

@@ -24,7 +24,7 @@ Found 2 errors:
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)
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 |
@@ -37,7 +37,7 @@ error.invalid-setState-in-useMemo.ts:6:4
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)
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(() => {

View File

@@ -21,7 +21,7 @@ Found 2 errors:
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`.
error.invalid-sketchy-code-use-forget.ts:1:0
> 1 | /* eslint-disable react-hooks/rules-of-hooks */
@@ -32,7 +32,7 @@ error.invalid-sketchy-code-use-forget.ts:1:0
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks`
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks`.
error.invalid-sketchy-code-use-forget.ts:5:2
3 | 'use forget';

View File

@@ -51,7 +51,7 @@ Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `cache` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
This argument is a function which may reassign or mutate a variable after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
19 | map: TInput => TOutput
20 | ): TInput => TOutput {

View File

@@ -40,7 +40,7 @@ Found 1 error:
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`.
error.invalid-unclosed-eslint-suppression.ts:2:0
1 | // Note: Everything below this is sketchy

View File

@@ -23,26 +23,26 @@ Found 2 errors:
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)
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);
| ^^^^ Found setState() within useMemo()
| ^^^^ Found setState() during render
7 | aliased(2);
8 |
9 | return x;
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)
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);
| ^^^^^^^ Found setState() within useMemo()
| ^^^^^^^ Found setState() during render
8 |
9 | return x;
10 | }

View File

@@ -26,7 +26,7 @@ Found 2 errors:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-use-ref-added-to-dep-without-type-info.ts:10:21
8 | // however, this is an instance of accessing a ref during render and is disallowed
@@ -39,7 +39,7 @@ error.invalid-use-ref-added-to-dep-without-type-info.ts:10:21
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-use-ref-added-to-dep-without-type-info.ts:12:28
10 | const x = {a, val: val.ref.current};

View File

@@ -17,7 +17,7 @@ Found 1 error:
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.
useMemo() callbacks are called by React to cache calculations across re-renders. They cannot accept 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) {

View File

@@ -21,7 +21,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-write-but-dont-read-ref-in-render.ts:5:2
3 | const ref = useRef(null);

View File

@@ -19,7 +19,7 @@ Found 1 error:
Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
error.invalid-write-ref-prop-in-render.ts:4:2
2 | function Component(props) {

View File

@@ -21,7 +21,7 @@ Found 1 error:
Error: This value cannot be modified
Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead.
Modifying a value returned from 'useState()', which cannot be modified directly. Use the setter function to update instead.
error.modify-state-2.ts:6:2
4 | const [state, setState] = useState({foo: {bar: 3}});

View File

@@ -20,7 +20,7 @@ Found 1 error:
Error: This value cannot be modified
Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead.
Modifying a value returned from 'useState()', which cannot be modified directly. Use the setter function to update instead.
error.modify-state.ts:5:2
3 | function Foo() {

View File

@@ -20,7 +20,7 @@ Found 1 error:
Error: This value cannot be modified
Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead.
Modifying a value returned from 'useReducer()', which cannot be modified directly. Use the dispatch function to update instead.
error.modify-useReducer-state.ts:5:2
3 | function Foo() {

View File

@@ -34,9 +34,9 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Error: Cannot reassign variable after render completes
Error: Cannot reassign local variables after render completes
Reassigning `a` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.
Reassigning local variables 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 = () => {

View File

@@ -19,7 +19,7 @@ Found 1 error:
Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
Cannot reassign variables declared outside of the component/hook.
error.mutate-property-from-global.ts:4:9
2 |

View File

@@ -21,7 +21,7 @@ Found 2 errors:
Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
Cannot reassign variables declared outside of the component/hook.
error.not-useEffect-external-mutate.ts:5:4
3 | function Component(props) {
@@ -34,7 +34,7 @@ error.not-useEffect-external-mutate.ts:5:4
Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
Cannot reassign variables declared outside of the component/hook.
error.not-useEffect-external-mutate.ts:6:4
4 | foo(() => {

View File

@@ -24,13 +24,15 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Error: Modifying a variable defined outside a component or hook is not allowed. Consider using an effect
Error: Cannot reassign variables declared outside of the component/hook
Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render).
error.object-capture-global-mutation.ts:4:4
2 | function Foo() {
3 | const x = () => {
> 4 | window.href = 'foo';
| ^^^^^^ Modifying a variable defined outside a component or hook is not allowed. Consider using an effect
| ^^^^^^ Cannot reassign variables declared outside of the component/hook
5 | };
6 | const y = {x};
7 | return <Bar y={y} />;

Some files were not shown because too many files have changed in this diff Show More