Skip to content

Commit 958ddcc

Browse files
committed
feat: Add support for index source maps
1 parent dffbdcd commit 958ddcc

File tree

3 files changed

+136
-4
lines changed

3 files changed

+136
-4
lines changed

src/decode/decode.test.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from "jsr:@std/assert";
1414
import { encodeSigned, encodeUnsigned } from "../vlq.ts";
1515
import { decode, DecodeMode } from "./decode.ts";
16-
import type { SourceMapJson } from "../scopes.d.ts";
16+
import type { IndexSourceMapJson, SourceMapJson } from "../scopes.d.ts";
1717
import { GeneratedRangeFlags, OriginalScopeFlags, Tag } from "../codec.ts";
1818

1919
class ItemEncoder {
@@ -492,4 +492,69 @@ describe("decode", () => {
492492
],
493493
]);
494494
});
495+
496+
it("applies 'generatedOffset' option correctly for line 0", () => {
497+
const scopes = new ScopeInfoBuilder().startRange(0, 0).endRange(0, 10)
498+
.build();
499+
const map = encode(scopes);
500+
501+
const info = decode(map, { generatedOffset: { line: 0, column: 20 } });
502+
503+
assertEquals(info.ranges[0].start, { line: 0, column: 20 });
504+
assertEquals(info.ranges[0].end, { line: 0, column: 30 });
505+
});
506+
507+
it("applies 'generatedOffset' option correctly for non-zero line and column", () => {
508+
const scopes = new ScopeInfoBuilder().startRange(0, 10).endRange(0, 20)
509+
.build();
510+
const map = encode(scopes);
511+
512+
const info = decode(map, { generatedOffset: { line: 2, column: 5 } });
513+
514+
assertEquals(info.ranges[0].start, { line: 2, column: 15 });
515+
assertEquals(info.ranges[0].end, { line: 2, column: 25 });
516+
});
517+
518+
it("decodes index source maps", () => {
519+
const map1 = encode(
520+
new ScopeInfoBuilder().startRange(0, 0).endRange(0, 10)
521+
.build(),
522+
);
523+
const map2 = encode(
524+
new ScopeInfoBuilder().startRange(0, 0).endRange(1, 20)
525+
.build(),
526+
);
527+
const map: IndexSourceMapJson = {
528+
version: 3,
529+
sections: [
530+
{ offset: { line: 0, column: 0 }, map: map1 },
531+
{ offset: { line: 1, column: 42 }, map: map2 },
532+
],
533+
};
534+
535+
const info = decode(map);
536+
537+
assertEquals(info.ranges[0].start, { line: 0, column: 0 });
538+
assertEquals(info.ranges[0].end, { line: 0, column: 10 });
539+
assertEquals(info.ranges[1].start, { line: 1, column: 42 });
540+
assertEquals(info.ranges[1].end, { line: 2, column: 20 });
541+
});
542+
543+
it("ignores 'generatedOffset' option for index source maps", () => {
544+
const map1 = encode(
545+
new ScopeInfoBuilder().startRange(0, 0).endRange(0, 10)
546+
.build(),
547+
);
548+
const map: IndexSourceMapJson = {
549+
version: 3,
550+
sections: [
551+
{ offset: { line: 0, column: 0 }, map: map1 },
552+
],
553+
};
554+
555+
const info = decode(map, { generatedOffset: { line: 4, column: 42 } });
556+
557+
assertEquals(info.ranges[0].start, { line: 0, column: 0 });
558+
assertEquals(info.ranges[0].end, { line: 0, column: 10 });
559+
});
495560
});

src/decode/decode.ts

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ import {
1111
} from "../codec.ts";
1212
import type {
1313
GeneratedRange,
14+
IndexSourceMapJson,
1415
OriginalScope,
16+
Position,
1517
ScopeInfo,
18+
SourceMap,
1619
SourceMapJson,
1720
SubRangeBinding,
1821
} from "../scopes.d.ts";
@@ -37,15 +40,64 @@ export const enum DecodeMode {
3740
LAX = 2,
3841
}
3942

