Skip to content

Commit

Permalink
fix: Handle relative paths in source maps correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
dividedmind committed Feb 6, 2024
1 parent a17ccbc commit 4417e57
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 37 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
48 changes: 21 additions & 27 deletions src/transform.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions test/__snapshots__/typescript-esm.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -18,7 +18,7 @@ exports[`esm-loader is loaded when required 1`] = `
"type": "class",
},
],
"name": "appmap-node-typescript-esm-test",
"name": "src",
"type": "package",
},
],
Expand All @@ -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,
},
Expand Down
6 changes: 3 additions & 3 deletions test/typescript-esm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ 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();
});

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:/);
});
File renamed without changes.
5 changes: 3 additions & 2 deletions test/typescript-esm/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 0 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 4417e57

Please sign in to comment.