Format CompilerErrors with Babel codeframes
Lauren Tan committed
Jan 19, 2023 at 11:37 UTC
32a83374be085a03942dd6e34358f9e8e285aef9
3 files changed
+166
-133
compiler/forget/src/CompilerError.ts
+51
-12
@@ -1,5 +1,6 @@
1
-import { SourceLocation } from "./HIR";
1
+import { Node, NodePath } from "@babel/core";
2
import type { ExtractClassProperties } from "./Utils/types";
3
+import { assertExhaustive } from "./Utils/utils";
4
5
export enum ErrorSeverity {
6
InvalidInput = "InvalidInput",
@@ -7,28 +8,66 @@ export enum ErrorSeverity {
8
}
9
10
export type CompilerErrorOptions = ExtractClassProperties<CompilerError>;
11
+type AnyNodePath = NodePath<Node | null | undefined>;
12
+
13
+class InvalidInputError extends Error {
14
+ constructor(message: string) {
15
+ super(message);
16
+ this.name = `${ErrorSeverity.InvalidInput}Error`;
17
+ }
18
+}
19
+class TodoError extends Error {
20
+ constructor(message: string) {
21
+ super(message);
22
+ this.name = `${ErrorSeverity.Todo}Error`;
23
+ }
24
+}
25
+
26
+function mapSeverityToErrorCtor(severity: ErrorSeverity) {
27
+ switch (severity) {
28
+ case ErrorSeverity.InvalidInput:
29
+ return InvalidInputError;
30
+ case ErrorSeverity.Todo:
31
+ return TodoError;
32
+ default:
33
+ assertExhaustive(severity, `Unhandled severity level: ${severity}`);
34
+ }
35
+}
36
+
37
+function printPathCodeFrame(
38
+ reason: string,
39
+ severity: ErrorSeverity,
40
+ path: AnyNodePath
41
+) {
42
+ return path
43
+ .buildCodeFrameError(reason, mapSeverityToErrorCtor(severity))
44
+ .toString();
45
+}
46
47
export class CompilerError {
48
severity: ErrorSeverity;
49
reason: string;
14
- source: string | null;
15
- loc: SourceLocation | null;
50
+ /**
51
+ * If a NodePath is provided, we will prefer Babel's built in codeframe error generation which
52
+ * will print error markers in the correct location.
53
+ */
54
+ nodePath: AnyNodePath | null;
55
56
constructor(options: CompilerErrorOptions) {
57
this.severity = options.severity;
58
this.reason = options.reason;
20
- this.source = options.source;
21
- this.loc = options.loc;
59
+ this.nodePath = options.nodePath;
60
}
61
62
toString(): string {
25
- const buffer = [`[${this.severity.toUpperCase()}] ${this.reason}`];
26
- if (this.loc != null && typeof this.loc != "symbol") {
27
- buffer.push(` on lines ${this.loc.start.line}:${this.loc.end.line}`);
28
- }
29
- if (this.source != null) {
30
- buffer.push(`\n${this.source}`);
63
+ const buffer = [];
64
+ if (this.nodePath != null) {
65
+ buffer.push(
66
+ printPathCodeFrame(this.reason, this.severity, this.nodePath)
67
+ );
68
+ } else {
69
+ buffer.push(`${this.severity}: ${this.reason}`);
70
}
32
- return `Forget Error: ${buffer.join("")}`;
71
+ return `[ReactForget] ${buffer.join("")}`;
72
}
73
}
compiler/forget/src/HIR/BuildHIR.ts
+10
-25
@@ -71,13 +71,7 @@ export function lower(
71
builder.pushError({
72
reason: `Support non-identifier params: ${param.node.type}`,
73
severity: ErrorSeverity.Todo,
74
- // 1. We can't use NodePath.toString() because we're using @babel/traverse directly, see
75
- // https://fburl.com/45x18dcm for details.
76
- // 2. Only NodePaths override toString to print the source, so if you tried this with
77
- // Node.toString() you'd just get [object Object].
78
- // 3. You will see this in other calls to builder.pushError in this file.
79
- source: func.toString(),
80
- loc: param.node.loc ?? null,
74
+ nodePath: param,
75
});
76
}
77
});
@@ -87,8 +81,7 @@ export function lower(
81
builder.pushError({
82
reason: "Support arrow functions",
83
severity: ErrorSeverity.Todo,
90
- source: func.toString(),
91
- loc: body.node.loc ?? null,
84
+ nodePath: body,
85
});
86
} else if (body.isBlockStatement()) {
87
lowerStatement(builder, body);
@@ -96,8 +89,7 @@ export function lower(
89
builder.pushError({
90
reason: `Unexpected function body kind: ${body.type}}`,
91
severity: ErrorSeverity.InvalidInput,
99
- source: func.toString(),
100
- loc: body.node.loc ?? null,
92
+ nodePath: body,
93
});
94
}
95
@@ -339,8 +331,7 @@ function lowerStatement(
331
builder.pushError({
332
reason: "Support non-variable initialization in for",
333
severity: ErrorSeverity.Todo,
342
- source: stmt.toString(),
343
- loc: init.node?.loc ?? null,
334
+ nodePath: stmt,
335
});
336
return { kind: "error", id: makeInstructionId(0) };
337
}
@@ -359,8 +350,7 @@ function lowerStatement(
350
builder.pushError({
351
reason: "Handle empty for updater",
352
severity: ErrorSeverity.Todo,
362
- source: stmt.toString(),
363
- loc: null,
353
+ nodePath: stmt,
354
});
355
return { kind: "error", id: makeInstructionId(0) };
356
}
@@ -404,8 +394,7 @@ function lowerStatement(
394
builder.pushError({
395
reason: "ForStatement without test",
396
severity: ErrorSeverity.Todo,
407
- source: stmt.toString(),
408
- loc: null,
397
+ nodePath: stmt,
398
});
399
} else {
400
builder.terminateWithContinuation(
@@ -492,8 +481,7 @@ function lowerStatement(
481
builder.pushError({
482
reason: "while statement must have a location",
483
severity: ErrorSeverity.InvalidInput,
495
- source: stmt.toString(),
496
- loc: null,
484
+ nodePath: stmt,
485
});
486
} else {
487
builder.terminateWithContinuation(
@@ -587,8 +575,7 @@ function lowerStatement(
575
reason:
576
"Expected at most one `default` branch, this code should have failed to parse",
577
severity: ErrorSeverity.InvalidInput,
590
- source: stmt.toString(),
591
- loc: case_.node.loc ?? null,
578
+ nodePath: case_,
579
});
580
break;
581
}
@@ -671,8 +658,7 @@ function lowerStatement(
658
builder.pushError({
659
reason: "`var` declarations are not supported, use let or const",
660
severity: ErrorSeverity.Todo,
674
- source: stmt.toString(),
675
- loc: stmt.node.loc ?? null,
661
+ nodePath: stmt,
662
});
663
// TODO: should we lower this to an error variant
664
return;
@@ -756,8 +742,7 @@ function lowerStatement(
742
builder.pushError({
743
reason: `Unhandled statement type: ${stmtPath.type}`,
744
severity: ErrorSeverity.Todo,
759
- source: stmtPath.toString(),
760
- loc: null,
745
+ nodePath: stmtPath,
746
});
747
builder.push({
748
id: makeInstructionId(0),
compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md
+105
-96
@@ -24,102 +24,111 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
24
## Error
25
26
```
27
-Forget Error: [TODO] Support non-identifier params: ArrayPattern on lines 1:1
28
-function foo([a, b], {
29
- c,
30
- d,
31
- e = "e"
32
-}, f = "f", ...args) {
33
- let i = 0;
34
- var x = [];
35
- class Bar {}
36
- with (true) {}
37
- for (; i < 3; i += 1) {
38
- x.push(i);
39
- }
40
- for (; i < 3;) {}
41
- for (;;) {}
42
-}
43
-
44
-Forget Error: [TODO] Support non-identifier params: ObjectPattern on lines 1:1
45
-function foo([a, b], {
46
- c,
47
- d,
48
- e = "e"
49
-}, f = "f", ...args) {
50
- let i = 0;
51
- var x = [];
52
- class Bar {}
53
- with (true) {}
54
- for (; i < 3; i += 1) {
55
- x.push(i);
56
- }
57
- for (; i < 3;) {}
58
- for (;;) {}
59
-}
60
-
61
-Forget Error: [TODO] Support non-identifier params: AssignmentPattern on lines 1:1
62
-function foo([a, b], {
63
- c,
64
- d,
65
- e = "e"
66
-}, f = "f", ...args) {
67
- let i = 0;
68
- var x = [];
69
- class Bar {}
70
- with (true) {}
71
- for (; i < 3; i += 1) {
72
- x.push(i);
73
- }
74
- for (; i < 3;) {}
75
- for (;;) {}
76
-}
77
-
78
-Forget Error: [TODO] Support non-identifier params: RestElement on lines 1:1
79
-function foo([a, b], {
80
- c,
81
- d,
82
- e = "e"
83
-}, f = "f", ...args) {
84
- let i = 0;
85
- var x = [];
86
- class Bar {}
87
- with (true) {}
88
- for (; i < 3; i += 1) {
89
- x.push(i);
90
- }
91
- for (; i < 3;) {}
92
- for (;;) {}
93
-}
94
-
95
-Forget Error: [TODO] `var` declarations are not supported, use let or const on lines 3:3
96
-var x = [];
97
-
98
-Forget Error: [TODO] Unhandled statement type: ClassDeclaration
99
-class Bar {}
100
-
101
-Forget Error: [TODO] Unhandled statement type: WithStatement
102
-with (true) {}
103
-
104
-Forget Error: [TODO] Support non-variable initialization in for
105
-for (; i < 3; i += 1) {
106
- x.push(i);
107
-}
108
-
109
-Forget Error: [TODO] Support non-variable initialization in for
110
-for (; i < 3;) {}
111
-
112
-Forget Error: [TODO] Handle empty for updater
113
-for (; i < 3;) {}
114
-
115
-Forget Error: [TODO] Support non-variable initialization in for
116
-for (;;) {}
117
-
118
-Forget Error: [TODO] Handle empty for updater
119
-for (;;) {}
120
-
121
-Forget Error: [TODO] ForStatement without test
122
-for (;;) {}
27
+[ReactForget] TodoError: Support non-identifier params: ArrayPattern
28
+> 1 | function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
29
+ | ^^^^^^
30
+ 2 | let i = 0;
31
+ 3 | var x = [];
32
+ 4 |
33
+
34
+[ReactForget] TodoError: Support non-identifier params: ObjectPattern
35
+> 1 | function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
36
+ | ^^^^^^^^^^^^^^^^^
37
+ 2 | let i = 0;
38
+ 3 | var x = [];
39
+ 4 |
40
+
41
+[ReactForget] TodoError: Support non-identifier params: AssignmentPattern
42
+> 1 | function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
43
+ | ^^^^^^^
44
+ 2 | let i = 0;
45
+ 3 | var x = [];
46
+ 4 |
47
+
48
+[ReactForget] TodoError: Support non-identifier params: RestElement
49
+> 1 | function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
50
+ | ^^^^^^^
51
+ 2 | let i = 0;
52
+ 3 | var x = [];
53
+ 4 |
54
+
55
+[ReactForget] TodoError: `var` declarations are not supported, use let or const
56
+ 1 | function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
57
+ 2 | let i = 0;
58
+> 3 | var x = [];
59
+ | ^^^^^^^^^^^
60
+ 4 |
61
+ 5 | class Bar {}
62
+ 6 |
63
+
64
+[ReactForget] TodoError: Unhandled statement type: ClassDeclaration
65
+ 3 | var x = [];
66
+ 4 |
67
+> 5 | class Bar {}
68
+ | ^^^^^^^^^^^^
69
+ 6 |
70
+ 7 | with (true) {
71
+ 8 | }
72
+
73
+[ReactForget] TodoError: Unhandled statement type: WithStatement
74
+ 5 | class Bar {}
75
+ 6 |
76
+> 7 | with (true) {
77
+ | ^
78
+ 8 | }
79
+ 9 |
80
+ 10 | for (; i < 3; i += 1) {
81
+
82
+[ReactForget] TodoError: Support non-variable initialization in for
83
+ 8 | }
84
+ 9 |
85
+> 10 | for (; i < 3; i += 1) {
86
+ | ^
87
+ 11 | x.push(i);
88
+ 12 | }
89
+ 13 | for (; i < 3; ) {}
90
+
91
+[ReactForget] TodoError: Support non-variable initialization in for
92
+ 11 | x.push(i);
93
+ 12 | }
94
+> 13 | for (; i < 3; ) {}
95
+ | ^^^^^^^^^^^^^^^^^^
96
+ 14 | for (;;) {}
97
+ 15 | }
98
+ 16 |
99
+
100
+[ReactForget] TodoError: Handle empty for updater
101
+ 11 | x.push(i);
102
+ 12 | }
103
+> 13 | for (; i < 3; ) {}
104
+ | ^^^^^^^^^^^^^^^^^^
105
+ 14 | for (;;) {}
106
+ 15 | }
107
+ 16 |
108
+
109
+[ReactForget] TodoError: Support non-variable initialization in for
110
+ 12 | }
111
+ 13 | for (; i < 3; ) {}
112
+> 14 | for (;;) {}
113
+ | ^^^^^^^^^^^
114
+ 15 | }
115
+ 16 |
116
+
117
+[ReactForget] TodoError: Handle empty for updater
118
+ 12 | }
119
+ 13 | for (; i < 3; ) {}
120
+> 14 | for (;;) {}
121
+ | ^^^^^^^^^^^
122
+ 15 | }
123
+ 16 |
124
+
125
+[ReactForget] TodoError: ForStatement without test
126
+ 12 | }
127
+ 13 | for (; i < 3; ) {}
128
+> 14 | for (;;) {}
129
+ | ^^^^^^^^^^^
130
+ 15 | }
131
+ 16 |
132
```
133
134
\ No newline at end of file