Skip to content

Commit d0634f1

Browse files
committed
Better error reporting
1 parent 04c4295 commit d0634f1

File tree

2 files changed

+108
-14
lines changed

2 files changed

+108
-14
lines changed

src/bin/generate.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,26 @@ async function main() {
110110
}
111111

112112
main().catch(error => {
113+
if (typeof error === 'string') {
114+
github.setOutput('error', error);
115+
} else if ('message' in error && typeof error.message === 'string') {
116+
if ('stack' in error && typeof error.stack === 'string') {
117+
if (error.stack.includes(error.message)) {
118+
github.setOutput('error', error.stack);
119+
} else {
120+
github.setOutput('error', error.message + '\n' + error.stack);
121+
}
122+
} else {
123+
github.setOutput('error', error.message);
124+
}
125+
} else {
126+
try {
127+
github.setOutput('error', JSON.stringify(error));
128+
} catch {
129+
// ignore
130+
}
131+
}
132+
113133
console.error(error);
114134
process.exit(1);
115135
});

src/lib/walker.ts

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,115 @@
1-
import { Root } from 'mdast';
1+
import { Heading, Root } from 'mdast';
2+
import markdown from 'remark-parse';
3+
import stringify from 'remark-stringify';
24
import { Option, assert } from 'ts-std';
5+
import unified from 'unified';
36
import { Node, Parent } from 'unist';
47
import { VFile } from 'vfile';
58

9+
610
type Handler<Options> = (this: Walker<Options>, node: Node) => Option<Node> | Promise<Option<Node>>;
711

812
function isParent(node: Node): node is Parent {
913
return Array.isArray(node.children);
1014
}
1115

16+
const Printer = unified().use(markdown).use(stringify);
17+
1218
export default class Walker<Options> {
19+
private lastNode: Option<Node> = null;
20+
private lastHeading: Option<Heading> = null;
21+
1322
constructor(protected options: Options, protected file: VFile) {}
1423

1524
[key: string]: unknown;
1625

1726
async walk(root: Node): Promise<Root> {
1827
assert(root.type === 'root', `Cannot walk \`${root.type}\` (must be \`root\`)`);
1928

20-
let result = await this.handle(root);
29+
try {
30+
this.lastNode = null;
31+
this.lastHeading = null;
2132

22-
if (Array.isArray(result)) {
23-
assert(result.length === 1, 'Must return a single root node');
24-
result = result[0];
25-
}
33+
let result = await this.handle(root);
2634

27-
if (result) {
28-
assert(result.type === 'root', 'Must return a root');
29-
return result as Root;
30-
} else {
31-
return {
32-
type: 'root',
33-
children: []
34-
};
35+
if (Array.isArray(result)) {
36+
assert(result.length === 1, 'Must return a single root node');
37+
result = result[0];
38+
}
39+
40+
if (result) {
41+
assert(result.type === 'root', 'Must return a root');
42+
return result as Root;
43+
} else {
44+
return {
45+
type: 'root',
46+
children: []
47+
};
48+
}
49+
} catch (error) {
50+
let message: string = 'Encounted an error';
51+
52+
if (this.file.path) {
53+
message += ` while processing ${this.file.path}`;
54+
}
55+
56+
if (this.lastNode?.position) {
57+
let { line, column } = this.lastNode.position.start;
58+
message += ` at L${line}:C${column}`;
59+
}
60+
61+
if (this.lastHeading) {
62+
try {
63+
let heading = Printer.stringify(this.lastHeading);
64+
message += ` (under the section "${heading}")`;
65+
} catch {
66+
// ignore
67+
}
68+
}
69+
70+
if (typeof error === 'string') {
71+
message += `.\nReason:\n${error}`;
72+
} else if ('message' in error && typeof error.message === 'string') {
73+
if ('stack' in error && typeof error.stack === 'string') {
74+
if (error.stack.includes(error.message)) {
75+
message += `.\nReason:\n${error.stack}`;
76+
} else {
77+
message += `.\nReason:\n${error.message}\n${error.stack}`;
78+
}
79+
} else {
80+
message += `.\nReason:\n${error.message}`;
81+
}
82+
}
83+
84+
let reason: Error | string;
85+
86+
if ('stack' in error && typeof error.stack === 'string') {
87+
reason = new Error(message);
88+
89+
if (error.stack.includes(error.message)) {
90+
reason.stack = error.stack.replace(error.message, message);
91+
} else {
92+
reason.stack = error.stack;
93+
}
94+
} else {
95+
reason = message;
96+
}
97+
98+
// upstream type for reason is wrong: should be Error | string
99+
this.file.fail(reason as string, this.lastNode?.position);
100+
} finally {
101+
this.lastNode = null;
102+
this.lastHeading = null;
35103
}
36104
}
37105

38106
protected async handle(node: Node): Promise<Option<Node> | Node[]> {
107+
this.lastNode = node;
108+
109+
if (node.type === 'heading') {
110+
this.lastHeading = node as Heading;
111+
}
112+
39113
let maybeHandler = this[node.type];
40114

41115
if (typeof maybeHandler === 'function') {

0 commit comments

Comments
 (0)