From 0ed03c14bf80b206c7b3e76b34ec2cbbb7ae0cfc Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Fri, 3 Feb 2023 15:21:57 +0100 Subject: [PATCH 1/2] 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. --- extensions/ql-vscode/scripts/source-map.ts | 116 ++++++++++++++++----- 1 file changed, 88 insertions(+), 28 deletions(-) diff --git a/extensions/ql-vscode/scripts/source-map.ts b/extensions/ql-vscode/scripts/source-map.ts index b934435774e..ea908cf1542 100644 --- a/extensions/ql-vscode/scripts/source-map.ts +++ b/extensions/ql-vscode/scripts/source-map.ts @@ -4,12 +4,13 @@ * works with released extensions. * * Usage: npx ts-node scripts/source-map.ts :: + * Alternative usage: npx ts-node scripts/source-map.ts */ import { spawnSync } from "child_process"; import { basename, resolve } from "path"; import { pathExists, readJSON } from "fs-extra"; -import { SourceMapConsumer } from "source-map"; +import { RawSourceMap, SourceMapConsumer } from "source-map"; if (process.argv.length !== 4) { console.error( @@ -17,10 +18,13 @@ if (process.argv.length !== 4) { ); } +const stackLineRegex = + /at (?.*)? \((?.*):(?\d+):(?\d+)\)/gm; + const versionNumber = process.argv[2].startsWith("v") ? process.argv[2] : `v${process.argv[2]}`; -const filenameAndLine = process.argv[3]; +const stacktrace = process.argv[3]; async function extractSourceMap() { const sourceMapsDirectory = resolve( @@ -64,39 +68,80 @@ async function extractSourceMap() { ]); } - const [filename, line, column] = filenameAndLine.split(":", 3); + if (stacktrace.includes("at")) { + const rawSourceMaps = new Map(); + + const mappedStacktrace = await replaceAsync( + stacktrace, + stackLineRegex, + async (match, name, file, line, column) => { + if (!rawSourceMaps.has(file)) { + const rawSourceMap: RawSourceMap = await readJSON( + resolve(sourceMapsDirectory, `${basename(file)}.map`), + ); + rawSourceMaps.set(file, rawSourceMap); + } + + const originalPosition = await SourceMapConsumer.with( + rawSourceMaps.get(file) as RawSourceMap, + null, + async function (consumer) { + return consumer.originalPositionFor({ + line: parseInt(line), + column: parseInt(column), + }); + }, + ); + + if (!originalPosition.source) { + return match; + } + + const originalFilename = resolve(file, "..", originalPosition.source); + + return `at ${originalPosition.name ?? name} (${originalFilename}:${ + originalPosition.line + }:${originalPosition.column})`; + }, + ); - const fileBasename = basename(filename); + console.log(mappedStacktrace); + } else { + // This means it's just a filename:line:column + const [filename, line, column] = stacktrace.split(":", 3); - const sourcemapName = `${fileBasename}.map`; - const sourcemapPath = resolve(sourceMapsDirectory, sourcemapName); + const fileBasename = basename(filename); - if (!(await pathExists(sourcemapPath))) { - throw new Error(`No source map found for ${fileBasename}`); - } + const sourcemapName = `${fileBasename}.map`; + const sourcemapPath = resolve(sourceMapsDirectory, sourcemapName); - const rawSourceMap = await readJSON(sourcemapPath); - - const originalPosition = await SourceMapConsumer.with( - rawSourceMap, - null, - async function (consumer) { - return consumer.originalPositionFor({ - line: parseInt(line), - column: parseInt(column), - }); - }, - ); + if (!(await pathExists(sourcemapPath))) { + throw new Error(`No source map found for ${fileBasename}`); + } - if (!originalPosition.source) { - throw new Error(`No source found for ${filenameAndLine}`); - } + const rawSourceMap: RawSourceMap = await readJSON(sourcemapPath); + + const originalPosition = await SourceMapConsumer.with( + rawSourceMap, + null, + async function (consumer) { + return consumer.originalPositionFor({ + line: parseInt(line), + column: parseInt(column), + }); + }, + ); - const originalFilename = resolve(filename, "..", originalPosition.source); + if (!originalPosition.source) { + throw new Error(`No source found for ${stacktrace}`); + } - console.log( - `${originalFilename}:${originalPosition.line}:${originalPosition.column}`, - ); + const originalFilename = resolve(filename, "..", originalPosition.source); + + console.log( + `${originalFilename}:${originalPosition.line}:${originalPosition.column}`, + ); + } } extractSourceMap().catch((e: unknown) => { @@ -122,3 +167,18 @@ type WorkflowRunListItem = { databaseId: number; number: number; }; + +async function replaceAsync( + str: string, + regex: RegExp, + replacer: (substring: string, ...args: any[]) => Promise, +) { + const promises: Array> = []; + str.replace(regex, (match, ...args) => { + const promise = replacer(match, ...args); + promises.push(promise); + return match; + }); + const data = await Promise.all(promises); + return str.replace(regex, () => data.shift() as string); +} From a5b95944fc124bc98e4a0d8b69cedf4562aafb63 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Mon, 6 Feb 2023 17:13:23 +0000 Subject: [PATCH 2/2] Add radix to `parseInt` --- extensions/ql-vscode/scripts/source-map.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/ql-vscode/scripts/source-map.ts b/extensions/ql-vscode/scripts/source-map.ts index ea908cf1542..013e35a4664 100644 --- a/extensions/ql-vscode/scripts/source-map.ts +++ b/extensions/ql-vscode/scripts/source-map.ts @@ -87,8 +87,8 @@ async function extractSourceMap() { null, async function (consumer) { return consumer.originalPositionFor({ - line: parseInt(line), - column: parseInt(column), + line: parseInt(line, 10), + column: parseInt(column, 10), }); }, ); @@ -126,8 +126,8 @@ async function extractSourceMap() { null, async function (consumer) { return consumer.originalPositionFor({ - line: parseInt(line), - column: parseInt(column), + line: parseInt(line, 10), + column: parseInt(column, 10), }); }, );