Skip to content

Commit 0ed03c1

Browse files
committed
Add source map support for full stacktraces
This adds support for mapping full stacktraces in the source map script. This allows you to pass a full stacktrace to the script and get back a stacktrace with all original positions.
1 parent 92e2975 commit 0ed03c1

File tree

1 file changed

+88
-28
lines changed

1 file changed

+88
-28
lines changed

extensions/ql-vscode/scripts/source-map.ts

Lines changed: 88 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,27 @@
44
* works with released extensions.
55
*
66
* Usage: npx ts-node scripts/source-map.ts <version-number> <filename>:<line>:<column>
7+
* Alternative usage: npx ts-node scripts/source-map.ts <version-number> <multi-line-stacktrace>
78
*/
89

910
import { spawnSync } from "child_process";
1011
import { basename, resolve } from "path";
1112
import { pathExists, readJSON } from "fs-extra";
12-
import { SourceMapConsumer } from "source-map";
13+
import { RawSourceMap, SourceMapConsumer } from "source-map";
1314

1415
if (process.argv.length !== 4) {
1516
console.error(
1617
"Expected 2 arguments - the version number and the filename:line number",
1718
);
1819
}
1920

21+
const stackLineRegex =
22+
/at (?<name>.*)? \((?<file>.*):(?<line>\d+):(?<column>\d+)\)/gm;
23+
2024
const versionNumber = process.argv[2].startsWith("v")
2125
? process.argv[2]
2226
: `v${process.argv[2]}`;
23-
const filenameAndLine = process.argv[3];
27+
const stacktrace = process.argv[3];
2428

2529
async function extractSourceMap() {
2630
const sourceMapsDirectory = resolve(
@@ -64,39 +68,80 @@ async function extractSourceMap() {
6468
]);
6569
}
6670

67-
const [filename, line, column] = filenameAndLine.split(":", 3);
71+
if (stacktrace.includes("at")) {
72+
const rawSourceMaps = new Map<string, RawSourceMap>();
73+
74+
const mappedStacktrace = await replaceAsync(
75+
stacktrace,
76+
stackLineRegex,
77+
async (match, name, file, line, column) => {
78+
if (!rawSourceMaps.has(file)) {
79+
const rawSourceMap: RawSourceMap = await readJSON(
80+
resolve(sourceMapsDirectory, `${basename(file)}.map`),
81+
);
82+
rawSourceMaps.set(file, rawSourceMap);
83+
}
84+
85+
const originalPosition = await SourceMapConsumer.with(
86+
rawSourceMaps.get(file) as RawSourceMap,
87+
null,
88+
async function (consumer) {
89+
return consumer.originalPositionFor({
90+
line: parseInt(line),
91+
column: parseInt(column),
92+
});
93+
},
94+
);
95+
96+
if (!originalPosition.source) {
97+
return match;
98+
}
99+
100+
const originalFilename = resolve(file, "..", originalPosition.source);
101+
102+
return `at ${originalPosition.name ?? name} (${originalFilename}:${
103+
originalPosition.line
104+
}:${originalPosition.column})`;
105+
},
106+
);
68107

69-
const fileBasename = basename(filename);
108+
console.log(mappedStacktrace);
109+
} else {
110+
// This means it's just a filename:line:column
111+
const [filename, line, column] = stacktrace.split(":", 3);
70112

71-
const sourcemapName = `${fileBasename}.map`;
72-
const sourcemapPath = resolve(sourceMapsDirectory, sourcemapName);
113+
const fileBasename = basename(filename);
73114

74-
if (!(await pathExists(sourcemapPath))) {
75-
throw new Error(`No source map found for ${fileBasename}`);
76-
}
115+
const sourcemapName = `${fileBasename}.map`;
116+
const sourcemapPath = resolve(sourceMapsDirectory, sourcemapName);
77117

78-
const rawSourceMap = await readJSON(sourcemapPath);
79-
80-
const originalPosition = await SourceMapConsumer.with(
81-
rawSourceMap,
82-
null,
83-
async function (consumer) {
84-
return consumer.originalPositionFor({
85-
line: parseInt(line),
86-
column: parseInt(column),
87-
});
88-
},
89-
);
118+
if (!(await pathExists(sourcemapPath))) {
119+
throw new Error(`No source map found for ${fileBasename}`);
120+
}
90121

91-
if (!originalPosition.source) {
92-
throw new Error(`No source found for ${filenameAndLine}`);
93-
}
122+
const rawSourceMap: RawSourceMap = await readJSON(sourcemapPath);
123+
124+
const originalPosition = await SourceMapConsumer.with(
125+
rawSourceMap,
126+
null,
127+
async function (consumer) {
128+
return consumer.originalPositionFor({
129+
line: parseInt(line),
130+
column: parseInt(column),
131+
});
132+
},
133+
);
94134

95-
const originalFilename = resolve(filename, "..", originalPosition.source);
135+
if (!originalPosition.source) {
136+
throw new Error(`No source found for ${stacktrace}`);
137+
}
96138

97-
console.log(
98-
`${originalFilename}:${originalPosition.line}:${originalPosition.column}`,
99-
);
139+
const originalFilename = resolve(filename, "..", originalPosition.source);
140+
141+
console.log(
142+
`${originalFilename}:${originalPosition.line}:${originalPosition.column}`,
143+
);
144+
}
100145
}
101146

102147
extractSourceMap().catch((e: unknown) => {
@@ -122,3 +167,18 @@ type WorkflowRunListItem = {
122167
databaseId: number;
123168
number: number;
124169
};
170+
171+
async function replaceAsync(
172+
str: string,
173+
regex: RegExp,
174+
replacer: (substring: string, ...args: any[]) => Promise<string>,
175+
) {
176+
const promises: Array<Promise<string>> = [];
177+
str.replace(regex, (match, ...args) => {
178+
const promise = replacer(match, ...args);
179+
promises.push(promise);
180+
return match;
181+
});
182+
const data = await Promise.all(promises);
183+
return str.replace(regex, () => data.shift() as string);
184+
}

0 commit comments

Comments
 (0)