@samitouri / QOS-React-2 / commits / 32a83374be

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