aboutsummaryrefslogtreecommitdiff
path: root/packages/bun-debug-adapter-protocol/debugger/sourcemap.ts
blob: 1e04e56f4f679427cd63ba7d72ef50b2a55ce619 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import { SourceMapConsumer } from "source-map-js";

export type Position = {
  line: number;
  column: number;
};

export interface SourceMap {
  generatedPosition(line?: number, column?: number, url?: string): Position;
  originalPosition(line?: number, column?: number): Position;
}

class ActualSourceMap implements SourceMap {
  #sourceMap: SourceMapConsumer;
  #sources: string[];

  constructor(sourceMap: SourceMapConsumer) {
    this.#sourceMap = sourceMap;
    // @ts-ignore
    this.#sources = sourceMap._absoluteSources;
  }

  #getSource(url?: string): string {
    const sources = this.#sources;
    if (sources.length === 1) {
      return sources[0];
    }
    if (!url) {
      return sources[0] ?? "";
    }
    for (const source of sources) {
      if (url.endsWith(source)) {
        return source;
      }
    }
    return "";
  }

  generatedPosition(line?: number, column?: number, url?: string): Position {
    try {
      const source = this.#getSource(url);
      const { line: gline, column: gcolumn } = this.#sourceMap.generatedPositionFor({
        line: line ?? 0,
        column: column ?? 0,
        source,
      });
      console.log(`[sourcemap] -->`, { source, url, line, column }, { gline, gcolumn });
      return {
        line: gline || 0,
        column: gcolumn || 0,
      };
    } catch (error) {
      console.error(error);
      return {
        line: line || 0,
        column: column || 0,
      };
    }
  }

  originalPosition(line?: number, column?: number): Position {
    try {
      const { line: oline, column: ocolumn } = this.#sourceMap.originalPositionFor({
        line: line ?? 0,
        column: column ?? 0,
      });
      console.log(`[sourcemap] <--`, { line, column }, { oline, ocolumn });
      return {
        line: oline || 0,
        column: ocolumn || 0,
      };
    } catch (error) {
      console.error(error);
      return {
        line: line || 0,
        column: column || 0,
      };
    }
  }
}

class NoopSourceMap implements SourceMap {
  generatedPosition(line?: number, column?: number, url?: string): Position {
    return {
      line: line ?? 0,
      column: column ?? 0,
    };
  }

  originalPosition(line?: number, column?: number): Position {
    return {
      line: line ?? 0,
      column: column ?? 0,
    };
  }
}

const defaultSourceMap = new NoopSourceMap();

export function SourceMap(url?: string): SourceMap {
  if (!url || !url.startsWith("data:")) {
    return defaultSourceMap;
  }
  try {
    const [_, base64] = url.split(",", 2);
    const decoded = Buffer.from(base64, "base64").toString("utf8");
    const sourceMap = new SourceMapConsumer(JSON.parse(decoded));
    return new ActualSourceMap(sourceMap);
  } catch (error) {
    console.warn("Failed to parse source map URL", url);
  }
  return defaultSourceMap;
}