Skip to content

Commit 81502fe

Browse files
authored
Merge pull request #2039 from github/koesie10/source-map-script-stacktrace
Add source map support for full stacktraces
2 parents 648e674 + 465da69 commit 81502fe

File tree

1 file changed

+96
-28
lines changed

1 file changed

+96
-28
lines changed

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

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,35 @@
55
*
66
* Usage: npx ts-node scripts/source-map.ts <version-number> <filename>:<line>:<column>
77
* For example: npx ts-node scripts/source-map.ts v1.7.8 "/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131164:13"
8+
*
9+
* Alternative usage: npx ts-node scripts/source-map.ts <version-number> <multi-line-stacktrace>
10+
* For example: npx ts-node scripts/source-map.ts v1.7.8 'Error: Failed to find CodeQL distribution.
11+
* at CodeQLCliServer.getCodeQlPath (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131164:13)
12+
* at CodeQLCliServer.launchProcess (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131169:24)
13+
* at CodeQLCliServer.runCodeQlCliInternal (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131194:24)
14+
* at CodeQLCliServer.runJsonCodeQlCliCommand (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131330:20)
15+
* at CodeQLCliServer.resolveRam (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131455:12)
16+
* at QueryServerClient2.startQueryServerImpl (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:138618:21)'
817
*/
918

1019
import { spawnSync } from "child_process";
1120
import { basename, resolve } from "path";
1221
import { pathExists, readJSON } from "fs-extra";
13-
import { SourceMapConsumer } from "source-map";
22+
import { RawSourceMap, SourceMapConsumer } from "source-map";
1423

1524
if (process.argv.length !== 4) {
1625
console.error(
1726
"Expected 2 arguments - the version number and the filename:line number",
1827
);
1928
}
2029

30+
const stackLineRegex =
31+
/at (?<name>.*)? \((?<file>.*):(?<line>\d+):(?<column>\d+)\)/gm;
32+
2133
const versionNumber = process.argv[2].startsWith("v")
2234
? process.argv[2]
2335
: `v${process.argv[2]}`;
24-
const filenameAndLine = process.argv[3];
36+
const stacktrace = process.argv[3];
2537

2638
async function extractSourceMap() {
2739
const sourceMapsDirectory = resolve(
@@ -65,39 +77,80 @@ async function extractSourceMap() {
6577
]);
6678
}
6779

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

70-
const fileBasename = basename(filename);
117+
console.log(mappedStacktrace);
118+
} else {
119+
// This means it's just a filename:line:column
120+
const [filename, line, column] = stacktrace.split(":", 3);
71121

72-
const sourcemapName = `${fileBasename}.map`;
73-
const sourcemapPath = resolve(sourceMapsDirectory, sourcemapName);
122+
const fileBasename = basename(filename);
74123

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

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

92-
if (!originalPosition.source) {
93-
throw new Error(`No source found for ${filenameAndLine}`);
94-
}
131+
const rawSourceMap: RawSourceMap = await readJSON(sourcemapPath);
132+
133+
const originalPosition = await SourceMapConsumer.with(
134+
rawSourceMap,
135+
null,
136+
async function (consumer) {
137+
return consumer.originalPositionFor({
138+
line: parseInt(line, 10),
139+
column: parseInt(column, 10),
140+
});
141+
},
142+
);
95143

96-
const originalFilename = resolve(filename, "..", originalPosition.source);
144+
if (!originalPosition.source) {
145+
throw new Error(`No source found for ${stacktrace}`);
146+
}
97147

98-
console.log(
99-
`${originalFilename}:${originalPosition.line}:${originalPosition.column}`,
100-
);
148+
const originalFilename = resolve(filename, "..", originalPosition.source);
149+
150+
console.log(
151+
`${originalFilename}:${originalPosition.line}:${originalPosition.column}`,
152+
);
153+
}
101154
}
102155

103156
extractSourceMap().catch((e: unknown) => {
@@ -123,3 +176,18 @@ type WorkflowRunListItem = {
123176
databaseId: number;
124177
number: number;
125178
};
179+
180+
async function replaceAsync(
181+
str: string,
182+
regex: RegExp,
183+
replacer: (substring: string, ...args: any[]) => Promise<string>,
184+
) {
185+
const promises: Array<Promise<string>> = [];
186+
str.replace(regex, (match, ...args) => {
187+
const promise = replacer(match, ...args);
188+
promises.push(promise);
189+
return match;
190+
});
191+
const data = await Promise.all(promises);
192+
return str.replace(regex, () => data.shift() as string);
193+
}

0 commit comments

Comments
 (0)