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');
}
});
}
}