import type { SourceLocation } from "./HIR";
import { assertExhaustive } from "./Utils/utils";
export enum ErrorSeverity {
InvalidJS = "InvalidJS",
InvalidReact = "InvalidReact",
InvalidConfig = "InvalidConfig",
CannotPreserveMemoization = "CannotPreserveMemoization",
Todo = "Todo",
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 | undefined;
};
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 this.printErrorMessage();
}
}
export class CompilerError extends Error {
details: Array<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;
}
static throw(options: CompilerErrorDetailOptions): never {
const errors = new CompilerError();
errors.pushErrorDetail(new CompilerErrorDetail(options));
throw errors;
}
constructor(...args: Array<any>) {
super(...args);
this.name = "ReactCompilerError";
}
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;
}
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.CannotPreserveMemoization:
case ErrorSeverity.Todo:
return false;
default:
assertExhaustive(detail.severity, "Unhandled error severity");
}
});
}
}