Refactor CompilerErrorDetails
- Made most static methods on CompilerError take a single options object as an argument. With the exception of invariant which takes a condition and an options object. - Added a new `suggestions` field on CompilerErrorDetail, which we'll use to provide eslint suggestions - Updated eslint-plugin-react-forget to handle suggestions
Lauren Tan committed
Jun 27, 2023 at 14:01 UTC
448ed2b67b7bbfe2882aadf9eb92e252a0ff4b1f
39 files changed
+1037
-673
compiler/forget/packages/babel-plugin-react-forget/src/CompilerError.ts
+54
-47
@@ -6,7 +6,6 @@
6
*/
7
8
import type { SourceLocation } from "./HIR";
9
-import type { ExtractClassProperties } from "./Utils/types";
9
import { assertExhaustive } from "./Utils/utils";
10
11
export enum ErrorSeverity {
@@ -33,29 +32,61 @@ export enum ErrorSeverity {
32
Invariant = "Invariant",
33
}
34
36
-export type CompilerErrorOptions = {
35
+export enum CompilerSuggestionOperation {
36
+ InsertBefore,
37
+ InsertAfter,
38
+ Remove,
39
+ Replace,
40
+}
41
+export type CompilerSuggestion =
42
+ | {
43
+ op:
44
+ | CompilerSuggestionOperation.InsertAfter
45
+ | CompilerSuggestionOperation.InsertBefore
46
+ | CompilerSuggestionOperation.Replace;
47
+ range: [number, number];
48
+ description: string;
49
+ text: string;
50
+ }
51
+ | {
52
+ op: CompilerSuggestionOperation.Remove;
53
+ range: [number, number];
54
+ description: string;
55
+ };
56
+
57
+export type CompilerErrorDetailOptions = {
58
reason: string;
59
description?: string | null | undefined;
60
severity: ErrorSeverity;
61
loc: SourceLocation | null;
62
+ suggestions: Array<CompilerSuggestion> | null;
63
};
42
-type CompilerErrorDetailOptions = ExtractClassProperties<CompilerErrorDetail>;
64
65
/**
66
* Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then
67
* aggregated into a single {@link CompilerError} later.
68
*/
69
export class CompilerErrorDetail {
49
- reason: string;
50
- description: string | null;
51
- severity: ErrorSeverity;
52
- loc: SourceLocation | null;
70
+ options: CompilerErrorDetailOptions;
71
72
constructor(options: CompilerErrorDetailOptions) {
55
- this.reason = options.reason;
56
- this.description = options.description;
57
- this.severity = options.severity;
58
- this.loc = options.loc;
73
+ this.options = options;
74
+ }
75
+
76
+ get reason(): CompilerErrorDetailOptions["reason"] {
77
+ return this.options.reason;
78
+ }
79
+ get description(): CompilerErrorDetailOptions["description"] {
80
+ return this.options.description;
81
+ }
82
+ get severity(): CompilerErrorDetailOptions["severity"] {
83
+ return this.options.severity;
84
+ }
85
+ get loc(): CompilerErrorDetailOptions["loc"] {
86
+ return this.options.loc;
87
+ }
88
+ get suggestions(): CompilerErrorDetailOptions["suggestions"] {
89
+ return this.options.suggestions;
90
}
91
92
printErrorMessage(): string {
@@ -79,17 +110,13 @@ export class CompilerError extends Error {
110
111
static invariant(
112
condition: unknown,
82
- reason: string,
83
- loc: SourceLocation | null,
84
- description: string | null = null
113
+ options: Omit<CompilerErrorDetailOptions, "severity">
114
): asserts condition {
115
if (!condition) {
116
const errors = new CompilerError();
117
errors.pushErrorDetail(
118
new CompilerErrorDetail({
90
- description,
91
- loc,
92
- reason,
119
+ ...options,
120
severity: ErrorSeverity.Invariant,
121
})
122
);
@@ -97,34 +124,21 @@ export class CompilerError extends Error {
124
}
125
}
126
100
- static todo(
101
- reason: string,
102
- loc: SourceLocation | null,
103
- description: string | null = null
104
- ): never {
127
+ static todo(options: Omit<CompilerErrorDetailOptions, "severity">): never {
128
const errors = new CompilerError();
129
errors.pushErrorDetail(
107
- new CompilerErrorDetail({
108
- description,
109
- loc,
110
- reason,
111
- severity: ErrorSeverity.Todo,
112
- })
130
+ new CompilerErrorDetail({ ...options, severity: ErrorSeverity.Todo })
131
);
132
throw errors;
133
}
134
135
static invalidInput(
118
- reason: string,
119
- loc: SourceLocation | null,
120
- description: string | null = null
136
+ options: Omit<CompilerErrorDetailOptions, "severity">
137
): never {
138
const errors = new CompilerError();
139
errors.pushErrorDetail(
140
new CompilerErrorDetail({
125
- description,
126
- loc,
127
- reason,
141
+ ...options,
142
severity: ErrorSeverity.InvalidInput,
143
})
144
);
@@ -132,16 +146,12 @@ export class CompilerError extends Error {
146
}
147
148
static invalidReact(
135
- reason: string,
136
- loc: SourceLocation | null,
137
- description: string | null = null
149
+ options: Omit<CompilerErrorDetailOptions, "severity">
150
): never {
151
const errors = new CompilerError();
152
errors.pushErrorDetail(
153
new CompilerErrorDetail({
142
- description,
143
- loc,
144
- reason,
154
+ ...options,
155
severity: ErrorSeverity.InvalidReact,
156
})
157
);
@@ -149,16 +159,12 @@ export class CompilerError extends Error {
159
}
160
161
static invalidConfig(
152
- reason: string,
153
- loc: SourceLocation | null,
154
- description: string | null = null
162
+ options: Omit<CompilerErrorDetailOptions, "severity">
163
): never {
164
const errors = new CompilerError();
165
errors.pushErrorDetail(
166
new CompilerErrorDetail({
159
- description,
160
- loc,
161
- reason,
167
+ ...options,
168
severity: ErrorSeverity.InvalidConfig,
169
})
170
);
@@ -180,11 +186,12 @@ export class CompilerError extends Error {
186
return this.details.map((detail) => detail.toString()).join("\n\n");
187
}
188
183
- push(options: CompilerErrorOptions): CompilerErrorDetail {
189
+ push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
190
const detail = new CompilerErrorDetail({
191
reason: options.reason,
192
description: options.description ?? null,
193
severity: options.severity,
194
+ suggestions: options.suggestions,
195
loc: typeof options.loc === "symbol" ? null : options.loc,
196
});
197
return this.pushErrorDetail(detail);
compiler/forget/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts
+34
-23
@@ -53,11 +53,12 @@ export function compileProgram(
53
return;
54
}
55
56
- CompilerError.invariant(
57
- fn.node.id != null,
58
- "FunctionDeclaration must have a name",
59
- fn.node.loc ?? GeneratedSource
60
- );
56
+ CompilerError.invariant(fn.node.id != null, {
57
+ reason: "FunctionDeclaration must have a name",
58
+ description: null,
59
+ loc: fn.node.loc ?? GeneratedSource,
60
+ suggestions: null,
61
+ });
62
const originalIdent = fn.node.id;
63
64
if (pass.opts.gating != null) {
@@ -65,11 +66,12 @@ export function compileProgram(
66
fn.node.id = addSuffix(fn.node.id, "_uncompiled");
67
68
// Rename and append compiled function
68
- CompilerError.invariant(
69
- compiled.id != null,
70
- "FunctionDeclaration must produce a name",
71
- fn.node.loc ?? GeneratedSource
72
- );
69
+ CompilerError.invariant(compiled.id != null, {
70
+ reason: "FunctionDeclaration must produce a name",
71
+ description: null,
72
+ loc: fn.node.loc ?? GeneratedSource,
73
+ suggestions: null,
74
+ });
75
compiled.id = addSuffix(compiled.id, "_forget");
76
const compiledFn = fn.insertAfter(compiled)[0];
77
compiledFn.skip();
@@ -212,6 +214,7 @@ export function compileProgram(
214
description: violation.value.trim(),
215
severity: ErrorSeverity.InvalidReact,
216
loc: violation.loc ?? null,
217
+ suggestions: null, // TODO(@poteto) add autofix for eslint
218
})
219
);
220
}
@@ -357,6 +360,7 @@ function buildFunctionDeclaration(
360
severity: ErrorSeverity.Todo,
361
description: `Handle ${fn.parentPath.type}`,
362
loc: fn.node.loc ?? null,
363
+ suggestions: null,
364
});
365
}
366
const variableDeclarator = fn.parentPath;
@@ -367,6 +371,7 @@ function buildFunctionDeclaration(
371
severity: ErrorSeverity.Todo,
372
description: `Handle ${variableDeclarator.parentPath.type}`,
373
loc: fn.node.loc ?? null,
374
+ suggestions: null,
375
});
376
}
377
const variableDeclaration = variableDeclarator.parentPath;
@@ -378,6 +383,7 @@ function buildFunctionDeclaration(
383
severity: ErrorSeverity.Todo,
384
description: `Handle ${id.type}`,
385
loc: fn.node.loc ?? null,
386
+ suggestions: null,
387
});
388
}
389
@@ -407,11 +413,12 @@ function buildBlockStatement(
413
return wrappedBody.node;
414
}
415
410
- CompilerError.invariant(
411
- body.isBlockStatement(),
412
- "Body must be a BlockStatement",
413
- body.node.loc ?? GeneratedSource
414
- );
416
+ CompilerError.invariant(body.isBlockStatement(), {
417
+ reason: "Body must be a BlockStatement",
418
+ description: null,
419
+ loc: body.node.loc ?? GeneratedSource,
420
+ suggestions: null,
421
+ });
422
423
return body.node;
424
}
@@ -426,16 +433,20 @@ function addImportsToProgram(
433
// Codegen currently does not rename import specifiers, so we do additional
434
// validation here
435
if (identifiers.has(importSpecifierName)) {
429
- CompilerError.invalidConfig(
430
- `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
431
- GeneratedSource
432
- );
436
+ CompilerError.invalidConfig({
437
+ reason: `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
438
+ description: null,
439
+ loc: GeneratedSource,
440
+ suggestions: null,
441
+ });
442
}
443
if (path.scope.hasBinding(importSpecifierName)) {
435
- CompilerError.invalidConfig(
436
- `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
437
- GeneratedSource
438
- );
444
+ CompilerError.invalidConfig({
445
+ reason: `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
446
+ description: null,
447
+ loc: GeneratedSource,
448
+ suggestions: null,
449
+ });
450
}
451
identifiers.add(importSpecifierName);
452
compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertConsistentIdentifiers.ts
+20
-18
@@ -35,18 +35,20 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
35
}
36
}
37
for (const instr of block.instructions) {
38
- CompilerError.invariant(
39
- instr.lvalue.identifier.name === null,
40
- `Expected all lvalues to be temporaries`,
41
- instr.lvalue.loc,
42
- `Found named lvalue '${instr.lvalue.identifier.name}'`
43
- );
44
- CompilerError.invariant(
45
- !assignments.has(instr.lvalue.identifier.id),
46
- `Expected lvalues to be assigned exactly once`,
47
- instr.lvalue.loc,
48
- `Found duplicate assignment of '${printPlace(instr.lvalue)}'`
49
- );
38
+ CompilerError.invariant(instr.lvalue.identifier.name === null, {
39
+ reason: `Expected all lvalues to be temporaries`,
40
+ description: `Found named lvalue '${instr.lvalue.identifier.name}'`,
41
+ loc: instr.lvalue.loc,
42
+ suggestions: null,
43
+ });
44
+ CompilerError.invariant(!assignments.has(instr.lvalue.identifier.id), {
45
+ reason: `Expected lvalues to be assigned exactly once`,
46
+ description: `Found duplicate assignment of '${printPlace(
47
+ instr.lvalue
48
+ )}'`,
49
+ loc: instr.lvalue.loc,
50
+ suggestions: null,
51
+ });
52
assignments.add(instr.lvalue.identifier.id);
53
for (const operand of eachInstructionLValue(instr)) {
54
validate(identifiers, operand.identifier, operand.loc);
@@ -72,11 +74,11 @@ function validate(
74
if (previous === undefined) {
75
identifiers.set(identifier.id, identifier);
76
} else {
75
- CompilerError.invariant(
76
- identifier === previous,
77
- `Duplicate identifier object`,
78
- loc ?? GeneratedSource,
79
- `Found duplicate identifier object for id ${identifier.id}`
80
- );
77
+ CompilerError.invariant(identifier === previous, {
78
+ reason: `Duplicate identifier object`,
79
+ description: `Found duplicate identifier object for id ${identifier.id}`,
80
+ loc: loc ?? GeneratedSource,
81
+ suggestions: null,
82
+ });
83
}
84
}
compiler/forget/packages/babel-plugin-react-forget/src/HIR/AssertTerminalSuccessorsExist.ts
+7
-7
@@ -13,14 +13,14 @@ import { mapTerminalSuccessors } from "./visitors";
13
export function assertTerminalSuccessorsExist(fn: HIRFunction): void {
14
for (const [, block] of fn.body.blocks) {
15
mapTerminalSuccessors(block.terminal, (successor) => {
16
- CompilerError.invariant(
17
- fn.body.blocks.has(successor),
18
- `Terminal successor references unknown block`,
19
- (block.terminal as any).loc ?? GeneratedSource,
20
- `Block bb${successor} does not exist for terminal '${printTerminal(
16
+ CompilerError.invariant(fn.body.blocks.has(successor), {
17
+ reason: `Terminal successor references unknown block`,
18
+ description: `Block bb${successor} does not exist for terminal '${printTerminal(
19
block.terminal
22
- )}'`
23
- );
20
+ )}'`,
21
+ loc: (block.terminal as any).loc ?? GeneratedSource,
22
+ suggestions: null,
23
+ });
24
return successor;
25
});
26
}
compiler/forget/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+110
-41
@@ -88,6 +88,7 @@ export function lower(
88
reason: `(BuildHIR::lower) Could not find binding for param '${param.node.name}'`,
89
severity: ErrorSeverity.Invariant,
90
loc: param.node.loc ?? null,
91
+ suggestions: null,
92
});
93
return;
94
}
@@ -122,6 +123,7 @@ export function lower(
123
reason: `(BuildHIR::lower) Handle ${param.node.type} params`,
124
severity: ErrorSeverity.Todo,
125
loc: param.node.loc ?? null,
126
+ suggestions: null,
127
});
128
}
129
});
@@ -143,6 +145,7 @@ export function lower(
145
reason: `(BuildHIR::lower) Unexpected function body kind: ${body.type}}`,
146
severity: ErrorSeverity.InvalidInput,
147
loc: body.node.loc ?? null,
148
+ suggestions: null,
149
});
150
}
151
@@ -320,6 +323,7 @@ function lowerStatement(
323
"(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement",
324
severity: ErrorSeverity.Todo,
325
loc: stmt.node.loc ?? null,
326
+ suggestions: null,
327
});
328
return {
329
kind: "unsupported",
@@ -391,6 +395,7 @@ function lowerStatement(
395
reason: `(BuildHIR::lowerStatement) Handle empty test in ForStatement`,
396
severity: ErrorSeverity.Todo,
397
loc: stmt.node.loc ?? null,
398
+ suggestions: null,
399
});
400
} else {
401
builder.terminateWithContinuation(
@@ -540,6 +545,7 @@ function lowerStatement(
545
"(BuildHIR::lowerStatement) Expected at most one `default` branch in SwitchStatement, this code should have failed to parse",
546
severity: ErrorSeverity.InvalidInput,
547
loc: case_.node.loc ?? null,
548
+ suggestions: null,
549
});
550
break;
551
}
@@ -614,6 +620,7 @@ function lowerStatement(
620
reason: `(BuildHIR::lowerStatement) Handle ${nodeKind} kinds in VariableDeclaration`,
621
severity: ErrorSeverity.Todo,
622
loc: stmt.node.loc ?? null,
623
+ suggestions: null,
624
});
625
return;
626
}
@@ -641,6 +648,7 @@ function lowerStatement(
648
reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
649
severity: ErrorSeverity.Invariant,
650
loc: id.node.loc ?? null,
651
+ suggestions: null,
652
});
653
} else {
654
const place: Place = {
@@ -655,6 +663,7 @@ function lowerStatement(
663
reason: `(BuildHIR::lowerAssignment) Invalid declaration kind (const) for variable later reassigned.`,
664
severity: ErrorSeverity.InvalidInput,
665
loc: id.node.loc ?? null,
666
+ suggestions: null,
667
});
668
}
669
lowerValueToTemporary(builder, {
@@ -681,6 +690,7 @@ function lowerStatement(
690
reason: `(BuildHIR::lowerStatement) Expected variable declaration to be an identifier if no initializer was provided.`,
691
severity: ErrorSeverity.InvalidInput,
692
loc: stmt.node.loc ?? null,
693
+ suggestions: null,
694
});
695
}
696
}
@@ -768,11 +778,12 @@ function lowerStatement(
778
case "FunctionDeclaration": {
779
const stmt = stmtPath as NodePath<t.FunctionDeclaration>;
780
stmt.skip();
771
- CompilerError.invariant(
772
- stmt.get("id").type === "Identifier",
773
- "function declarations must have a name",
774
- stmt.node.loc ?? null
775
- );
781
+ CompilerError.invariant(stmt.get("id").type === "Identifier", {
782
+ reason: "function declarations must have a name",
783
+ description: null,
784
+ loc: stmt.node.loc ?? null,
785
+ suggestions: null,
786
+ });
787
const id = stmt.get("id") as NodePath<t.Identifier>;
788
789
// Desugar FunctionDeclaration to FunctionExpression.
@@ -795,11 +806,13 @@ function lowerStatement(
806
),
807
])
808
);
798
- CompilerError.invariant(
799
- desugared.length === 1,
800
- "only one declaration is created from desugaring function declaration",
801
- stmt.node.loc ?? null
802
- );
809
+ CompilerError.invariant(desugared.length === 1, {
810
+ reason:
811
+ "only one declaration is created from desugaring function declaration",
812
+ description: null,
813
+ loc: stmt.node.loc ?? null,
814
+ suggestions: null,
815
+ });
816
lowerStatement(builder, desugared.at(0)!);
817
return;
818
}
@@ -844,11 +857,12 @@ function lowerStatement(
857
let test: Place;
858
if (left.isVariableDeclaration()) {
859
const declarations = left.get("declarations");
847
- CompilerError.invariant(
848
- declarations.length === 1,
849
- `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,
850
- left.node.loc ?? null
851
- );
860
+ CompilerError.invariant(declarations.length === 1, {
861
+ reason: `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,
862
+ description: null,
863
+ loc: left.node.loc ?? null,
864
+ suggestions: null,
865
+ });
866
const id = declarations[0].get("id");
867
const nextIterableOf = lowerValueToTemporary(builder, {
868
kind: "NextIterableOf",
@@ -868,6 +882,7 @@ function lowerStatement(
882
reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForOfStatement`,
883
severity: ErrorSeverity.Todo,
884
loc: left.node.loc ?? null,
885
+ suggestions: null,
886
});
887
return;
888
}
@@ -935,6 +950,7 @@ function lowerStatement(
950
reason: `(BuildHIR::lowerStatement) Handle ${stmtPath.type} statements`,
951
severity: ErrorSeverity.Todo,
952
loc: stmtPath.node.loc ?? null,
953
+ suggestions: null,
954
});
955
lowerValueToTemporary(builder, {
956
kind: "UnsupportedNode",
@@ -1007,6 +1023,7 @@ function lowerExpression(
1023
reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
1024
severity: ErrorSeverity.Todo,
1025
loc: propertyPath.node.loc ?? null,
1026
+ suggestions: null,
1027
});
1028
continue;
1029
}
@@ -1016,6 +1033,7 @@ function lowerExpression(
1033
reason: `(BuildHIR::lowerExpression) Handle ${valuePath.type} values in ObjectExpression`,
1034
severity: ErrorSeverity.Todo,
1035
loc: valuePath.node.loc ?? null,
1036
+ suggestions: null,
1037
});
1038
continue;
1039
}
@@ -1039,6 +1057,7 @@ function lowerExpression(
1057
reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.type} properties in ObjectExpression`,
1058
severity: ErrorSeverity.Todo,
1059
loc: propertyPath.node.loc ?? null,
1060
+ suggestions: null,
1061
});
1062
continue;
1063
}
@@ -1058,6 +1077,7 @@ function lowerExpression(
1077
reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`,
1078
severity: ErrorSeverity.Todo,
1079
loc: null,
1080
+ suggestions: null,
1081
});
1082
continue;
1083
} else if (element.isExpression()) {
@@ -1073,6 +1093,7 @@ function lowerExpression(
1093
reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`,
1094
severity: ErrorSeverity.Todo,
1095
loc: element.node.loc ?? null,
1096
+ suggestions: null,
1097
});
1098
continue;
1099
}
@@ -1091,6 +1112,7 @@ function lowerExpression(
1112
reason: `(BuildHIR::lowerExpression) Expected Expression, got ${calleePath.type} in NewExpression (v8 intrinsics not supported): ${calleePath.type}`,
1113
severity: ErrorSeverity.Todo,
1114
loc: calleePath.node.loc ?? null,
1115
+ suggestions: null,
1116
});
1117
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1118
}
@@ -1116,6 +1138,7 @@ function lowerExpression(
1138
reason: `(BuildHIR::lowerExpression) Expected Expression, got ${calleePath.type} in CallExpression (v8 intrinsics not supported)`,
1139
severity: ErrorSeverity.Todo,
1140
loc: calleePath.node.loc ?? null,
1141
+ suggestions: null,
1142
});
1143
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1144
}
@@ -1149,6 +1172,7 @@ function lowerExpression(
1172
reason: `(BuildHIR::lowerExpression) Expected Expression, got ${leftPath.type} lval in BinaryExpression`,
1173
severity: ErrorSeverity.Todo,
1174
loc: leftPath.node.loc ?? null,
1175
+ suggestions: null,
1176
});
1177
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1178
}
@@ -1180,6 +1204,7 @@ function lowerExpression(
1204
reason: `(BuildHIR::lowerExpression) Expected SequenceExpression to have at least one expression`,
1205
severity: ErrorSeverity.InvalidInput,
1206
loc: expr.node.loc ?? null,
1207
+ suggestions: null,
1208
});
1209
} else {
1210
lowerValueToTemporary(builder, {
@@ -1391,6 +1416,7 @@ function lowerExpression(
1416
reason: `(BuildHIR::lowerExpression) Handle ${operator} operators in AssignmentExpression`,
1417
severity: ErrorSeverity.Todo,
1418
loc: expr.node.loc ?? null,
1419
+ suggestions: null,
1420
});
1421
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1422
}
@@ -1463,6 +1489,7 @@ function lowerExpression(
1489
reason: `(BuildHIR::lowerExpression) Expected Identifier or MemberExpression, got ${expr.type} lval in AssignmentExpression`,
1490
severity: ErrorSeverity.Todo,
1491
loc: expr.node.loc ?? null,
1492
+ suggestions: null,
1493
});
1494
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1495
}
@@ -1504,6 +1531,7 @@ function lowerExpression(
1531
reason: `(BuildHIR::lowerExpression) Handle ${attribute.type} attributes in JSXElement`,
1532
severity: ErrorSeverity.Todo,
1533
loc: attribute.node.loc ?? null,
1534
+ suggestions: null,
1535
});
1536
continue;
1537
}
@@ -1516,14 +1544,16 @@ function lowerExpression(
1544
reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name '${name}'`,
1545
severity: ErrorSeverity.Todo,
1546
loc: namePath.node.loc ?? null,
1547
+ suggestions: null,
1548
});
1549
}
1550
} else {
1522
- CompilerError.invariant(
1523
- namePath.isJSXNamespacedName(),
1524
- "Refinement",
1525
- namePath.node.loc ?? null
1526
- );
1551
+ CompilerError.invariant(namePath.isJSXNamespacedName(), {
1552
+ reason: "Refinement",
1553
+ description: null,
1554
+ loc: namePath.node.loc ?? null,
1555
+ suggestions: null,
1556
+ });
1557
const namespace = namePath.node.namespace.name;
1558
const name = namePath.node.name.name;
1559
propName = `${namespace}:${name}`;
@@ -1538,6 +1568,7 @@ function lowerExpression(
1568
reason: `(BuildHIR::lowerExpression) Handle ${valueExpr.type} attribute values in JSXElement`,
1569
severity: ErrorSeverity.Todo,
1570
loc: valueExpr.node?.loc ?? null,
1571
+ suggestions: null,
1572
});
1573
continue;
1574
}
@@ -1547,6 +1578,7 @@ function lowerExpression(
1578
reason: `(BuildHIR::lowerExpression) Handle ${expression.type} expressions in JSXExpressionContainer within JSXElement`,
1579
severity: ErrorSeverity.Todo,
1580
loc: valueExpr.node.loc ?? null,
1581
+ suggestions: null,
1582
});
1583
continue;
1584
}
@@ -1589,14 +1621,17 @@ function lowerExpression(
1621
"(BuildHIR::lowerExpression) Handle tagged template with interpolations",
1622
severity: ErrorSeverity.Todo,
1623
loc: exprPath.node.loc ?? null,
1624
+ suggestions: null,
1625
});
1626
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1627
}
1595
- CompilerError.invariant(
1596
- expr.get("quasi").get("quasis").length == 1,
1597
- "there should be only one quasi as we don't support interpolations yet",
1598
- expr.node.loc ?? null
1599
- );
1628
+ CompilerError.invariant(expr.get("quasi").get("quasis").length == 1, {
1629
+ reason:
1630
+ "there should be only one quasi as we don't support interpolations yet",
1631
+ description: null,
1632
+ loc: expr.node.loc ?? null,
1633
+ suggestions: null,
1634
+ });
1635
const value = expr.get("quasi").get("quasis").at(0)!.node.value;
1636
if (value.raw !== value.cooked) {
1637
builder.errors.push({
@@ -1604,6 +1639,7 @@ function lowerExpression(
1639
"(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value",
1640
severity: ErrorSeverity.Todo,
1641
loc: exprPath.node.loc ?? null,
1642
+ suggestions: null,
1643
});
1644
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1645
}
@@ -1625,6 +1661,7 @@ function lowerExpression(
1661
reason: `(BuildHIR::lowerExpression) Unexpected quasi and subexpression lengths in TemplateLiteral.`,
1662
severity: ErrorSeverity.InvalidInput,
1663
loc: exprPath.node.loc ?? null,
1664
+ suggestions: null,
1665
});
1666
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1667
}
@@ -1634,6 +1671,7 @@ function lowerExpression(
1671
reason: `(BuildHIR::lowerAssignment) Handle TSType in TemplateLiteral.`,
1672
severity: ErrorSeverity.Todo,
1673
loc: exprPath.node.loc ?? null,
1674
+ suggestions: null,
1675
});
1676
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1677
}
@@ -1675,6 +1713,7 @@ function lowerExpression(
1713
reason: `(BuildHIR::lowerExpression) delete on a non-member expression has no semantic meaning`,
1714
severity: ErrorSeverity.InvalidInput,
1715
loc: expr.node.loc ?? null,
1716
+ suggestions: null,
1717
});
1718
return { kind: "UnsupportedNode", node: expr.node, loc: exprLoc };
1719
}
@@ -1712,6 +1751,7 @@ function lowerExpression(
1751
reason: `(BuildHIR::lowerExpression) Handle UpdateExpression with ${argument.type} argument`,
1752
severity: ErrorSeverity.Todo,
1753
loc: exprPath.node.loc ?? null,
1754
+ suggestions: null,
1755
});
1756
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1757
}
@@ -1720,6 +1760,7 @@ function lowerExpression(
1760
reason: `(BuildHIR::lowerExpression) Handle prefix UpdateExpression`,
1761
severity: ErrorSeverity.Todo,
1762
loc: exprPath.node.loc ?? null,
1763
+ suggestions: null,
1764
});
1765
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1766
}
@@ -1774,6 +1815,7 @@ function lowerExpression(
1815
reason: `(BuildHIR::lowerExpression) Handle ${exprPath.type} expressions`,
1816
severity: ErrorSeverity.Todo,
1817
loc: exprPath.node.loc ?? null,
1818
+ suggestions: null,
1819
});
1820
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1821
}
@@ -1843,7 +1885,12 @@ function lowerOptionalMemberExpression(
1885
loc,
1886
};
1887
});
1846
- CompilerError.invariant(object !== null, "Satisfy type checker", null);
1888
+ CompilerError.invariant(object !== null, {
1889
+ reason: "Satisfy type checker",
1890
+ description: null,
1891
+ loc: null,
1892
+ suggestions: null,
1893
+ });
1894
1895
// block to evaluate if the callee is non-null/undefined. arguments are lowered in this block to preserve
1896
// the semantic of conditional evaluation depending on the callee
@@ -2047,6 +2094,7 @@ function lowerReorderableExpression(
2094
reason: `(BuildHIR::node.lowerReorderableExpression) Expression type '${expr.type}' cannot be safely reordered`,
2095
severity: ErrorSeverity.Todo,
2096
loc: expr.node.loc ?? null,
2097
+ suggestions: null,
2098
});
2099
}
2100
return lowerExpressionToTemporary(builder, expr);
@@ -2138,6 +2186,7 @@ function lowerArguments(
2186
reason: `(BuildHIR::lowerExpression) Handle ${argPath.type} arguments in CallExpression`,
2187
severity: ErrorSeverity.Todo,
2188
loc: argPath.node.loc ?? null,
2189
+ suggestions: null,
2190
});
2191
}
2192
}
@@ -2167,6 +2216,7 @@ function lowerMemberExpression(
2216
reason: `(BuildHIR::lowerMemberExpression) Handle ${propertyNode.type} property`,
2217
severity: ErrorSeverity.Todo,
2218
loc: propertyNode.node.loc ?? null,
2219
+ suggestions: null,
2220
});
2221
return {
2222
object,
@@ -2187,6 +2237,7 @@ function lowerMemberExpression(
2237
reason: `(BuildHIR::lowerMemberExpression) Expected Expression, got ${propertyNode.type} property`,
2238
severity: ErrorSeverity.Todo,
2239
loc: propertyNode.node.loc ?? null,
2240
+ suggestions: null,
2241
});
2242
return {
2243
object,
@@ -2243,6 +2294,7 @@ function lowerJsxElementName(
2294
reason: `(BuildHIR::lowerJsxElementName) Expected JSXNamespacedName to have no colons in the namespace or name, got '${namespace}' : '${name}'`,
2295
severity: ErrorSeverity.InvalidInput,
2296
loc: exprPath.node.loc ?? null,
2297
+ suggestions: null,
2298
});
2299
}
2300
const place = lowerValueToTemporary(builder, {
@@ -2256,6 +2308,7 @@ function lowerJsxElementName(
2308
reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`,
2309
severity: ErrorSeverity.Todo,
2310
loc: exprPath.node.loc ?? null,
2311
+ suggestions: null,
2312
});
2313
return lowerValueToTemporary(builder, {
2314
kind: "UnsupportedNode",
@@ -2275,11 +2328,12 @@ function lowerJsxMemberExpression(
2328
if (object.isJSXMemberExpression()) {
2329
objectPlace = lowerJsxMemberExpression(builder, object);
2330
} else {
2278
- CompilerError.invariant(
2279
- object.isJSXIdentifier(),
2280
- `TypeScript refinement fail: expected 'JsxIdentifier', got '${object.node.type}'`,
2281
- object.node.loc ?? null
2282
- );
2331
+ CompilerError.invariant(object.isJSXIdentifier(), {
2332
+ reason: `TypeScript refinement fail: expected 'JsxIdentifier', got '${object.node.type}'`,
2333
+ description: null,
2334
+ loc: object.node.loc ?? null,
2335
+ suggestions: null,
2336
+ });
2337
objectPlace = lowerIdentifier(builder, object);
2338
}
2339
const property = exprPath.get("property").node.name;
@@ -2310,11 +2364,12 @@ function lowerJsxElement(
2364
if (expression.isJSXEmptyExpression()) {
2365
return null;
2366
} else {
2313
- CompilerError.invariant(
2314
- expression.isExpression(),
2315
- `(BuildHIR::lowerJsxElement) Expected Expression but found ${expression.type}!`,
2316
- expression.node.loc ?? null
2317
- );
2367
+ CompilerError.invariant(expression.isExpression(), {
2368
+ reason: `(BuildHIR::lowerJsxElement) Expected Expression but found ${expression.type}!`,
2369
+ description: null,
2370
+ loc: expression.node.loc ?? null,
2371
+ suggestions: null,
2372
+ });
2373
return lowerExpressionToTemporary(builder, expression);
2374
}
2375
} else if (exprPath.isJSXText()) {
@@ -2329,6 +2384,7 @@ function lowerJsxElement(
2384
reason: `(BuildHIR::lowerJsxElement) Unhandled JsxElement, got: ${exprPath.type}`,
2385
severity: ErrorSeverity.Todo,
2386
loc: exprPath.node.loc ?? null,
2387
+ suggestions: null,
2388
});
2389
const place = lowerValueToTemporary(builder, {
2390
kind: "UnsupportedNode",
@@ -2481,6 +2537,7 @@ function lowerIdentifierForAssignment(
2537
reason: `(BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.`,
2538
severity: ErrorSeverity.InvalidReact,
2539
loc: path.node.loc ?? null,
2540
+ suggestions: null,
2541
});
2542
} else {
2543
// Else its an internal error bc we couldn't find the binding
@@ -2488,6 +2545,7 @@ function lowerIdentifierForAssignment(
2545
reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
2546
severity: ErrorSeverity.Invariant,
2547
loc: path.node.loc ?? null,
2548
+ suggestions: null,
2549
});
2550
}
2551
return null;
@@ -2530,6 +2588,7 @@ function lowerAssignment(
2588
reason: `(BuildHIR::lowerAssignment) Invalid declaration kind (const) for variable later reassigned.`,
2589
severity: ErrorSeverity.InvalidInput,
2590
loc: lvalue.node.loc ?? null,
2591
+ suggestions: null,
2592
});
2593
}
2594
lowerValueToTemporary(builder, {
@@ -2560,11 +2619,12 @@ function lowerAssignment(
2619
}
2620
case "MemberExpression": {
2621
// This can only occur because of a coding error, parsers enforce this condition
2563
- CompilerError.invariant(
2564
- kind === InstructionKind.Reassign,
2565
- "MemberExpression may only appear in an assignment expression",
2566
- lvaluePath.node.loc ?? null
2567
- );
2622
+ CompilerError.invariant(kind === InstructionKind.Reassign, {
2623
+ reason: "MemberExpression may only appear in an assignment expression",
2624
+ description: null,
2625
+ loc: lvaluePath.node.loc ?? null,
2626
+ suggestions: null,
2627
+ });
2628
const lvalue = lvaluePath as NodePath<t.MemberExpression>;
2629
const property = lvalue.get("property");
2630
const object = lowerExpressionToTemporary(builder, lvalue.get("object"));
@@ -2574,6 +2634,7 @@ function lowerAssignment(
2634
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in MemberExpression`,
2635
severity: ErrorSeverity.Todo,
2636
loc: property.node.loc ?? null,
2637
+ suggestions: null,
2638
});
2639
return { kind: "UnsupportedNode", node: lvalueNode, loc };
2640
}
@@ -2592,6 +2653,7 @@ function lowerAssignment(
2653
"(BuildHIR::lowerAssignment) Expected private name to appear as a non-computed property",
2654
severity: ErrorSeverity.Todo,
2655
loc: property.node.loc ?? null,
2656
+ suggestions: null,
2657
});
2658
return { kind: "UnsupportedNode", node: lvalueNode, loc };
2659
}
@@ -2624,6 +2686,7 @@ function lowerAssignment(
2686
reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ArrayPattern`,
2687
severity: ErrorSeverity.Todo,
2688
loc: element.node.loc ?? null,
2689
+ suggestions: null,
2690
});
2691
continue;
2692
}
@@ -2691,6 +2754,7 @@ function lowerAssignment(
2754
reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ArrayPattern`,
2755
severity: ErrorSeverity.Todo,
2756
loc: argument.node.loc ?? null,
2757
+ suggestions: null,
2758
});
2759
continue;
2760
}
@@ -2714,6 +2778,7 @@ function lowerAssignment(
2778
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`,
2779
severity: ErrorSeverity.Todo,
2780
loc: property.node.loc ?? null,
2781
+ suggestions: null,
2782
});
2783
continue;
2784
}
@@ -2722,6 +2787,7 @@ function lowerAssignment(
2787
reason: `(BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern`,
2788
severity: ErrorSeverity.Todo,
2789
loc: property.node.loc ?? null,
2790
+ suggestions: null,
2791
});
2792
continue;
2793
}
@@ -2731,6 +2797,7 @@ function lowerAssignment(
2797
reason: `(BuildHIR::lowerAssignment) Handle ${key.type} keys in ObjectPattern`,
2798
severity: ErrorSeverity.Todo,
2799
loc: key.node.loc ?? null,
2800
+ suggestions: null,
2801
});
2802
continue;
2803
}
@@ -2740,6 +2807,7 @@ function lowerAssignment(
2807
reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`,
2808
severity: ErrorSeverity.Todo,
2809
loc: element.node.loc ?? null,
2810
+ suggestions: null,
2811
});
2812
continue;
2813
}
@@ -2875,6 +2943,7 @@ function lowerAssignment(
2943
reason: `(BuildHIR::lowerAssignment) Handle ${lvaluePath.type} assignments`,
2944
severity: ErrorSeverity.Todo,
2945
loc: lvaluePath.node.loc ?? null,
2946
+ suggestions: null,
2947
});
2948
return { kind: "UnsupportedNode", node: lvalueNode, loc };
2949
}
compiler/forget/packages/babel-plugin-react-forget/src/HIR/Dominator.ts
+18
-7
@@ -88,7 +88,12 @@ export class Dominator<T> {
88
*/
89
get(id: T): T | null {
90
const dominator = this.#nodes.get(id);
91
- CompilerError.invariant(dominator !== undefined, "Unknown node", null);
91
+ CompilerError.invariant(dominator !== undefined, {
92
+ reason: "Unknown node",
93
+ description: null,
94
+ loc: null,
95
+ suggestions: null,
96
+ });
97
return dominator === id ? null : dominator;
98
}
99
@@ -119,7 +124,12 @@ export class PostDominator<T> {
124
*/
125
get(id: T): T | null {
126
const dominator = this.#nodes.get(id);
122
- CompilerError.invariant(dominator !== undefined, "Unknown node", null);
127
+ CompilerError.invariant(dominator !== undefined, {
128
+ reason: "Unknown node",
129
+ description: null,
130
+ loc: null,
131
+ suggestions: null,
132
+ });
133
return dominator === id ? null : dominator;
134
}
135
@@ -159,11 +169,12 @@ function computeImmediateDominators<T>(graph: Graph<T>): Map<T, T> {
169
break;
170
}
171
}
162
- CompilerError.invariant(
163
- newIdom !== null,
164
- `At least one predecessor must have been visited for block ${id}`,
165
- null
166
- );
172
+ CompilerError.invariant(newIdom !== null, {
173
+ reason: `At least one predecessor must have been visited for block ${id}`,
174
+ description: null,
175
+ loc: null,
176
+ suggestions: null,
177
+ });
178
179
for (const pred of node.preds) {
180
// For all other predecessors
compiler/forget/packages/babel-plugin-react-forget/src/HIR/Environment.ts
+18
-15
@@ -195,11 +195,12 @@ export class Environment {
195
if (config?.customHooks) {
196
this.#globals = new Map(DEFAULT_GLOBALS);
197
for (const [hookName, hook] of config.customHooks) {
198
- CompilerError.invariant(
199
- !this.#globals.has(hookName),
200
- `[Globals] Found existing definition in global registry for custom hook ${hookName}`,
201
- null
202
- );
198
+ CompilerError.invariant(!this.#globals.has(hookName), {
199
+ reason: `[Globals] Found existing definition in global registry for custom hook ${hookName}`,
200
+ description: null,
201
+ loc: null,
202
+ suggestions: null,
203
+ });
204
this.#globals.set(
205
hookName,
206
addHook(this.#shapes, [], {
@@ -271,11 +272,12 @@ export class Environment {
272
// If an object or function has a shapeId, it must have been assigned
273
// by Forget (and be present in a builtin or user-defined registry)
274
const shape = this.#shapes.get(shapeId);
274
- CompilerError.invariant(
275
- shape !== undefined,
276
- `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
277
- null
278
- );
275
+ CompilerError.invariant(shape !== undefined, {
276
+ reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
277
+ description: null,
278
+ loc: null,
279
+ suggestions: null,
280
+ });
281
return shape.properties.get(property) ?? null;
282
} else {
283
return null;
@@ -286,11 +288,12 @@ export class Environment {
288
const { shapeId } = type;
289
if (shapeId !== null) {
290
const shape = this.#shapes.get(shapeId);
289
- CompilerError.invariant(
290
- shape !== undefined,
291
- `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
292
- null
293
- );
291
+ CompilerError.invariant(shape !== undefined, {
292
+ reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
293
+ description: null,
294
+ loc: null,
295
+ suggestions: null,
296
+ });
297
return shape.functionType;
298
}
299
return null;
compiler/forget/packages/babel-plugin-react-forget/src/HIR/FindContextIdentifiers.ts
+18
-14
@@ -137,18 +137,20 @@ function handleAssignment(
137
for (const property of path.get("properties")) {
138
if (property.isObjectProperty()) {
139
const valuePath = property.get("value");
140
- CompilerError.invariant(
141
- valuePath.isLVal(),
142
- `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
143
- valuePath.node.loc ?? GeneratedSource
144
- );
140
+ CompilerError.invariant(valuePath.isLVal(), {
141
+ reason: `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
142
+ description: null,
143
+ loc: valuePath.node.loc ?? GeneratedSource,
144
+ suggestions: null,
145
+ });
146
handleAssignment(reassigned, valuePath);
147
} else {
147
- CompilerError.invariant(
148
- property.isRestElement(),
149
- `[FindContextIdentifiers] Invalid assumptions for babel types.`,
150
- property.node.loc ?? GeneratedSource
151
- );
148
+ CompilerError.invariant(property.isRestElement(), {
149
+ reason: `[FindContextIdentifiers] Invalid assumptions for babel types.`,
150
+ description: null,
151
+ loc: property.node.loc ?? GeneratedSource,
152
+ suggestions: null,
153
+ });
154
handleAssignment(reassigned, property);
155
}
156
}
@@ -170,10 +172,12 @@ function handleAssignment(
172
break;
173
}
174
default: {
173
- CompilerError.todo(
174
- `[FindContextIdentifiers] Cannot handle Object destructuring assignment target ${lvalNode.type}`,
175
- lvalNode.loc ?? GeneratedSource
176
- );
175
+ CompilerError.todo({
176
+ reason: `[FindContextIdentifiers] Cannot handle Object destructuring assignment target ${lvalNode.type}`,
177
+ description: null,
178
+ loc: lvalNode.loc ?? GeneratedSource,
179
+ suggestions: null,
180
+ });
181
}
182
}
183
}
compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+30
-21
@@ -883,7 +883,12 @@ export function isMutableEffect(
883
}
884
885
case Effect.Unknown: {
886
- CompilerError.invariant(false, "Unexpected unknown effect", location);
886
+ CompilerError.invariant(false, {
887
+ reason: "Unexpected unknown effect",
888
+ description: null,
889
+ loc: location,
890
+ suggestions: null,
891
+ });
892
}
893
case Effect.Read:
894
case Effect.Freeze: {
@@ -921,11 +926,12 @@ const opaqueBlockId = Symbol();
926
export type BlockId = number & { [opaqueBlockId]: "BlockId" };
927
928
export function makeBlockId(id: number): BlockId {
924
- CompilerError.invariant(
925
- id >= 0 && Number.isInteger(id),
926
- "Expected block id to be a non-negative integer",
927
- null
928
- );
929
+ CompilerError.invariant(id >= 0 && Number.isInteger(id), {
930
+ reason: "Expected block id to be a non-negative integer",
931
+ description: null,
932
+ loc: null,
933
+ suggestions: null,
934
+ });
935
return id as BlockId;
936
}
937
@@ -937,11 +943,12 @@ const opaqueScopeId = Symbol();
943
export type ScopeId = number & { [opaqueScopeId]: "ScopeId" };
944
945
export function makeScopeId(id: number): ScopeId {
940
- CompilerError.invariant(
941
- id >= 0 && Number.isInteger(id),
942
- "Expected block id to be a non-negative integer",
943
- null
944
- );
946
+ CompilerError.invariant(id >= 0 && Number.isInteger(id), {
947
+ reason: "Expected block id to be a non-negative integer",
948
+ description: null,
949
+ loc: null,
950
+ suggestions: null,
951
+ });
952
return id as ScopeId;
953
}
954
@@ -953,11 +960,12 @@ const opaqueIdentifierId = Symbol();
960
export type IdentifierId = number & { [opaqueIdentifierId]: "IdentifierId" };
961
962
export function makeIdentifierId(id: number): IdentifierId {
956
- CompilerError.invariant(
957
- id >= 0 && Number.isInteger(id),
958
- "Expected identifier id to be a non-negative integer",
959
- null
960
- );
963
+ CompilerError.invariant(id >= 0 && Number.isInteger(id), {
964
+ reason: "Expected identifier id to be a non-negative integer",
965
+ description: null,
966
+ loc: null,
967
+ suggestions: null,
968
+ });
969
return id as IdentifierId;
970
}
971
@@ -969,11 +977,12 @@ const opaqueInstructionId = Symbol();
977
export type InstructionId = number & { [opaqueInstructionId]: "IdentifierId" };
978
979
export function makeInstructionId(id: number): InstructionId {
972
- CompilerError.invariant(
973
- id >= 0 && Number.isInteger(id),
974
- "Expected instruction id to be a non-negative integer",
975
- null
976
- );
980
+ CompilerError.invariant(id >= 0 && Number.isInteger(id), {
981
+ reason: "Expected instruction id to be a non-negative integer",
982
+ description: null,
983
+ loc: null,
984
+ suggestions: null,
985
+ });
986
return id as InstructionId;
987
}
988
compiler/forget/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts
+48
-27
@@ -392,8 +392,12 @@ export default class HIRBuilder {
392
last.kind === "label" &&
393
last.label === label &&
394
last.breakBlock === breakBlock,
395
- "Mismatched label",
396
- null
395
+ {
396
+ reason: "Mismatched label",
397
+ description: null,
398
+ loc: null,
399
+ suggestions: null,
400
+ }
401
);
402
return value;
403
}
@@ -411,8 +415,12 @@ export default class HIRBuilder {
415
last.kind === "switch" &&
416
last.label === label &&
417
last.breakBlock === breakBlock,
414
- "Mismatched label",
415
- null
418
+ {
419
+ reason: "Mismatched label",
420
+ description: null,
421
+ loc: null,
422
+ suggestions: null,
423
+ }
424
);
425
return value;
426
}
@@ -447,8 +455,12 @@ export default class HIRBuilder {
455
last.label === label &&
456
last.continueBlock === continueBlock &&
457
last.breakBlock === breakBlock,
450
- "Mismatched loops",
451
- null
458
+ {
459
+ reason: "Mismatched loops",
460
+ description: null,
461
+ loc: null,
462
+ suggestions: null,
463
+ }
464
);
465
return value;
466
}
@@ -464,11 +476,12 @@ export default class HIRBuilder {
476
return scope.breakBlock;
477
}
478
}
467
- CompilerError.invariant(
468
- false,
469
- "Expected a loop or switch to be in scope",
470
- null
471
- );
479
+ CompilerError.invariant(false, {
480
+ reason: "Expected a loop or switch to be in scope",
481
+ description: null,
482
+ loc: null,
483
+ suggestions: null,
484
+ });
485
}
486
487
/**
@@ -484,14 +497,20 @@ export default class HIRBuilder {
497
return scope.continueBlock;
498
}
499
} else if (label !== null && scope.label === label) {
487
- CompilerError.invariant(
488
- false,
489
- "Continue may only refer to a labeled loop",
490
- null
491
- );
500
+ CompilerError.invariant(false, {
501
+ reason: "Continue may only refer to a labeled loop",
502
+ description: null,
503
+ loc: null,
504
+ suggestions: null,
505
+ });
506
}
507
}
494
- CompilerError.invariant(false, "Expected a loop to be in scope", null);
508
+ CompilerError.invariant(false, {
509
+ reason: "Expected a loop to be in scope",
510
+ description: null,
511
+ loc: null,
512
+ suggestions: null,
513
+ });
514
}
515
}
516
@@ -511,11 +530,12 @@ function _shrink(func: HIR): void {
530
return target;
531
}
532
const block = func.blocks.get(blockId);
514
- CompilerError.invariant(
515
- block != null,
516
- `expected block ${blockId} to exist`,
517
- null
518
- );
533
+ CompilerError.invariant(block != null, {
534
+ reason: `expected block ${blockId} to exist`,
535
+ description: null,
536
+ loc: null,
537
+ suggestions: null,
538
+ });
539
target = getTargetIfIndirection(block);
540
if (target !== null) {
541
// the target might also be a simple goto, recurse
@@ -731,11 +751,12 @@ export function markInstructionIds(func: HIR): void {
751
const visited = new Set<Instruction>();
752
for (const [_, block] of func.blocks) {
753
for (const instr of block.instructions) {
734
- CompilerError.invariant(
735
- !visited.has(instr),
736
- `${printInstruction(instr)} already visited!`,
737
- instr.loc
738
- );
754
+ CompilerError.invariant(!visited.has(instr), {
755
+ reason: `${printInstruction(instr)} already visited!`,
756
+ description: null,
757
+ loc: instr.loc,
758
+ suggestions: null,
759
+ });
760
visited.add(instr);
761
instr.id = makeInstructionId(++id);
762
}
compiler/forget/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts
+12
-10
@@ -47,11 +47,12 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
47
const originalPredecessorId = Array.from(block.preds)[0]!;
48
const predecessorId = merged.get(originalPredecessorId);
49
const predecessor = fn.body.blocks.get(predecessorId);
50
- CompilerError.invariant(
51
- predecessor !== undefined,
52
- `Expected predecessor ${predecessorId} to exist`,
53
- null
54
- );
50
+ CompilerError.invariant(predecessor !== undefined, {
51
+ reason: `Expected predecessor ${predecessorId} to exist`,
52
+ description: null,
53
+ loc: null,
54
+ suggestions: null,
55
+ });
56
if (predecessor.terminal.kind !== "goto" || predecessor.kind !== "block") {
57
// The predecessor is not guaranteed to transfer control to this block,
58
// they aren't consecutive.
@@ -60,11 +61,12 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
61
62
// Replace phis in the merged block with canonical assignments to the single operand value
63
for (const phi of block.phis) {
63
- CompilerError.invariant(
64
- phi.operands.size === 1,
65
- `Found a block with a single predecessor but where a phi has multiple (${phi.operands.size}) operands`,
66
- null
67
- );
64
+ CompilerError.invariant(phi.operands.size === 1, {
65
+ reason: `Found a block with a single predecessor but where a phi has multiple (${phi.operands.size}) operands`,
66
+ description: null,
67
+ loc: null,
68
+ suggestions: null,
69
+ });
70
const operand = Array.from(phi.operands.values())[0]!;
71
const instr: Instruction = {
72
id: predecessor.terminal.id,
compiler/forget/packages/babel-plugin-react-forget/src/HIR/ObjectShape.ts
+6
-5
@@ -103,11 +103,12 @@ function addShape(
103
functionType,
104
};
105
106
- CompilerError.invariant(
107
- !registry.has(id),
108
- `[ObjectShape] Could not add shape to registry: name ${id} already exists.`,
109
- null
110
- );
106
+ CompilerError.invariant(!registry.has(id), {
107
+ reason: `[ObjectShape] Could not add shape to registry: name ${id} already exists.`,
108
+ description: null,
109
+ loc: null,
110
+ suggestions: null,
111
+ });
112
registry.set(id, shape);
113
return shape;
114
}
compiler/forget/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts
+6
-2
@@ -469,8 +469,12 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
469
value = "`";
470
CompilerError.invariant(
471
instrValue.subexprs.length === instrValue.quasis.length - 1,
472
- "Bad assumption about quasi length.",
473
- instrValue.loc
472
+ {
473
+ reason: "Bad assumption about quasi length.",
474
+ description: null,
475
+ loc: instrValue.loc,
476
+ suggestions: null,
477
+ }
478
);
479
for (let i = 0; i < instrValue.subexprs.length; i++) {
480
value += instrValue.quasis[i].raw;
compiler/forget/packages/babel-plugin-react-forget/src/HIR/Types.ts
+6
-5
@@ -62,11 +62,12 @@ const opaqueTypeId = Symbol();
62
export type TypeId = number & { [opaqueTypeId]: "IdentifierId" };
63
64
export function makeTypeId(id: number): TypeId {
65
- CompilerError.invariant(
66
- id >= 0 && Number.isInteger(id),
67
- "Expected instruction id to be a non-negative integer",
68
- null
69
- );
65
+ CompilerError.invariant(id >= 0 && Number.isInteger(id), {
66
+ reason: "Expected instruction id to be a non-negative integer",
67
+ description: null,
68
+ loc: null,
69
+ suggestions: null,
70
+ });
71
return id as TypeId;
72
}
73
compiler/forget/packages/babel-plugin-react-forget/src/Inference/AnalyseFunctions.ts
+6
-5
@@ -169,11 +169,12 @@ function infer(
169
// In practice this never really matters because the Component function has no
170
// context refs, so it will never have duplicate deps.
171
for (const place of context) {
172
- CompilerError.invariant(
173
- place.identifier.name !== null,
174
- "context refs should always have a name",
175
- place.loc
176
- );
172
+ CompilerError.invariant(place.identifier.name !== null, {
173
+ reason: "context refs should always have a name",
174
+ description: null,
175
+ loc: place.loc,
176
+ suggestions: null,
177
+ });
178
179
const effect = mutations.get(place.identifier.name);
180
if (effect !== undefined) {
compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferMutableLifetimes.ts
+6
-5
@@ -108,11 +108,12 @@ export function inferMutableLifetimes(
108
start = Math.min(start, operand.mutableRange.start);
109
end = Math.max(end, operand.mutableRange.end);
110
}
111
- CompilerError.invariant(
112
- start !== Number.MAX_SAFE_INTEGER,
113
- "Expected phi to have a start range value",
114
- null
115
- );
111
+ CompilerError.invariant(start !== Number.MAX_SAFE_INTEGER, {
112
+ reason: "Expected phi to have a start range value",
113
+ description: null,
114
+ loc: null,
115
+ suggestions: null,
116
+ });
117
phi.id.mutableRange = {
118
start: makeInstructionId(start),
119
end: makeInstructionId(end),
compiler/forget/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts
+88
-67
@@ -191,11 +191,13 @@ class InferenceState {
191
* (Re)initializes a @param value with its default @param kind.
192
*/
193
initialize(value: InstructionValue, kind: ValueKind): void {
194
- CompilerError.invariant(
195
- value.kind !== "LoadLocal",
196
- "Expected all top-level identifiers to be defined as variables, not values",
197
- value.loc
198
- );
194
+ CompilerError.invariant(value.kind !== "LoadLocal", {
195
+ reason:
196
+ "Expected all top-level identifiers to be defined as variables, not values",
197
+ description: null,
198
+ loc: value.loc,
199
+ suggestions: null,
200
+ });
201
this.#values.set(value, kind);
202
}
203
@@ -204,24 +206,25 @@ class InferenceState {
206
*/
207
kind(place: Place): ValueKind {
208
const values = this.#variables.get(place.identifier.id);
207
- CompilerError.invariant(
208
- values != null,
209
- `Expected value kind to be initialized at '${printSourceLocation(
209
+ CompilerError.invariant(values != null, {
210
+ reason: `Expected value kind to be initialized at '${printSourceLocation(
211
place.loc
212
)}'`,
212
- place.loc
213
- );
213
+ description: null,
214
+ loc: place.loc,
215
+ suggestions: null,
216
+ });
217
let mergedKind: ValueKind | null = null;
218
for (const value of values) {
219
const kind = this.#values.get(value)!;
220
mergedKind = mergedKind !== null ? mergeValues(mergedKind, kind) : kind;
221
}
219
- CompilerError.invariant(
220
- mergedKind !== null,
221
- `InferReferenceEffects::kind: Expected at least one value`,
222
- place.loc,
223
- `No value found at '${printPlace(place)}'`
224
- );
222
+ CompilerError.invariant(mergedKind !== null, {
223
+ reason: `InferReferenceEffects::kind: Expected at least one value`,
224
+ description: `No value found at '${printPlace(place)}'`,
225
+ loc: place.loc,
226
+ suggestions: null,
227
+ });
228
return mergedKind;
229
}
230
@@ -230,11 +233,12 @@ class InferenceState {
233
*/
234
alias(place: Place, value: Place): void {
235
const values = this.#variables.get(value.identifier.id);
233
- CompilerError.invariant(
234
- values != null,
235
- `Expected value for identifier \`${value.identifier.id}\` to be initialized.`,
236
- value.loc
237
- );
236
+ CompilerError.invariant(values != null, {
237
+ reason: `Expected value for identifier \`${value.identifier.id}\` to be initialized.`,
238
+ description: null,
239
+ loc: value.loc,
240
+ suggestions: null,
241
+ });
242
this.#variables.set(place.identifier.id, new Set(values));
243
}
244
@@ -242,11 +246,14 @@ class InferenceState {
246
* Defines (initializing or updating) a variable with a specific kind of value.
247
*/
248
define(place: Place, value: InstructionValue): void {
245
- CompilerError.invariant(
246
- this.#values.has(value),
247
- `Expected value to be initialized at '${printSourceLocation(value.loc)}'`,
248
- value.loc
249
- );
249
+ CompilerError.invariant(this.#values.has(value), {
250
+ reason: `Expected value to be initialized at '${printSourceLocation(
251
+ value.loc
252
+ )}'`,
253
+ description: null,
254
+ loc: value.loc,
255
+ suggestions: null,
256
+ });
257
this.#variables.set(place.identifier.id, new Set([value]));
258
}
259
@@ -269,11 +276,12 @@ class InferenceState {
276
reference(place: Place, effectKind: Effect): void {
277
const values = this.#variables.get(place.identifier.id);
278
if (values === undefined) {
272
- CompilerError.invariant(
273
- effectKind !== Effect.Store,
274
- "[InferReferenceEffects] Unhandled store reference effect",
275
- place.loc
276
- );
279
+ CompilerError.invariant(effectKind !== Effect.Store, {
280
+ reason: "[InferReferenceEffects] Unhandled store reference effect",
281
+ description: null,
282
+ loc: place.loc,
283
+ suggestions: null,
284
+ });
285
place.effect =
286
effectKind === Effect.ConditionallyMutate
287
? Effect.ConditionallyMutate
@@ -315,13 +323,14 @@ class InferenceState {
323
) {
324
effect = Effect.Mutate;
325
} else {
318
- CompilerError.invalidReact(
319
- `InferReferenceEffects: inferred mutation of known immutable value`,
320
- place.loc,
321
- `Found mutation of ${printIdentifier(place.identifier)}${printType(
322
- place.identifier.type
323
- )} (${valueKind})`
324
- );
326
+ CompilerError.invalidReact({
327
+ reason: `InferReferenceEffects: inferred mutation of known immutable value`,
328
+ description: `Found mutation of ${printIdentifier(
329
+ place.identifier
330
+ )}${printType(place.identifier.type)} (${valueKind})`,
331
+ loc: place.loc,
332
+ suggestions: null,
333
+ });
334
}
335
break;
336
}
@@ -330,13 +339,14 @@ class InferenceState {
339
valueKind !== ValueKind.Mutable &&
340
valueKind !== ValueKind.Context
341
) {
333
- CompilerError.invalidReact(
334
- `InferReferenceEffects: inferred mutation of known immutable value`,
335
- place.loc,
336
- `Found mutation of ${printIdentifier(place.identifier)}${printType(
337
- place.identifier.type
338
- )} (${valueKind})`
339
- );
342
+ CompilerError.invalidReact({
343
+ reason: `InferReferenceEffects: inferred mutation of known immutable value`,
344
+ description: `Found mutation of ${printIdentifier(
345
+ place.identifier
346
+ )}${printType(place.identifier.type)} (${valueKind})`,
347
+ loc: place.loc,
348
+ suggestions: null,
349
+ });
350
}
351
352
// TODO(gsn): This should be bailout once we add bailout infra.
@@ -365,11 +375,13 @@ class InferenceState {
375
break;
376
}
377
case Effect.Unknown: {
368
- CompilerError.invariant(
369
- false,
370
- "Unexpected unknown effect, expected to infer a precise effect kind",
371
- place.loc
372
- );
378
+ CompilerError.invariant(false, {
379
+ reason:
380
+ "Unexpected unknown effect, expected to infer a precise effect kind",
381
+ description: null,
382
+ loc: place.loc,
383
+ suggestions: null,
384
+ });
385
}
386
default: {
387
assertExhaustive(
@@ -378,11 +390,12 @@ class InferenceState {
390
);
391
}
392
}
381
- CompilerError.invariant(
382
- effect !== null,
383
- "Expected effect to be set",
384
- place.loc
385
- );
393
+ CompilerError.invariant(effect !== null, {
394
+ reason: "Expected effect to be set",
395
+ description: null,
396
+ loc: place.loc,
397
+ suggestions: null,
398
+ });
399
place.effect = effect;
400
}
401
@@ -753,11 +766,13 @@ function inferBlock(
766
continue;
767
}
768
case "MethodCall": {
756
- CompilerError.invariant(
757
- state.isDefined(instrValue.receiver),
758
- "[InferReferenceEffects] Internal error: receiver of PropertyCall should have been defined by corresponding PropertyLoad",
759
- instrValue.loc
760
- );
769
+ CompilerError.invariant(state.isDefined(instrValue.receiver), {
770
+ reason:
771
+ "[InferReferenceEffects] Internal error: receiver of PropertyCall should have been defined by corresponding PropertyLoad",
772
+ description: null,
773
+ loc: instrValue.loc,
774
+ suggestions: null,
775
+ });
776
state.reference(instrValue.property, Effect.Read);
777
778
const signature = getFunctionCallSignature(
@@ -892,8 +907,13 @@ function inferBlock(
907
const valueKind = state.kind(instrValue.place);
908
CompilerError.invariant(
909
valueKind === ValueKind.Mutable || valueKind === ValueKind.Context,
895
- "[InferReferenceEffects] Context variables are always mutable.",
896
- instrValue.loc
910
+ {
911
+ reason:
912
+ "[InferReferenceEffects] Context variables are always mutable.",
913
+ description: null,
914
+ loc: instrValue.loc,
915
+ suggestions: null,
916
+ }
917
);
918
state.initialize(instrValue, valueKind);
919
state.define(lvalue, instrValue);
@@ -980,11 +1000,12 @@ function inferBlock(
1000
}
1001
1002
for (const operand of eachInstructionOperand(instr)) {
983
- CompilerError.invariant(
984
- effectKind != null,
985
- `effectKind must be set for instruction value \`${instrValue.kind}\``,
986
- instrValue.loc
987
- );
1003
+ CompilerError.invariant(effectKind != null, {
1004
+ reason: `effectKind must be set for instruction value \`${instrValue.kind}\``,
1005
+ description: null,
1006
+ loc: instrValue.loc,
1007
+ suggestions: null,
1008
+ });
1009
state.reference(operand, effectKind);
1010
}
1011
compiler/forget/packages/babel-plugin-react-forget/src/Inference/InlineUseMemo.ts
+13
-8
@@ -100,17 +100,22 @@ export function inlineUseMemo(fn: HIRFunction): void {
100
}
101
102
if (body.loweredFunc.params.length > 0) {
103
- CompilerError.invalidReact(
104
- "useMemo callbacks may not accept any arguments",
105
- body.loc
106
- );
103
+ CompilerError.invalidReact({
104
+ reason: "useMemo callbacks may not accept any arguments",
105
+ description: null,
106
+ loc: body.loc,
107
+ suggestions: null,
108
+ });
109
}
110
111
if (body.loweredFunc.async || body.loweredFunc.generator) {
110
- CompilerError.invalidReact(
111
- "useMemo callbacks may not be async or generator functions",
112
- body.loc
113
- );
112
+ CompilerError.invalidReact({
113
+ reason:
114
+ "useMemo callbacks may not be async or generator functions",
115
+ description: null,
116
+ loc: body.loc,
117
+ suggestions: null,
118
+ });
119
}
120
121
// We know this function is used for useMemo and can prune it later
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveBlocks.ts
+31
-25
@@ -70,11 +70,12 @@ class Context {
70
this.#builders.push(builder);
71
fn();
72
const popped = this.#builders.pop();
73
- CompilerError.invariant(
74
- popped === builder,
75
- "Expected push/pop to be called 1:1",
76
- null
77
- );
73
+ CompilerError.invariant(popped === builder, {
74
+ reason: "Expected push/pop to be called 1:1",
75
+ description: null,
76
+ loc: null,
77
+ suggestions: null,
78
+ });
79
return builder.complete();
80
}
81
}
@@ -94,11 +95,12 @@ class Builder {
95
96
append(item: ReactiveStatement, label: BlockId | null): void {
97
if (label !== null) {
97
- CompilerError.invariant(
98
- item.kind === "terminal",
99
- "Only terminals may have a label",
100
- null
101
- );
98
+ CompilerError.invariant(item.kind === "terminal", {
99
+ reason: "Only terminals may have a label",
100
+ description: null,
101
+ loc: null,
102
+ suggestions: null,
103
+ });
104
item.label = label;
105
}
106
this.#instructions.push(item);
@@ -138,11 +140,12 @@ class Builder {
140
// "Expected all scopes to be closed when exiting a block"
141
// );
142
const first = this.#stack[0]!;
141
- CompilerError.invariant(
142
- first.kind === "block",
143
- "Expected first stack item to be a basic block",
144
- null
145
- );
143
+ CompilerError.invariant(first.kind === "block", {
144
+ reason: "Expected first stack item to be a basic block",
145
+ description: null,
146
+ loc: null,
147
+ suggestions: null,
148
+ });
149
return first.block;
150
}
151
}
@@ -173,11 +176,12 @@ function visitBlock(context: Context, block: ReactiveBlock): void {
176
break;
177
}
178
case "scope": {
176
- CompilerError.invariant(
177
- false,
178
- "Expected the function to not have scopes already assigned",
179
- null
180
- );
179
+ CompilerError.invariant(false, {
180
+ reason: "Expected the function to not have scopes already assigned",
181
+ description: null,
182
+ loc: null,
183
+ suggestions: null,
184
+ });
185
}
186
default: {
187
assertExhaustive(
@@ -192,12 +196,14 @@ function visitBlock(context: Context, block: ReactiveBlock): void {
196
export function getInstructionScope(
197
instr: ReactiveInstruction
198
): ReactiveScope | null {
195
- CompilerError.invariant(
196
- instr.lvalue !== null,
197
- "Expected lvalues to not be null when assigning scopes. " +
199
+ CompilerError.invariant(instr.lvalue !== null, {
200
+ reason:
201
+ "Expected lvalues to not be null when assigning scopes. " +
202
"Pruning lvalues too early can result in missing scope information.",
199
- instr.loc
200
- );
203
+ description: null,
204
+ loc: instr.loc,
205
+ suggestions: null,
206
+ });
207
for (const operand of eachInstructionLValue(instr)) {
208
const operandScope = getPlaceScope(instr.id, operand);
209
if (operandScope !== null) {
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+110
-83
@@ -65,11 +65,12 @@ class Driver {
65
}
66
67
visitBlock(block: BasicBlock, blockValue: ReactiveBlock): void {
68
- CompilerError.invariant(
69
- !this.cx.emitted.has(block.id),
70
- `Cannot emit the same block twice: bb${block.id}`,
71
- null
72
- );
68
+ CompilerError.invariant(!this.cx.emitted.has(block.id), {
69
+ reason: `Cannot emit the same block twice: bb${block.id}`,
70
+ description: null,
71
+ loc: null,
72
+ suggestions: null,
73
+ });
74
this.cx.emitted.add(block.id);
75
for (const instruction of block.instructions) {
76
blockValue.push({
@@ -252,11 +253,12 @@ class Driver {
253
loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
254
} else {
255
const break_ = this.visitBreak(terminal.loop, null);
255
- CompilerError.invariant(
256
- break_ !== null,
257
- "If loop body is already scheduled it must be a break",
258
- null
259
- );
256
+ CompilerError.invariant(break_ !== null, {
257
+ reason: "If loop body is already scheduled it must be a break",
258
+ description: null,
259
+ loc: null,
260
+ suggestions: null,
261
+ });
262
loopBody = [break_];
263
}
264
@@ -309,11 +311,12 @@ class Driver {
311
loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
312
} else {
313
const break_ = this.visitBreak(terminal.loop, null);
312
- CompilerError.invariant(
313
- break_ !== null,
314
- "If loop body is already scheduled it must be a break",
315
- null
316
- );
314
+ CompilerError.invariant(break_ !== null, {
315
+ reason: "If loop body is already scheduled it must be a break",
316
+ description: null,
317
+ loc: null,
318
+ suggestions: null,
319
+ });
320
loopBody = [break_];
321
}
322
@@ -394,11 +397,12 @@ class Driver {
397
loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
398
} else {
399
const break_ = this.visitBreak(terminal.loop, null);
397
- CompilerError.invariant(
398
- break_ !== null,
399
- "If loop body is already scheduled it must be a break",
400
- null
401
- );
400
+ CompilerError.invariant(break_ !== null, {
401
+ reason: "If loop body is already scheduled it must be a break",
402
+ description: null,
403
+ loc: null,
404
+ suggestions: null,
405
+ });
406
loopBody = [break_];
407
}
408
@@ -470,11 +474,12 @@ class Driver {
474
loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
475
} else {
476
const break_ = this.visitBreak(terminal.loop, null);
473
- CompilerError.invariant(
474
- break_ !== null,
475
- "If loop body is already scheduled it must be a break",
476
- null
477
- );
477
+ CompilerError.invariant(break_ !== null, {
478
+ reason: "If loop body is already scheduled it must be a break",
479
+ description: null,
480
+ loc: null,
481
+ suggestions: null,
482
+ });
483
loopBody = [break_];
484
}
485
@@ -547,11 +552,13 @@ class Driver {
552
let block: ReactiveBlock;
553
if (this.cx.isScheduled(terminal.block)) {
554
const break_ = this.visitBreak(terminal.block, null);
550
- CompilerError.invariant(
551
- break_ !== null,
552
- "Expected a break target for a label whose body is already scheduled",
553
- terminal.loc
554
- );
555
+ CompilerError.invariant(break_ !== null, {
556
+ reason:
557
+ "Expected a break target for a label whose body is already scheduled",
558
+ description: null,
559
+ loc: terminal.loc,
560
+ suggestions: null,
561
+ });
562
block = [break_];
563
} else {
564
block = this.traverseBlock(this.cx.ir.blocks.get(terminal.block)!);
@@ -630,11 +637,12 @@ class Driver {
637
break;
638
}
639
case "unsupported": {
633
- CompilerError.invariant(
634
- false,
635
- "Unexpected unsupported terminal",
636
- terminal.loc
637
- );
640
+ CompilerError.invariant(false, {
641
+ reason: "Unexpected unsupported terminal",
642
+ description: null,
643
+ loc: terminal.loc,
644
+ suggestions: null,
645
+ });
646
}
647
default: {
648
assertExhaustive(terminal, "Unexpected terminal");
@@ -665,8 +673,13 @@ class Driver {
673
CompilerError.invariant(
674
instr.lvalue.identifier.id ===
675
defaultBlock.terminal.test.identifier.id,
668
- "Expected branch block to end in an instruction that sets the test value",
669
- instr.lvalue.loc
676
+ {
677
+ reason:
678
+ "Expected branch block to end in an instruction that sets the test value",
679
+ description: null,
680
+ loc: instr.lvalue.loc,
681
+ suggestions: null,
682
+ }
683
);
684
return {
685
block: defaultBlock.id,
@@ -693,11 +706,12 @@ class Driver {
706
} else if (defaultBlock.terminal.kind === "goto") {
707
const instructions = defaultBlock.instructions;
708
if (instructions.length === 0) {
696
- CompilerError.invariant(
697
- false,
698
- "Expected goto value block to have at least one instruction",
699
- null
700
- );
709
+ CompilerError.invariant(false, {
710
+ reason: "Expected goto value block to have at least one instruction",
711
+ description: null,
712
+ loc: null,
713
+ suggestions: null,
714
+ });
715
} else if (defaultBlock.instructions.length === 1) {
716
const instr = defaultBlock.instructions[0]!;
717
let place: Place = instr.lvalue!;
@@ -792,11 +806,12 @@ class Driver {
806
case "optional": {
807
const test = this.visitValueBlock(terminal.test, terminal.loc);
808
const testBlock = this.cx.ir.blocks.get(test.block)!;
795
- CompilerError.invariant(
796
- testBlock.terminal.kind === "branch",
797
- `Unexpected terminal kind '${testBlock.terminal.kind}' for optional call test block`,
798
- testBlock.terminal.loc
799
- );
809
+ CompilerError.invariant(testBlock.terminal.kind === "branch", {
810
+ reason: `Unexpected terminal kind '${testBlock.terminal.kind}' for optional call test block`,
811
+ description: null,
812
+ loc: testBlock.terminal.loc,
813
+ suggestions: null,
814
+ });
815
const consequent = this.visitValueBlock(
816
testBlock.terminal.consequent,
817
terminal.loc
@@ -831,11 +846,12 @@ class Driver {
846
case "logical": {
847
const test = this.visitValueBlock(terminal.test, terminal.loc);
848
const testBlock = this.cx.ir.blocks.get(test.block)!;
834
- CompilerError.invariant(
835
- testBlock.terminal.kind === "branch",
836
- `Unexpected terminal kind '${testBlock.terminal.kind}' for logical test block`,
837
- testBlock.terminal.loc
838
- );
849
+ CompilerError.invariant(testBlock.terminal.kind === "branch", {
850
+ reason: `Unexpected terminal kind '${testBlock.terminal.kind}' for logical test block`,
851
+ description: null,
852
+ loc: testBlock.terminal.loc,
853
+ suggestions: null,
854
+ });
855
856
const leftFinal = this.visitValueBlock(
857
testBlock.terminal.consequent,
@@ -876,11 +892,12 @@ class Driver {
892
case "ternary": {
893
const test = this.visitValueBlock(terminal.test, terminal.loc);
894
const testBlock = this.cx.ir.blocks.get(test.block)!;
879
- CompilerError.invariant(
880
- testBlock.terminal.kind === "branch",
881
- `Unexpected terminal kind '${testBlock.terminal.kind}' for ternary test block`,
882
- testBlock.terminal.loc
883
- );
895
+ CompilerError.invariant(testBlock.terminal.kind === "branch", {
896
+ reason: `Unexpected terminal kind '${testBlock.terminal.kind}' for ternary test block`,
897
+ description: null,
898
+ loc: testBlock.terminal.loc,
899
+ suggestions: null,
900
+ });
901
const consequent = this.visitValueBlock(
902
testBlock.terminal.consequent,
903
terminal.loc
@@ -905,11 +922,12 @@ class Driver {
922
};
923
}
924
default: {
908
- CompilerError.invariant(
909
- false,
910
- `Unexpected value block terminal kind '${terminal.kind}'`,
911
- terminal.loc
912
- );
925
+ CompilerError.invariant(false, {
926
+ reason: `Unexpected value block terminal kind '${terminal.kind}'`,
927
+ description: null,
928
+ loc: terminal.loc,
929
+ suggestions: null,
930
+ });
931
}
932
}
933
}
@@ -924,7 +942,12 @@ class Driver {
942
): ReactiveTerminalStatement<ReactiveBreakTerminal> {
943
const target = this.cx.getBreakTarget(block);
944
if (target === null) {
927
- CompilerError.invariant(false, "Expected a break target", null);
945
+ CompilerError.invariant(false, {
946
+ reason: "Expected a break target",
947
+ description: null,
948
+ loc: null,
949
+ suggestions: null,
950
+ });
951
}
952
switch (target.type) {
953
case "implicit": {
@@ -962,11 +985,12 @@ class Driver {
985
id: InstructionId
986
): ReactiveTerminalStatement<ReactiveContinueTerminal> {
987
const target = this.cx.getContinueTarget(block);
965
- CompilerError.invariant(
966
- target !== null,
967
- `Expected continue target to be scheduled for bb${block}`,
968
- null
969
- );
988
+ CompilerError.invariant(target !== null, {
989
+ reason: `Expected continue target to be scheduled for bb${block}`,
990
+ description: null,
991
+ loc: null,
992
+ suggestions: null,
993
+ });
994
switch (target.type) {
995
case "implicit": {
996
return {
@@ -1046,11 +1070,12 @@ class Context {
1070
*/
1071
schedule(block: BlockId, type: "if" | "switch" | "case"): number {
1072
const id = this.#nextScheduleId++;
1049
- CompilerError.invariant(
1050
- !this.#scheduled.has(block),
1051
- `Break block is already scheduled: bb${block}`,
1052
- null
1053
- );
1073
+ CompilerError.invariant(!this.#scheduled.has(block), {
1074
+ reason: `Break block is already scheduled: bb${block}`,
1075
+ description: null,
1076
+ loc: null,
1077
+ suggestions: null,
1078
+ });
1079
this.#scheduled.add(block);
1080
this.#controlFlowStack.push({ block, id, type });
1081
return id;
@@ -1064,11 +1089,12 @@ class Context {
1089
const id = this.#nextScheduleId++;
1090
const ownsBlock = !this.#scheduled.has(fallthroughBlock);
1091
this.#scheduled.add(fallthroughBlock);
1067
- CompilerError.invariant(
1068
- !this.#scheduled.has(continueBlock),
1069
- `Continue block is already scheduled: bb${continueBlock}`,
1070
- null
1071
- );
1092
+ CompilerError.invariant(!this.#scheduled.has(continueBlock), {
1093
+ reason: `Continue block is already scheduled: bb${continueBlock}`,
1094
+ description: null,
1095
+ loc: null,
1096
+ suggestions: null,
1097
+ });
1098
this.#scheduled.add(continueBlock);
1099
let ownsLoop = false;
1100
if (loopBlock !== null) {
@@ -1093,11 +1119,12 @@ class Context {
1119
*/
1120
unschedule(scheduleId: number): void {
1121
const last = this.#controlFlowStack.pop();
1096
- CompilerError.invariant(
1097
- last !== undefined && last.id === scheduleId,
1098
- "Can only unschedule the last target",
1099
- null
1100
- );
1122
+ CompilerError.invariant(last !== undefined && last.id === scheduleId, {
1123
+ reason: "Can only unschedule the last target",
1124
+ description: null,
1125
+ loc: null,
1126
+ suggestions: null,
1127
+ });
1128
if (last.type !== "loop" || last.ownsBlock !== null) {
1129
this.#scheduled.delete(last.block);
1130
}
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+138
-104
@@ -215,11 +215,12 @@ function codegenReactiveScope(
215
firstOutputIndex = index;
216
}
217
218
- CompilerError.invariant(
219
- identifier.name != null,
220
- `Expected identifier '@${identifier.id}' to be named`,
221
- null
222
- );
218
+ CompilerError.invariant(identifier.name != null, {
219
+ reason: `Expected identifier '@${identifier.id}' to be named`,
220
+ description: null,
221
+ loc: null,
222
+ suggestions: null,
223
+ });
224
225
const name = convertIdentifier(identifier);
226
if (!cx.hasDeclared(identifier)) {
@@ -283,11 +284,12 @@ function codegenReactiveScope(
284
null as t.Expression | null
285
);
286
if (testCondition === null) {
286
- CompilerError.invariant(
287
- firstOutputIndex !== null,
288
- `Expected scope '@${scope.id}' to have at least one declaration`,
289
- null
290
- );
287
+ CompilerError.invariant(firstOutputIndex !== null, {
288
+ reason: `Expected scope '@${scope.id}' to have at least one declaration`,
289
+ description: null,
290
+ loc: null,
291
+ suggestions: null,
292
+ });
293
testCondition = t.binaryExpression(
294
"===",
295
t.memberExpression(
@@ -344,17 +346,19 @@ function codegenTerminal(
346
);
347
}
348
case "for-of": {
347
- CompilerError.invariant(
348
- terminal.init.kind === "SequenceExpression",
349
- `Expected a sequence expression init for ForOf`,
350
- terminal.init.loc,
351
- `Got '${terminal.init.kind}' expression instead`
352
- );
349
+ CompilerError.invariant(terminal.init.kind === "SequenceExpression", {
350
+ reason: `Expected a sequence expression init for ForOf`,
351
+ description: `Got '${terminal.init.kind}' expression instead`,
352
+ loc: terminal.init.loc,
353
+ suggestions: null,
354
+ });
355
if (terminal.init.instructions.length !== 2) {
354
- CompilerError.todo(
355
- "Support non-trivial ForOf inits",
356
- terminal.init.loc
357
- );
356
+ CompilerError.todo({
357
+ reason: "Support non-trivial ForOf inits",
358
+ description: null,
359
+ loc: terminal.init.loc,
360
+ suggestions: null,
361
+ });
362
}
363
const iterableCollection = terminal.init.instructions[0];
364
const iterableItem = terminal.init.instructions[1];
@@ -369,12 +373,12 @@ function codegenTerminal(
373
break;
374
}
375
default:
372
- CompilerError.invariant(
373
- false,
374
- `Expected a StoreLocal or Destructure to be assigned to the collection`,
375
- iterableItem.value.loc,
376
- `Found ${iterableItem.value.kind}`
377
- );
376
+ CompilerError.invariant(false, {
377
+ reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,
378
+ description: `Found ${iterableItem.value.kind}`,
379
+ loc: iterableItem.value.loc,
380
+ suggestions: null,
381
+ });
382
}
383
let varDeclKind: "const" | "let";
384
switch (iterableItem.value.lvalue.kind) {
@@ -385,11 +389,13 @@ function codegenTerminal(
389
varDeclKind = "let" as const;
390
break;
391
case InstructionKind.Reassign:
388
- CompilerError.invariant(
389
- false,
390
- "Destructure should never be Reassign as it would be an Object/ArrayPattern",
391
- iterableItem.loc
392
- );
392
+ CompilerError.invariant(false, {
393
+ reason:
394
+ "Destructure should never be Reassign as it would be an Object/ArrayPattern",
395
+ description: null,
396
+ loc: iterableItem.loc,
397
+ suggestions: null,
398
+ });
399
default:
400
assertExhaustive(
401
iterableItem.value.lvalue.kind,
@@ -509,11 +515,13 @@ function codegenInstructionNullable(
515
hasDeclaration ||= !isDeclared;
516
}
517
if (hasReasign && hasDeclaration) {
512
- CompilerError.invariant(
513
- false,
514
- "Encountered a destructuring operation where some identifiers are already declared (reassignments) but others are not (declarations)",
515
- instr.loc
516
- );
518
+ CompilerError.invariant(false, {
519
+ reason:
520
+ "Encountered a destructuring operation where some identifiers are already declared (reassignments) but others are not (declarations)",
521
+ description: null,
522
+ loc: instr.loc,
523
+ suggestions: null,
524
+ });
525
} else if (hasReasign) {
526
kind = InstructionKind.Reassign;
527
}
@@ -521,31 +529,34 @@ function codegenInstructionNullable(
529
}
530
switch (kind) {
531
case InstructionKind.Const: {
524
- CompilerError.invariant(
525
- instr.lvalue === null,
526
- `Const declaration cannot be referenced as an expression`,
527
- instr.value.loc
528
- );
532
+ CompilerError.invariant(instr.lvalue === null, {
533
+ reason: `Const declaration cannot be referenced as an expression`,
534
+ description: null,
535
+ loc: instr.value.loc,
536
+ suggestions: null,
537
+ });
538
return createVariableDeclaration(instr.loc, "const", [
539
t.variableDeclarator(codegenLValue(lvalue), value),
540
]);
541
}
542
case InstructionKind.Let: {
534
- CompilerError.invariant(
535
- instr.lvalue === null,
536
- `Const declaration cannot be referenced as an expression`,
537
- instr.value.loc
538
- );
543
+ CompilerError.invariant(instr.lvalue === null, {
544
+ reason: `Const declaration cannot be referenced as an expression`,
545
+ description: null,
546
+ loc: instr.value.loc,
547
+ suggestions: null,
548
+ });
549
return createVariableDeclaration(instr.loc, "let", [
550
t.variableDeclarator(codegenLValue(lvalue), value),
551
]);
552
}
553
case InstructionKind.Reassign: {
544
- CompilerError.invariant(
545
- value !== null,
546
- "Expected a value for reassignment",
547
- instr.value.loc
548
- );
554
+ CompilerError.invariant(value !== null, {
555
+ reason: "Expected a value for reassignment",
556
+ description: null,
557
+ loc: instr.value.loc,
558
+ suggestions: null,
559
+ });
560
const expr = t.assignmentExpression("=", codegenLValue(lvalue), value);
561
if (instr.lvalue !== null) {
562
if (instr.value.kind !== "StoreContext") {
@@ -592,11 +603,12 @@ function codegenForInit(
603
}))
604
).body;
605
const declaration = body[0]!;
595
- CompilerError.invariant(
596
- declaration.type === "VariableDeclaration",
597
- "Expected a variable declaration",
598
- declaration.loc ?? null
599
- );
606
+ CompilerError.invariant(declaration.type === "VariableDeclaration", {
607
+ reason: "Expected a variable declaration",
608
+ description: null,
609
+ loc: declaration.loc ?? null,
610
+ suggestions: null,
611
+ });
612
return declaration;
613
} else {
614
return codegenInstructionValue(cx, init);
@@ -744,11 +756,12 @@ function codegenInstructionValue(
756
switch (optionalValue.type) {
757
case "OptionalCallExpression":
758
case "CallExpression": {
747
- CompilerError.invariant(
748
- t.isExpression(optionalValue.callee),
749
- "v8 intrinsics are validated during lowering",
750
- optionalValue.callee.loc ?? null
751
- );
759
+ CompilerError.invariant(t.isExpression(optionalValue.callee), {
760
+ reason: "v8 intrinsics are validated during lowering",
761
+ description: null,
762
+ loc: optionalValue.callee.loc ?? null,
763
+ suggestions: null,
764
+ });
765
value = t.optionalCallExpression(
766
optionalValue.callee,
767
optionalValue.arguments,
@@ -759,11 +772,12 @@ function codegenInstructionValue(
772
case "OptionalMemberExpression":
773
case "MemberExpression": {
774
const property = optionalValue.property;
762
- CompilerError.invariant(
763
- t.isExpression(property),
764
- "Private names are validated during lowering",
765
- property.loc ?? null
766
- );
775
+ CompilerError.invariant(t.isExpression(property), {
776
+ reason: "Private names are validated during lowering",
777
+ description: null,
778
+ loc: property.loc ?? null,
779
+ suggestions: null,
780
+ });
781
value = t.optionalMemberExpression(
782
optionalValue.object,
783
property,
@@ -773,12 +787,13 @@ function codegenInstructionValue(
787
break;
788
}
789
default: {
776
- CompilerError.invariant(
777
- false,
778
- "Expected an optional value to resolve to a call expression or member expression",
779
- instrValue.loc,
780
- `Got a '${optionalValue.type}'`
781
- );
790
+ CompilerError.invariant(false, {
791
+ reason:
792
+ "Expected an optional value to resolve to a call expression or member expression",
793
+ description: `Got a '${optionalValue.type}'`,
794
+ loc: instrValue.loc,
795
+ suggestions: null,
796
+ });
797
}
798
}
799
break;
@@ -788,18 +803,28 @@ function codegenInstructionValue(
803
CompilerError.invariant(
804
t.isMemberExpression(memberExpr) ||
805
t.isOptionalMemberExpression(memberExpr),
791
- "[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. " +
792
- `Got a '${memberExpr.type}'`,
793
- memberExpr.loc ?? null
806
+ {
807
+ reason:
808
+ "[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. " +
809
+ `Got a '${memberExpr.type}'`,
810
+ description: null,
811
+ loc: memberExpr.loc ?? null,
812
+ suggestions: null,
813
+ }
814
);
815
CompilerError.invariant(
816
t.isNodesEquivalent(
817
memberExpr.object,
818
codegenPlace(cx, instrValue.receiver)
819
),
800
- "[Codegen] Internal error: Forget should always generate MethodCall::property " +
801
- "as a MemberExpression of MethodCall::receiver",
802
- memberExpr.loc ?? null
820
+ {
821
+ reason:
822
+ "[Codegen] Internal error: Forget should always generate MethodCall::property " +
823
+ "as a MemberExpression of MethodCall::receiver",
824
+ description: null,
825
+ loc: memberExpr.loc ?? null,
826
+ suggestions: null,
827
+ }
828
);
829
const args = instrValue.args.map((arg) => codegenArgument(cx, arg));
830
value = createCallExpression(instrValue.loc, memberExpr, args);
@@ -851,11 +876,12 @@ function codegenInstructionValue(
876
} else if (tagValue.type === "MemberExpression") {
877
tag = convertMemberExpressionToJsx(tagValue);
878
} else {
854
- CompilerError.invariant(
855
- tagValue.type === "StringLiteral",
856
- `Expected JSX tag to be an identifier or string, got '${tagValue.type}'`,
857
- tagValue.loc ?? null
858
- );
879
+ CompilerError.invariant(tagValue.type === "StringLiteral", {
880
+ reason: `Expected JSX tag to be an identifier or string, got '${tagValue.type}'`,
881
+ description: null,
882
+ loc: tagValue.loc ?? null,
883
+ suggestions: null,
884
+ });
885
if (tagValue.value.indexOf(":") >= 0) {
886
const [namespace, name] = tagValue.value.split(":", 2);
887
tag = createJsxNamespacedName(
@@ -1049,6 +1075,7 @@ function codegenInstructionValue(
1075
}'`,
1076
severity: ErrorSeverity.Todo,
1077
loc: declarator.loc ?? null,
1078
+ suggestions: null,
1079
});
1080
return t.stringLiteral(`TODO handle ${declarator.id}`);
1081
} else {
@@ -1056,6 +1083,7 @@ function codegenInstructionValue(
1083
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
1084
severity: ErrorSeverity.Todo,
1085
loc: stmt.loc ?? null,
1086
+ suggestions: null,
1087
});
1088
return t.stringLiteral(`TODO handle ${stmt.type}`);
1089
}
@@ -1105,11 +1133,12 @@ function codegenInstructionValue(
1133
case "Destructure":
1134
case "StoreLocal":
1135
case "StoreContext": {
1108
- CompilerError.invariant(
1109
- false,
1110
- `Unexpected ${instrValue.kind} in codegenInstructionValue`,
1111
- instrValue.loc
1112
- );
1136
+ CompilerError.invariant(false, {
1137
+ reason: `Unexpected ${instrValue.kind} in codegenInstructionValue`,
1138
+ description: null,
1139
+ loc: instrValue.loc,
1140
+ suggestions: null,
1141
+ });
1142
}
1143
default: {
1144
assertExhaustive(
@@ -1193,20 +1222,23 @@ function codegenJsxElement(
1222
function convertMemberExpressionToJsx(
1223
expr: t.MemberExpression
1224
): t.JSXMemberExpression {
1196
- CompilerError.invariant(
1197
- expr.property.type === "Identifier",
1198
- "Expected JSX member expression property to be a string",
1199
- expr.loc ?? null
1200
- );
1225
+ CompilerError.invariant(expr.property.type === "Identifier", {
1226
+ reason: "Expected JSX member expression property to be a string",
1227
+ description: null,
1228
+ loc: expr.loc ?? null,
1229
+ suggestions: null,
1230
+ });
1231
const property = t.jsxIdentifier(expr.property.name);
1232
if (expr.object.type === "Identifier") {
1233
return t.jsxMemberExpression(t.jsxIdentifier(expr.object.name), property);
1234
} else {
1205
- CompilerError.invariant(
1206
- expr.object.type === "MemberExpression",
1207
- "Expected JSX member expression to be an identifier or nested member expression",
1208
- expr.object.loc ?? null
1209
- );
1235
+ CompilerError.invariant(expr.object.type === "MemberExpression", {
1236
+ reason:
1237
+ "Expected JSX member expression to be an identifier or nested member expression",
1238
+ description: null,
1239
+ loc: expr.object.loc ?? null,
1240
+ suggestions: null,
1241
+ });
1242
const object = convertMemberExpressionToJsx(expr.object);
1243
return t.jsxMemberExpression(object, property);
1244
}
@@ -1288,12 +1320,14 @@ function codegenPlace(cx: Context, place: Place): t.Expression {
1320
if (tmp != null) {
1321
return tmp;
1322
}
1291
- CompilerError.invariant(
1292
- place.identifier.name !== null || tmp !== undefined,
1293
- `[Codegen] No value found for temporary`,
1294
- place.loc,
1295
- `Value for '${printPlace(place)}' was not set in the codegen context`
1296
- );
1323
+ CompilerError.invariant(place.identifier.name !== null || tmp !== undefined, {
1324
+ reason: `[Codegen] No value found for temporary`,
1325
+ description: `Value for '${printPlace(
1326
+ place
1327
+ )}' was not set in the codegen context`,
1328
+ loc: place.loc,
1329
+ suggestions: null,
1330
+ });
1331
const identifier = convertIdentifier(place.identifier);
1332
identifier.loc = place.loc as any;
1333
return identifier;
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/DeriveMinimalDependencies.ts
+27
-17
@@ -126,8 +126,13 @@ export class ReactiveScopeDependencyTree {
126
deps.every(
127
(dep) => dep.accessType === PropertyAccessType.UnconditionalDependency
128
),
129
- "[PropagateScopeDependencies] All dependencies must be reduced to unconditional dependencies.",
130
- null
129
+ {
130
+ reason:
131
+ "[PropagateScopeDependencies] All dependencies must be reduced to unconditional dependencies.",
132
+ description: null,
133
+ loc: null,
134
+ suggestions: null,
135
+ }
136
);
137
138
for (const dep of deps) {
@@ -163,11 +168,12 @@ export class ReactiveScopeDependencyTree {
168
promoteDepsFromExhaustiveConditionals(
169
trees: Array<ReactiveScopeDependencyTree>
170
): void {
166
- CompilerError.invariant(
167
- trees.length > 1,
168
- "Expected trees to be at least 2 elements long.",
169
- null
170
- );
171
+ CompilerError.invariant(trees.length > 1, {
172
+ reason: "Expected trees to be at least 2 elements long.",
173
+ description: null,
174
+ loc: null,
175
+ suggestions: null,
176
+ });
177
178
for (const [id, root] of this.#roots) {
179
const nodesForRootId = mapNonNull(trees, (tree) => tree.#roots.get(id));
@@ -458,19 +464,23 @@ function addSubtreeIntersection(
464
otherProperties: Array<Map<string, DependencyNode>>,
465
currProperties: Map<string, DependencyNode>
466
): void {
461
- CompilerError.invariant(
462
- otherProperties.length > 1,
463
- "[DeriveMinimalDependencies] Expected otherProperties to be at least 2 elements long.",
464
- null
465
- );
467
+ CompilerError.invariant(otherProperties.length > 1, {
468
+ reason:
469
+ "[DeriveMinimalDependencies] Expected otherProperties to be at least 2 elements long.",
470
+ description: null,
471
+ loc: null,
472
+ suggestions: null,
473
+ });
474
475
otherProperties.forEach((properties) =>
476
properties.forEach((node, _) =>
469
- CompilerError.invariant(
470
- !isUnconditional(node.accessType),
471
- "[DeriveMinimalDependencies] Expected otherProperties to only contain unconditional nodes!",
472
- null
473
- )
477
+ CompilerError.invariant(!isUnconditional(node.accessType), {
478
+ reason:
479
+ "[DeriveMinimalDependencies] Expected otherProperties to only contain unconditional nodes!",
480
+ description: null,
481
+ loc: null,
482
+ suggestions: null,
483
+ })
484
)
485
);
486
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/InferReactiveIdentifiers.ts
+6
-5
@@ -105,11 +105,12 @@ class Visitor extends ReactiveFunctionVisitor<State> {
105
break;
106
}
107
case Effect.Unknown: {
108
- CompilerError.invariant(
109
- false,
110
- "Unexpected unknown effect",
111
- operand.loc
112
- );
108
+ CompilerError.invariant(false, {
109
+ reason: "Unexpected unknown effect",
110
+ description: null,
111
+ loc: operand.loc,
112
+ suggestions: null,
113
+ });
114
}
115
default: {
116
assertExhaustive(
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts
+6
-5
@@ -226,11 +226,12 @@ function printTerminal(writer: Writer, terminal: ReactiveTerminal): void {
226
writer.writeLine(`${prefix}: {`);
227
writer.indented(() => {
228
const block = case_.block;
229
- CompilerError.invariant(
230
- block != null,
231
- "Expected case to have a block",
232
- case_.test?.loc ?? null
233
- );
229
+ CompilerError.invariant(block != null, {
230
+ reason: "Expected case to have a block",
231
+ description: null,
232
+ loc: case_.test?.loc ?? null,
233
+ suggestions: null,
234
+ });
235
printReactiveInstructions(writer, block);
236
});
237
writer.writeLine("}");
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PromoteUsedTemporaries.ts
+7
-5
@@ -75,11 +75,13 @@ export function promoteUsedTemporaries(fn: ReactiveFunction): void {
75
}
76
77
function promoteTemporary(identifier: Identifier, state: VisitorState): void {
78
- CompilerError.invariant(
79
- identifier.name === null,
80
- "promoteTemporary: Expected to be called only for temporary variables",
81
- GeneratedSource
82
- );
78
+ CompilerError.invariant(identifier.name === null, {
79
+ reason:
80
+ "promoteTemporary: Expected to be called only for temporary variables",
81
+ description: null,
82
+ loc: GeneratedSource,
83
+ suggestions: null,
84
+ });
85
if (state.tags.has(identifier.id)) {
86
identifier.name = `T${state.nextId++}`;
87
} else {
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+7
-6
@@ -459,12 +459,13 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
459
// OptionalExpression value is a SequenceExpression where the instructions
460
// represent the code prior to the `?` and the final value represents the
461
// conditional code that follows.
462
- CompilerError.invariant(
463
- inner.kind === "SequenceExpression",
464
- "Expected OptionalExpression value to be a SequenceExpression",
465
- value.loc,
466
- `Found a '${value.kind}'`
467
- );
462
+ CompilerError.invariant(inner.kind === "SequenceExpression", {
463
+ reason:
464
+ "Expected OptionalExpression value to be a SequenceExpression",
465
+ description: `Found a '${value.kind}'`,
466
+ loc: value.loc,
467
+ suggestions: null,
468
+ });
469
// Instructions are the unconditionally executed portion before the `?`
470
for (const instr of inner.instructions) {
471
this.visitInstruction(instr, context);
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneNonEscapingScopes.ts
+24
-16
@@ -243,11 +243,12 @@ class State {
243
this.scopes.set(scope.id, node);
244
}
245
const identifierNode = this.identifiers.get(identifier);
246
- CompilerError.invariant(
247
- identifierNode !== undefined,
248
- "Expected identifier to be initialized",
249
- place.loc
250
- );
246
+ CompilerError.invariant(identifierNode !== undefined, {
247
+ reason: "Expected identifier to be initialized",
248
+ description: null,
249
+ loc: place.loc,
250
+ suggestions: null,
251
+ });
252
identifierNode.scopes.add(scope.id);
253
}
254
}
@@ -264,11 +265,12 @@ function computeMemoizedIdentifiers(state: State): Set<IdentifierId> {
265
// Visit an identifier, optionally forcing it to be memoized
266
function visit(id: IdentifierId, forceMemoize: boolean = false): boolean {
267
const node = state.identifiers.get(id);
267
- CompilerError.invariant(
268
- node !== undefined,
269
- `Expected a node for all identifiers, none found for '${id}'`,
270
- null
271
- );
268
+ CompilerError.invariant(node !== undefined, {
269
+ reason: `Expected a node for all identifiers, none found for '${id}'`,
270
+ description: null,
271
+ loc: null,
272
+ suggestions: null,
273
+ });
274
if (node.seen) {
275
return node.memoized;
276
}
@@ -303,11 +305,12 @@ function computeMemoizedIdentifiers(state: State): Set<IdentifierId> {
305
// Force all the scope's optionally-memoizeable dependencies (not "Never") to be memoized
306
function forceMemoizeScopeDependencies(id: ScopeId): void {
307
const node = state.scopes.get(id);
306
- CompilerError.invariant(
307
- node !== undefined,
308
- "Expected a node for all scopes",
309
- null
310
- );
308
+ CompilerError.invariant(node !== undefined, {
309
+ reason: "Expected a node for all scopes",
310
+ description: null,
311
+ loc: null,
312
+ suggestions: null,
313
+ });
314
if (node.seen) {
315
return;
316
}
@@ -606,7 +609,12 @@ function computeMemoizationInputs(
609
};
610
}
611
case "UnsupportedNode": {
609
- CompilerError.invariant(false, `Unexpected unsupported node`, value.loc);
612
+ CompilerError.invariant(false, {
613
+ reason: `Unexpected unsupported node`,
614
+ description: null,
615
+ loc: value.loc,
616
+ suggestions: null,
617
+ });
618
}
619
default: {
620
assertExhaustive(value, `Unexpected value kind '${(value as any).kind}'`);
compiler/forget/packages/babel-plugin-react-forget/src/ReactiveScopes/RenameVariables.ts
+6
-1
@@ -103,6 +103,11 @@ class Scopes {
103
this.#stack.push(next);
104
fn();
105
const last = this.#stack.pop();
106
- CompilerError.invariant(last === next, "Mismatch push/pop calls", null);
106
+ CompilerError.invariant(last === next, {
107
+ reason: "Mismatch push/pop calls",
108
+ description: null,
109
+ loc: null,
110
+ suggestions: null,
111
+ });
112
}
113
}
compiler/forget/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts
+6
-5
@@ -83,11 +83,12 @@ export function eliminateRedundantPhi(fn: HIRFunction): void {
83
same = operand;
84
}
85
}
86
- CompilerError.invariant(
87
- same !== null,
88
- "Expected phis to be non-empty",
89
- null
90
- );
86
+ CompilerError.invariant(same !== null, {
87
+ reason: "Expected phis to be non-empty",
88
+ description: null,
89
+ loc: null,
90
+ suggestions: null,
91
+ });
92
rewrites.set(phi.id, same);
93
block.phis.delete(phi);
94
}
compiler/forget/packages/babel-plugin-react-forget/src/SSA/EnterSSA.ts
+31
-26
@@ -67,11 +67,12 @@ class SSABuilder {
67
}
68
69
state(): State {
70
- CompilerError.invariant(
71
- this.#current !== null,
72
- "we need to be in a block to access state!",
73
- null
74
- );
70
+ CompilerError.invariant(this.#current !== null, {
71
+ reason: "we need to be in a block to access state!",
72
+ description: null,
73
+ loc: null,
74
+ suggestions: null,
75
+ });
76
return this.#states.get(this.#current)!;
77
}
78
@@ -96,12 +97,12 @@ class SSABuilder {
97
98
definePlace(oldPlace: Place): Place {
99
const oldId = oldPlace.identifier;
99
- CompilerError.invariant(
100
- !this.#unknown.has(oldId),
101
- `EnterSSA: Expected identifier to be defined before being used`,
102
- oldPlace.loc,
103
- `Identifier ${printIdentifier(oldId)} is undefined`
104
- );
100
+ CompilerError.invariant(!this.#unknown.has(oldId), {
101
+ reason: `EnterSSA: Expected identifier to be defined before being used`,
102
+ description: `Identifier ${printIdentifier(oldId)} is undefined`,
103
+ loc: oldPlace.loc,
104
+ suggestions: null,
105
+ });
106
107
// Do not redefine context references.
108
if (this.#context.has(oldId)) {
@@ -237,11 +238,12 @@ function enterSSAImpl(
238
): void {
239
const visitedBlocks: Set<BasicBlock> = new Set();
240
for (const [blockId, block] of func.body.blocks) {
240
- CompilerError.invariant(
241
- !visitedBlocks.has(block),
242
- `found a cycle! visiting bb${block.id} again`,
243
- null
244
- );
241
+ CompilerError.invariant(!visitedBlocks.has(block), {
242
+ reason: `found a cycle! visiting bb${block.id} again`,
243
+ description: null,
244
+ loc: null,
245
+ suggestions: null,
246
+ });
247
visitedBlocks.add(block);
248
249
builder.startBlock(block);
@@ -249,11 +251,12 @@ function enterSSAImpl(
251
if (blockId === rootEntry) {
252
// NOTE: func.context should be empty for the root function
253
if (func.env.enableOptimizeFunctionExpressions) {
252
- CompilerError.invariant(
253
- func.context.length === 0,
254
- `Expected function context to be empty for outer function declarations`,
255
- func.loc
256
- );
254
+ CompilerError.invariant(func.context.length === 0, {
255
+ reason: `Expected function context to be empty for outer function declarations`,
256
+ description: null,
257
+ loc: func.loc,
258
+ suggestions: null,
259
+ });
260
} else {
261
func.context = func.context.map((p) => builder.defineContext(p));
262
}
@@ -270,11 +273,13 @@ function enterSSAImpl(
273
) {
274
const loweredFunc = instr.value.loweredFunc;
275
const entry = loweredFunc.body.blocks.get(loweredFunc.body.entry)!;
273
- CompilerError.invariant(
274
- entry.preds.size === 0,
275
- "Expected function expression entry block to have zero predecessors",
276
- null
277
- );
276
+ CompilerError.invariant(entry.preds.size === 0, {
277
+ reason:
278
+ "Expected function expression entry block to have zero predecessors",
279
+ description: null,
280
+ loc: null,
281
+ suggestions: null,
282
+ });
283
entry.preds.add(blockId);
284
builder.defineFunction(loweredFunc);
285
builder.enter(() => {
compiler/forget/packages/babel-plugin-react-forget/src/SSA/LeaveSSA.ts
+54
-33
@@ -131,12 +131,12 @@ export function leaveSSA(fn: HIRFunction): void {
131
if (value.kind === "DeclareLocal") {
132
const name = value.lvalue.place.identifier.name;
133
if (name !== null) {
134
- CompilerError.invariant(
135
- !declarations.has(name),
136
- `Unexpected duplicate declaration`,
137
- value.lvalue.place.loc,
138
- `Found duplicate declaration for '${name}'`
139
- );
134
+ CompilerError.invariant(!declarations.has(name), {
135
+ reason: `Unexpected duplicate declaration`,
136
+ description: `Found duplicate declaration for '${name}'`,
137
+ loc: value.lvalue.place.loc,
138
+ suggestions: null,
139
+ });
140
declarations.set(name, {
141
lvalue: value.lvalue,
142
place: value.lvalue.place,
@@ -153,8 +153,12 @@ export function leaveSSA(fn: HIRFunction): void {
153
) {
154
CompilerError.invariant(
155
originalLVal !== undefined || block.kind === "block",
156
- `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
157
- value.lvalue.place.loc
156
+ {
157
+ reason: `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
158
+ description: null,
159
+ loc: value.lvalue.place.loc,
160
+ suggestions: null,
161
+ }
162
);
163
declarations.set(value.lvalue.place.identifier.name, {
164
lvalue: value.lvalue,
@@ -176,9 +180,14 @@ export function leaveSSA(fn: HIRFunction): void {
180
if (place.identifier.name == null) {
181
CompilerError.invariant(
182
kind === null || kind === InstructionKind.Const,
179
- `Expected consistent kind for destructuring`,
180
- place.loc,
181
- `other places were '${kind}' but '${printPlace(place)}' is const`
183
+ {
184
+ reason: `Expected consistent kind for destructuring`,
185
+ description: `other places were '${kind}' but '${printPlace(
186
+ place
187
+ )}' is const`,
188
+ loc: place.loc,
189
+ suggestions: null,
190
+ }
191
);
192
kind = InstructionKind.Const;
193
} else {
@@ -189,8 +198,12 @@ export function leaveSSA(fn: HIRFunction): void {
198
) {
199
CompilerError.invariant(
200
originalLVal !== undefined || block.kind !== "value",
192
- `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
193
- place.loc
201
+ {
202
+ reason: `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
203
+ description: null,
204
+ loc: place.loc,
205
+ suggestions: null,
206
+ }
207
);
208
declarations.set(place.identifier.name, {
209
lvalue: value.lvalue,
@@ -198,32 +211,39 @@ export function leaveSSA(fn: HIRFunction): void {
211
});
212
CompilerError.invariant(
213
kind === null || kind === InstructionKind.Const,
201
- `Expected consistent kind for destructuring`,
202
- place.loc,
203
- `Other places were '${kind}' but '${printPlace(
204
- place
205
- )}' is const`
214
+ {
215
+ reason: `Expected consistent kind for destructuring`,
216
+ description: `Other places were '${kind}' but '${printPlace(
217
+ place
218
+ )}' is const`,
219
+ loc: place.loc,
220
+ suggestions: null,
221
+ }
222
);
223
kind = InstructionKind.Const;
224
} else {
225
CompilerError.invariant(
226
kind === null || kind === InstructionKind.Reassign,
211
- `Expected consistent kind for destructuring`,
212
- place.loc,
213
- `Other places were '${kind}' but '${printPlace(
214
- place
215
- )}' is reassigned`
227
+ {
228
+ reason: `Expected consistent kind for destructuring`,
229
+ description: `Other places were '${kind}' but '${printPlace(
230
+ place
231
+ )}' is reassigned`,
232
+ loc: place.loc,
233
+ suggestions: null,
234
+ }
235
);
236
kind = InstructionKind.Reassign;
237
originalLVal.lvalue.kind = InstructionKind.Let;
238
}
239
}
240
}
222
- CompilerError.invariant(
223
- kind !== null,
224
- "Expected at least one operand",
225
- null
226
- );
241
+ CompilerError.invariant(kind !== null, {
242
+ reason: "Expected at least one operand",
243
+ description: null,
244
+ loc: null,
245
+ suggestions: null,
246
+ });
247
value.lvalue.kind = kind;
248
}
249
rewritePlace(lvalue, rewrites, declarations);
@@ -356,11 +376,12 @@ export function leaveSSA(fn: HIRFunction): void {
376
377
// If we never saw a declaration for this phi, it may have been pruned by DCE, so synthesize
378
// a new Let binding
359
- CompilerError.invariant(
360
- phi.id.name != null,
361
- "Expected reassignment phis to have a name",
362
- null
363
- );
379
+ CompilerError.invariant(phi.id.name != null, {
380
+ reason: "Expected reassignment phis to have a name",
381
+ description: null,
382
+ loc: null,
383
+ suggestions: null,
384
+ });
385
const declaration = declarations.get(phi.id.name);
386
if (declaration === undefined) {
387
let initValue: Place;
compiler/forget/packages/babel-plugin-react-forget/src/TypeInference/InferTypes.ts
+6
-5
@@ -342,11 +342,12 @@ class Unifier {
342
if (type.kind === "Phi") {
343
const operands = new Set(type.operands.map((i) => this.get(i).kind));
344
345
- CompilerError.invariant(
346
- operands.size > 0,
347
- "there should be at least one operand",
348
- null
349
- );
345
+ CompilerError.invariant(operands.size > 0, {
346
+ reason: "there should be at least one operand",
347
+ description: null,
348
+ loc: null,
349
+ suggestions: null,
350
+ });
351
const kind = operands.values().next().value;
352
353
// there's only one unique type and it's not a type var
compiler/forget/packages/babel-plugin-react-forget/src/Utils/DisjointSet.ts
+6
-5
@@ -20,11 +20,12 @@ export default class DisjointSet<T> {
20
*/
21
union(items: Array<T>): void {
22
const first = items.shift();
23
- CompilerError.invariant(
24
- first != null,
25
- "Expected set to be non-empty",
26
- null
27
- );
23
+ CompilerError.invariant(first != null, {
24
+ reason: "Expected set to be non-empty",
25
+ description: null,
26
+ loc: null,
27
+ suggestions: null,
28
+ });
29
// determine an arbitrary "root" for this set: if the first
30
// item already has a root then use that, otherwise the first item
31
// will be the new root.
compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateFrozenLambdas.ts
+1
@@ -138,6 +138,7 @@ function validateOperand(
138
reason:
139
"Cannot use a mutable function where an immutable value is expected",
140
severity: ErrorSeverity.InvalidReact,
141
+ suggestions: null,
142
});
143
}
144
}
compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateHooksUsage.ts
+1
@@ -31,6 +31,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
31
"Hooks may not be referenced as normal values, they must be called. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)",
32
loc: typeof place.loc !== "symbol" ? place.loc : null,
33
severity: ErrorSeverity.InvalidReact,
34
+ suggestions: null,
35
})
36
);
37
};
compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateNoRefAccesInRender.ts
+2
@@ -109,6 +109,7 @@ function validateNonRefValue(error: CompilerError, operand: Place): void {
109
reason:
110
"Ref values (the `current` property) may not be accessed during render",
111
severity: ErrorSeverity.InvalidReact,
112
+ suggestions: null,
113
})
114
);
115
}
@@ -124,6 +125,7 @@ function validateNonRefObject(error: CompilerError, operand: Place): void {
125
reason:
126
"Ref values may not be passed to functions because they could read the ref value (`current` property) during render",
127
severity: ErrorSeverity.InvalidReact,
128
+ suggestions: null,
129
})
130
);
131
}
compiler/forget/packages/babel-plugin-react-forget/src/Validation/ValidateUnconditionalHooks.ts
+1
@@ -98,6 +98,7 @@ export function validateUnconditionalHooks(
98
"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)",
99
loc,
100
severity: ErrorSeverity.InvalidReact,
101
+ suggestions: null,
102
})
103
);
104
}
compiler/forget/packages/babel-plugin-react-forget/src/index.ts
+1
@@ -9,6 +9,7 @@ export { default as BabelPlugin } from "./Babel/BabelPlugin";
9
export {
10
CompilerError,
11
CompilerErrorDetail,
12
+ CompilerSuggestionOperation,
13
ErrorSeverity,
14
} from "./CompilerError";
15
export {
compiler/forget/packages/eslint-plugin-react-forget/src/rules/ReactForgetDiagnostics.ts
+66
-5
@@ -8,6 +8,7 @@
8
import { transformFromAstSync } from "@babel/core";
9
import type { SourceLocation as BabelSourceLocation } from "@babel/types";
10
import ReactForgetBabelPlugin, {
11
+ CompilerSuggestionOperation,
12
ErrorSeverity,
13
type CompilerError,
14
type CompilerErrorDetail,
@@ -20,6 +21,10 @@ type CompilerErrorDetailWithLoc = Omit<CompilerErrorDetail, "loc"> & {
21
loc: BabelSourceLocation;
22
};
23
24
+function assertExhaustive(_: never, errorMsg: string): never {
25
+ throw new Error(errorMsg);
26
+}
27
+
28
function isReactForgetCompilerError(err: Error): err is CompilerError {
29
return err.name === "ReactForgetCompilerError";
30
}
@@ -63,6 +68,7 @@ const rule: Rule.RuleModule = {
68
description: "Surfaces diagnostics from React Forget",
69
recommended: true,
70
},
71
+ fixable: "code",
72
},
73
create(context: Rule.RuleContext) {
74
// Compat with older versions of eslint
@@ -90,12 +96,67 @@ const rule: Rule.RuleModule = {
96
} catch (err) {
97
if (isReactForgetCompilerError(err) && Array.isArray(err.details)) {
98
for (const detail of err.details) {
93
- if (isReportableDiagnostic(detail)) {
94
- context.report({
95
- message: detail.toString(),
96
- loc: detail.loc,
97
- });
99
+ if (!isReportableDiagnostic(detail)) {
100
+ continue;
101
+ }
102
+ let suggest: Array<Rule.SuggestionReportDescriptor> = [];
103
+ if (Array.isArray(detail.suggestions)) {
104
+ for (const suggestion of detail.suggestions) {
105
+ switch (suggestion.op) {
106
+ case CompilerSuggestionOperation.InsertBefore:
107
+ suggest.push({
108
+ desc: suggestion.description,
109
+ fix(fixer) {
110
+ return fixer.insertTextBeforeRange(
111
+ suggestion.range,
112
+ suggestion.text
113
+ );
114
+ },
115
+ });
116
+ break;
117
+ case CompilerSuggestionOperation.InsertAfter:
118
+ suggest.push({
119
+ desc: suggestion.description,
120
+ fix(fixer) {
121
+ return fixer.insertTextAfterRange(
122
+ suggestion.range,
123
+ suggestion.text
124
+ );
125
+ },
126
+ });
127
+ break;
128
+ case CompilerSuggestionOperation.Replace:
129
+ suggest.push({
130
+ desc: suggestion.description,
131
+ fix(fixer) {
132
+ return fixer.replaceTextRange(
133
+ suggestion.range,
134
+ suggestion.text
135
+ );
136
+ },
137
+ });
138
+ break;
139
+ case CompilerSuggestionOperation.Remove:
140
+ suggest.push({
141
+ desc: suggestion.description,
142
+ fix(fixer) {
143
+ return fixer.removeRange(suggestion.range);
144
+ },
145
+ });
146
+ break;
147
+ default:
148
+ assertExhaustive(
149
+ suggestion,
150
+ "Unhandled suggestion operation"
151
+ );
152
+ }
153
+ }
154
}
155
+ context.report({
156
+ message: detail.toString(),
157
+ loc: detail.loc,
158
+ suggest,
159
+ });
160
}
161
} else {
162
throw err;