43+
export interface DecodeOptions {
44+
mode: DecodeMode;
45+
46+
/**
47+
* Offsets `start` and `end` of all generated ranges by the specified amount.
48+
* Intended to be used when decoding sections of index source maps one-by-one.
49+
*
50+
* Has no effect when passing a {@link IndexSourceMapJson} directly to {@link decode}.
51+
*/
52+
generatedOffset: Position;
53+
}
54+
55+
export const DEFAULT_DECODE_OPTIONS: DecodeOptions = {
56+
mode: DecodeMode.LAX,
57+
generatedOffset: { line: 0, column: 0 },
58+
};
59+
4060
export function decode(
61+
sourceMap: SourceMap,
62+
options: Partial<DecodeOptions> = DEFAULT_DECODE_OPTIONS,
63+
): ScopeInfo {
64+
const opts = { ...DEFAULT_DECODE_OPTIONS, ...options };
65+
if ("sections" in sourceMap) {
66+
return decodeIndexMap(sourceMap, {
67+
...opts,
68+
generatedOffset: { line: 0, column: 0 },
69+
});
70+
}
71+
return decodeMap(sourceMap, opts);
72+
}
73+
74+
function decodeMap(
4175
sourceMap: SourceMapJson,
42-
options?: { mode: DecodeMode },
76+
options: DecodeOptions,
4377
): ScopeInfo {
4478
if (!sourceMap.scopes || !sourceMap.names) return { scopes: [], ranges: [] };
4579

4680
return new Decoder(sourceMap.scopes, sourceMap.names, options).decode();
4781
}
4882

83+
function decodeIndexMap(
84+
sourceMap: IndexSourceMapJson,
85+
options: DecodeOptions,
86+
): ScopeInfo {
87+
const scopeInfo: ScopeInfo = { scopes: [], ranges: [] };
88+
89+
for (const section of sourceMap.sections) {
90+
const { scopes, ranges } = decode(section.map, {
91+
...options,
92+
generatedOffset: section.offset,
93+
});
94+
for (const scope of scopes) scopeInfo.scopes.push(scope);
95+
for (const range of ranges) scopeInfo.ranges.push(range);
96+
}
97+
98+
return scopeInfo;
99+
}
100+
49101
const DEFAULT_SCOPE_STATE = {
50102
line: 0,
51103
column: 0,
@@ -77,10 +129,12 @@ class Decoder {
77129
#flatOriginalScopes: OriginalScope[] = [];
78130
#subRangeBindingsForRange = new Map<number, [number, number, number][]>();
79131

80-
constructor(scopes: string, names: string[], options?: { mode: DecodeMode }) {
132+
constructor(scopes: string, names: string[], options: DecodeOptions) {
81133
this.#encodedScopes = scopes;
82134
this.#names = names;
83-
this.#mode = options?.mode ?? DecodeMode.LAX;
135+
this.#mode = options.mode;
136+
this.#rangeState.line = options.generatedOffset.line;
137+
this.#rangeState.column = options.generatedOffset.column;
84138
}
85139

86140
decode(): ScopeInfo {

src/scopes.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,19 @@ export interface OriginalPosition extends Position {
159159
sourceIndex: number;
160160
}
161161

162+
/**
163+
* A standard source map, or index source map as per https://tc39.es/ecma426.
164+
*/
165+
export type SourceMap = SourceMapJson | IndexSourceMapJson;
166+
167+
/**
168+
* A standard index source map json object as per https://tc39.es/ecma426.
169+
*/
170+
export interface IndexSourceMapJson {
171+
version: 3;
172+
sections: Array<{ offset: Position; map: SourceMapJson }>;
173+
}
174+
162175
/**
163176
* A standard source map json object as per https://tc39.es/ecma426.
164177
*/

0 commit comments

Comments
 (0)