Skip to content

Commit 6785fe1

Browse files
committed
Better error reporting
1 parent 04c4295 commit 6785fe1

File tree

2 files changed

+103
-14
lines changed

2 files changed

+103
-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: 83 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,110 @@
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 ${line}:${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+
reason.stack = error.stack;
89+
} else {
90+
reason = message;
91+
}
92+
93+
// upstream type for reason is wrong: should be Error | string
94+
this.file.fail(reason as string, this.lastNode?.position);
95+
} finally {
96+
this.lastNode = null;
97+
this.lastHeading = null;
3598
}
3699
}
37100

38101
protected async handle(node: Node): Promise<Option<Node> | Node[]> {
102+
this.lastNode = node;
103+
104+
if (node.type === 'heading') {
105+
this.lastHeading = node as Heading;
106+
}
107+
39108
let maybeHandler = this[node.type];
40109

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

0 commit comments

Comments
 (0)