diff --git a/package.json b/package.json index c8faff11..6fdbb3af 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,6 @@ "acorn-walk": "^8.2.0", "astring": "^1.8.6", "chalk": "<5", - "convert-source-map": "^2.0.0", "meriyah": "^4.3.7", "source-map-js": "^1.0.2", "strip-json-comments": "^3", diff --git a/src/transform.ts b/src/transform.ts index ddba0378..90131a2c 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -1,11 +1,11 @@ import assert from "node:assert"; import { readFileSync, writeFileSync } from "node:fs"; +import { dirname } from "node:path"; import { URL, fileURLToPath } from "node:url"; import { debuglog } from "node:util"; import { isNativeError } from "node:util/types"; import { generate } from "astring"; -import * as SourceMapConverter from "convert-source-map"; import { parse, type ESTree } from "meriyah"; import { RawSourceMap, SourceMapConsumer } from "source-map-js"; @@ -64,34 +64,28 @@ function dumpTree(xformed: ESTree.Program, url: URL) { treeDebug("wrote transformed tree to %s", path); } -function getSourceMap(url: URL, code: string): SourceMapConsumer | undefined { - if ( - process.versions.node.split(".").map(Number) < [18, 19] && - [".ts", ".mts", ".tsx"].some((e) => url.pathname.toLowerCase().endsWith(e)) - ) { - // HACK: In node 18.18, when using --loader node-ts/esm - // sourcemap gets inserted twice to the typescript file. We remove the second one - // which reflects transpiled files map, instead of the original typescript file map. - code = SourceMapConverter.removeComments(code); - if (code.indexOf("//# sourceMappingURL") > -1) - // Correct one remains, it needs to start in new line. - code.replace("//# sourceMappingURL", "\n//# sourceMappingURL"); - } +function getSourceMap(fileUrl: URL, code: string): SourceMapConsumer | undefined { + const sourceMappingUrl = code.match(/\/\/# sourceMappingURL=(.*)/)?.[1]; + if (!sourceMappingUrl) return; - const readFile = (filename: string) => { - const fileUrl = new URL(filename, url); - switch (fileUrl.protocol) { - case "file:": - return readFileSync(fileUrl, "utf8"); - case "data:": - return parseDataUrl(fileUrl); - default: - throw new Error(`unhandled protocol reading source map: ${fileUrl.protocol}`); - } - }; + const sourceMapUrl = new URL(sourceMappingUrl, fileUrl); + + let sourceMap: RawSourceMap; + + switch (sourceMapUrl.protocol) { + case "data:": + sourceMap = JSON.parse(parseDataUrl(sourceMapUrl)) as RawSourceMap; + break; + case "file:": + fileUrl = sourceMapUrl; + sourceMap = JSON.parse(readFileSync(fileURLToPath(sourceMapUrl), "utf8")) as RawSourceMap; + break; + default: + throw new Error(`Unsupported source map protocol: ${sourceMapUrl.protocol}`); + } - const map = SourceMapConverter.fromMapFileSource(code, readFile); - if (map) return new SourceMapConsumer(map.sourcemap as RawSourceMap); + sourceMap.sourceRoot ||= dirname(fileURLToPath(fileUrl)); + return new SourceMapConsumer(sourceMap); } function parseDataUrl(fileUrl: URL) { diff --git a/test/__snapshots__/typescript-esm.test.ts.snap b/test/__snapshots__/typescript-esm.test.ts.snap index cd78305c..80e8c2aa 100644 --- a/test/__snapshots__/typescript-esm.test.ts.snap +++ b/test/__snapshots__/typescript-esm.test.ts.snap @@ -8,7 +8,7 @@ exports[`esm-loader is loaded when required 1`] = ` { "children": [ { - "location": "index.ts:1", + "location": "src/index.ts:1", "name": "run", "static": true, "type": "function", @@ -18,7 +18,7 @@ exports[`esm-loader is loaded when required 1`] = ` "type": "class", }, ], - "name": "appmap-node-typescript-esm-test", + "name": "src", "type": "package", }, ], @@ -30,7 +30,7 @@ exports[`esm-loader is loaded when required 1`] = ` "lineno": 1, "method_id": "run", "parameters": [], - "path": "index.ts", + "path": "src/index.ts", "static": true, "thread_id": 0, }, diff --git a/test/typescript-esm.test.ts b/test/typescript-esm.test.ts index 604361fd..4f559f69 100644 --- a/test/typescript-esm.test.ts +++ b/test/typescript-esm.test.ts @@ -4,7 +4,7 @@ import { spawnSync } from "node:child_process"; import { integrationTest, readAppmap, resolveTarget, runAppmapNode } from "./helpers"; integrationTest("esm-loader is loaded when required", () => { - expect(runAppmapNode("node", "--loader", "ts-node/esm", "index.ts").status).toBe(0); + expect(runAppmapNode("node", "--loader", "ts-node/esm", "src/index.ts").status).toBe(0); expect(readAppmap()).toMatchSnapshot(); }); @@ -12,9 +12,9 @@ integrationTest("appmap-node uses external source maps", () => { expect( spawnSync("yarn", ["tsc"], { cwd: resolveTarget(), shell: true, stdio: "inherit" }).status, ).toBe(0); - expect(runAppmapNode("index.js").status).toBe(0); + expect(runAppmapNode("dist/index.js").status).toBe(0); const appMap = readAppmap(); const fun = appMap.classMap.at(0)?.children?.at(0)?.children?.at(0); assert(fun?.type === "function"); - expect(fun.location).toContain("index.ts"); + expect(fun.location).toMatch(/^src\/index.ts:/); }); diff --git a/test/typescript-esm/index.ts b/test/typescript-esm/src/index.ts similarity index 100% rename from test/typescript-esm/index.ts rename to test/typescript-esm/src/index.ts diff --git a/test/typescript-esm/tsconfig.json b/test/typescript-esm/tsconfig.json index a6bd3f76..13784fc8 100644 --- a/test/typescript-esm/tsconfig.json +++ b/test/typescript-esm/tsconfig.json @@ -2,11 +2,12 @@ "ts-node": { "esm": true }, - "files": ["index.ts"], + "files": ["src/index.ts"], "compilerOptions": { "strictNullChecks": true, "module": "ESNext" /* Specify what module code is generated. */, - "rootDir": "./" /* Specify the root folder within your source files. */, + "rootDir": "src" /* Specify the root folder within your source files. */, + "outDir": "dist", "types": ["node"], "sourceMap": true } diff --git a/yarn.lock b/yarn.lock index f3260ce8..593e9a1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2669,7 +2669,6 @@ __metadata: astring: ^1.8.6 caller: ^1.1.0 chalk: <5 - convert-source-map: ^2.0.0 eslint: ^8.47.0 eslint-config-prettier: ^9.0.0 eslint-plugin-prettier: ^5.0.1