Files
react/compiler/packages/babel-plugin-react-forget/src/CompilerError.ts
T
Sathya Gunasekaran 42497b60f5 [error] Prefix side-effecting function names with throw
I did a double take when I thought we didn't handle returning the 

error when reading the code and when I edited the code, typescript told 

me that there's no need to return as creating the error will throw. 

This PR makes it clear from the name of the function that we will throw.
2023-11-08 16:09:13 +00:00

224 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { SourceLocation } from "./HIR";
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",
// Code that breaks the rules of React.
InvalidReact = "InvalidReact",
// Incorrect configuration of the compiler.
InvalidConfig = "InvalidConfig",
// 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",
}
export enum CompilerSuggestionOperation {
InsertBefore,
InsertAfter,
Remove,
Replace,
}
export type CompilerSuggestion =
| {
op:
| CompilerSuggestionOperation.InsertAfter
| CompilerSuggestionOperation.InsertBefore
| CompilerSuggestionOperation.Replace;
range: [number, number];
description: string;
text: string;
}
| {
op: CompilerSuggestionOperation.Remove;
range: [number, number];
description: string;
};
export type CompilerErrorDetailOptions = {
reason: string;
description?: string | null | undefined;
severity: ErrorSeverity;
loc: SourceLocation | null;
suggestions: Array<CompilerSuggestion> | null;
};
/*
* Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then
* aggregated into a single {@link CompilerError} later.
*/
export class CompilerErrorDetail {
options: CompilerErrorDetailOptions;
constructor(options: CompilerErrorDetailOptions) {
this.options = options;
}
get reason(): CompilerErrorDetailOptions["reason"] {
return this.options.reason;
}
get description(): CompilerErrorDetailOptions["description"] {
return this.options.description;
}
get severity(): CompilerErrorDetailOptions["severity"] {
return this.options.severity;
}
get loc(): CompilerErrorDetailOptions["loc"] {
return this.options.loc;
}
get suggestions(): CompilerErrorDetailOptions["suggestions"] {
return this.options.suggestions;
}
printErrorMessage(): string {
const buffer = [`${this.severity}: ${this.reason}`];
if (this.description !== null) {
buffer.push(`. ${this.description}`);
}
if (this.loc != null && typeof this.loc !== "symbol") {
buffer.push(` (${this.loc.start.line}:${this.loc.end.line})`);
}
return buffer.join("");
}
toString(): string {
return `[ReactForget] ${this.printErrorMessage()}`;
}
}
export class CompilerError extends Error {
details: CompilerErrorDetail[] = [];
static invariant(
condition: unknown,
options: Omit<CompilerErrorDetailOptions, "severity">
): asserts condition {
if (!condition) {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
...options,
severity: ErrorSeverity.Invariant,
})
);
throw errors;
}
}
static throwTodo(
options: Omit<CompilerErrorDetailOptions, "severity">
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({ ...options, severity: ErrorSeverity.Todo })
);
throw errors;
}
static throwInvalidJS(
options: Omit<CompilerErrorDetailOptions, "severity">
): 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,
})
);
throw errors;
}
static throwInvalidConfig(
options: Omit<CompilerErrorDetailOptions, "severity">
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
...options,
severity: ErrorSeverity.InvalidConfig,
})
);
throw errors;
}
constructor(...args: any[]) {
super(...args);
this.name = "ReactForgetCompilerError";
}
override get message(): string {
return this.toString();
}
override set message(_message: string) {}
override toString(): string {
return this.details.map((detail) => detail.toString()).join("\n\n");
}
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,
});
return this.pushErrorDetail(detail);
}
pushErrorDetail(detail: CompilerErrorDetail): CompilerErrorDetail {
this.details.push(detail);
return detail;
}
hasErrors(): boolean {
return this.details.length > 0;
}
/*
* An error is critical if it means the compiler has entered into a broken state and cannot
* continue safely. Other expected errors such as Todos mean that we can skip over that component
* but otherwise continue compiling the rest of the app.
*/
isCritical(): boolean {
return this.details.some((detail) => {
switch (detail.severity) {
case ErrorSeverity.Invariant:
case ErrorSeverity.InvalidJS:
case ErrorSeverity.InvalidReact:
case ErrorSeverity.InvalidConfig:
return true;
case ErrorSeverity.Todo:
return false;
default:
assertExhaustive(detail.severity, "Unhandled error severity");
}
});
}
}