1import { describe, expect, it } from "@effect/vitest";
2
3import { decodeSourceMap, symbolicateStack, type CompactSourceMap } from "./sourceMap.ts";
4
5// Real `hermesc -O -output-source-map` output and the stack its VM printed: a frame's column is the
6// map's 0-based generated column. In `t.js`, alpha throws at 2:18, beta calls it at 5:19, gamma calls
7// beta at 8:13, and the top level calls gamma at 10:6.
8const HERMES_MAP = {
9 version: 3,
10 sources: ["t.js"],
11 mappings:
12 "AAAA,iBAAA,OAAA,MAAA,KAAA,MAAA,KAAA,MASA,OAAK,IAAE,EATP,EACY,MAAK,KAAA,WAAA,IAAA,IAAf,EAEF,EACa,OAAK,OAAT,IAAP,EAEF,AACE,EAAM,OAAI,IAAI,EAAE,IAAY,MAAO,OAAF,KAAW,EAC9C;",
13 x_hermes_function_offsets: { "0": [0, 65, 99, 121] },
14};
15const HERMES_STACK = [
16 "Error: boom",
17 " at alpha (address at t.hbc:1:89)",
18 " at beta (address at t.hbc:1:108)",
19 " at gamma (address at t.hbc:1:130)",
20 " at global (address at t.hbc:1:59)",
21].join("\n");
22
23describe("symbolicateStack", () => {
24 it("rewrites Hermes bundle frames to their original locations and keeps the rest", () => {
25 const map = decodeSourceMap(HERMES_MAP) as CompactSourceMap;
26 const result = symbolicateStack(
27 `${HERMES_STACK}\n at apply (native)\n at tryCallOne (address at InternalBytecode.js:1:89)`,
28 map,
29 Number.POSITIVE_INFINITY,
30 );
31 expect(result).toEqual({
32 stack: [
33 "Error: boom",
34 " at alpha (t.js:2:18)",
35 " at beta (t.js:5:19)",
36 " at gamma (t.js:8:13)",
37 " at global (t.js:10:6)",
38 " at apply (native)",
39 " at tryCallOne (address at InternalBytecode.js:1:89)",
40 ].join("\n"),
41 bundleFrames: 4,
42 mappedFrames: 4,
43 growth: -50,
44 });
45 });
46});
47
48describe("decodeSourceMap", () => {
49 it("fits a map as large as our iOS release map, read from an OTA bundle's path", () => {
50 // 2.1M segments, every other one repeating its predecessor's position, like Hermes output.
51 const mappings = `AAAA,${"CAAC,CAAA,".repeat(1_050_000)}CAAC`;
52 const map = decodeSourceMap({ version: 3, sources: ["src/App.tsx"], mappings });
53 const frame =
54 " at render (address at /var/mobile/Containers/Data/Application/A1/Library/" +
55 "Application Support/.expo-internal/bundle:1:4)";
56 expect(symbolicateStack(frame, map as CompactSourceMap, Number.POSITIVE_INFINITY).stack).toBe(
57 " at render (src/App.tsx:1:3)",
58 );
59 });
60});