Skip to content

Commit 3d0a2a5

Browse files
authored
feat: Add indicator in decoding result whether variables and binding expressions are present (#3)
1 parent 2a2f5fe commit 3d0a2a5

5 files changed

Lines changed: 108 additions & 15 deletions

File tree

src/decode/decode.test.ts

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import { describe, it } from "@std/testing/bdd";
66
import { ScopeInfoBuilder } from "../builder/builder.ts";
77
import { encode } from "../encode/encode.ts";
88
import {
9+
assert,
910
assertEquals,
1011
assertExists,
12+
assertFalse,
1113
assertStrictEquals,
1214
assertThrows,
1315
} from "@std/assert";
@@ -75,7 +77,7 @@ describe("decode", () => {
7577
encodeUnsigned(256),
7678
];
7779
map.scopes = items.join(",");
78-
assertEquals(decode(map), info);
80+
assertEquals(decode(map), { ...info, hasVariableAndBindingInfo: false });
7981
});
8082

8183
it("handles trailing VLQs in ORIGINAL_SCOPE_START items", () => {
@@ -90,7 +92,7 @@ describe("decode", () => {
9092
parts[0] += encodeSigned(-16);
9193
map.scopes = parts.join(",");
9294

93-
assertEquals(decode(map), info);
95+
assertEquals(decode(map), { ...info, hasVariableAndBindingInfo: false });
9496
});
9597

9698
it("handles trailing VLQs in ORIGINAL_SCOPE_END items", () => {
@@ -105,7 +107,7 @@ describe("decode", () => {
105107
parts[1] += encodeSigned(-16);
106108
map.scopes = parts.join(",");
107109

108-
assertEquals(decode(map), info);
110+
assertEquals(decode(map), { ...info, hasVariableAndBindingInfo: false });
109111
});
110112

111113
it("ignores wrong 'name' indices in lax mode", () => {
@@ -567,6 +569,7 @@ describe("decode", () => {
567569
assertEquals(decode(map, { mode: DecodeMode.STRICT }), {
568570
scopes: [],
569571
ranges: [],
572+
hasVariableAndBindingInfo: false,
570573
});
571574
});
572575

@@ -589,6 +592,63 @@ describe("decode", () => {
589592
encoder.finishItem();
590593
const map = createMap(encoder.encode(), []);
591594

592-
assertEquals(decode(map), { scopes: [], ranges: [] });
595+
assertEquals(decode(map), {
596+
scopes: [],
597+
ranges: [],
598+
hasVariableAndBindingInfo: false,
599+
});
600+
});
601+
602+
describe("hasVariableAndBindingInfo", () => {
603+
it("is 'false' when no variables/bindings are present", () => {
604+
const map = encode(
605+
new ScopeInfoBuilder().startScope(0, 0, {
606+
isStackFrame: true,
607+
key: "fn",
608+
}).endScope(10, 0).startRange(0, 0, {
609+
scopeKey: "fn",
610+
isStackFrame: true,
611+
}).endRange(0, 10).build(),
612+
);
613+
614+
const { hasVariableAndBindingInfo } = decode(map);
615+
616+
assertFalse(hasVariableAndBindingInfo);
617+
});
618+
619+
it("is 'false' when only variables are present", () => {
620+
const map = encode(
621+
new ScopeInfoBuilder().startScope(0, 0, {
622+
isStackFrame: true,
623+
key: "fn",
624+
variables: ["foo", "bar"],
625+
}).endScope(10, 0).startRange(0, 0, {
626+
scopeKey: "fn",
627+
isStackFrame: true,
628+
}).endRange(0, 10).build(),
629+
);
630+
631+
const { hasVariableAndBindingInfo } = decode(map);
632+
633+
assertFalse(hasVariableAndBindingInfo);
634+
});
635+
636+
it("is 'true' when variables/bindings are present", () => {
637+
const map = encode(
638+
new ScopeInfoBuilder().startScope(0, 0, {
639+
isStackFrame: true,
640+
key: "fn",
641+
variables: ["foo", "bar"],
642+
}).endScope(10, 0).startRange(0, 0, {
643+
scopeKey: "fn",
644+
isStackFrame: true,
645+
values: ["n", "m"],
646+
}).endRange(0, 10).build(),
647+
);
648+
649+
const { hasVariableAndBindingInfo } = decode(map);
650+
651+
assert(hasVariableAndBindingInfo);
652+
});
593653
});
594654
});

src/decode/decode.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import {
1010
Tag,
1111
} from "../codec.ts";
1212
import type {
13+
DecodedScopeInfo,
1314
GeneratedRange,
1415
IndexSourceMapJson,
1516
OriginalScope,
1617
Position,
17-
ScopeInfo,
1818
SourceMap,
1919
SourceMapJson,
2020
SubRangeBinding,
@@ -60,7 +60,7 @@ export const DEFAULT_DECODE_OPTIONS: DecodeOptions = {
6060
export function decode(
6161
sourceMap: SourceMap,
6262
options: Partial<DecodeOptions> = DEFAULT_DECODE_OPTIONS,
63-
): ScopeInfo {
63+
): DecodedScopeInfo {
6464
const opts = { ...DEFAULT_DECODE_OPTIONS, ...options };
6565
if ("sections" in sourceMap) {
6666
return decodeIndexMap(sourceMap, {
@@ -74,25 +74,32 @@ export function decode(
7474
function decodeMap(
7575
sourceMap: SourceMapJson,
7676
options: DecodeOptions,
77-
): ScopeInfo {
78-
if (!sourceMap.scopes || !sourceMap.names) return { scopes: [], ranges: [] };
77+
): DecodedScopeInfo {
78+
if (!sourceMap.scopes || !sourceMap.names) {
79+
return { scopes: [], ranges: [], hasVariableAndBindingInfo: false };
80+
}
7981

8082
return new Decoder(sourceMap.scopes, sourceMap.names, options).decode();
8183
}
8284

8385
function decodeIndexMap(
8486
sourceMap: IndexSourceMapJson,
8587
options: DecodeOptions,
86-
): ScopeInfo {
87-
const scopeInfo: ScopeInfo = { scopes: [], ranges: [] };
88+
): DecodedScopeInfo {
89+
const scopeInfo: DecodedScopeInfo = {
90+
scopes: [],
91+
ranges: [],
92+
hasVariableAndBindingInfo: false,
93+
};
8894

8995
for (const section of sourceMap.sections) {
90-
const { scopes, ranges } = decode(section.map, {
96+
const { scopes, ranges, hasVariableAndBindingInfo } = decode(section.map, {
9197
...options,
9298
generatedOffset: section.offset,
9399
});
94100
for (const scope of scopes) scopeInfo.scopes.push(scope);
95101
for (const range of ranges) scopeInfo.ranges.push(range);
102+
scopeInfo.hasVariableAndBindingInfo ||= hasVariableAndBindingInfo;
96103
}
97104

98105
return scopeInfo;
@@ -132,6 +139,9 @@ class Decoder {
132139
Map<number, [number, number, number][]>
133140
>();
134141

142+
#seenOriginalScopeVariables = false;
143+
#seenGeneratedRangeBindings = false;
144+
135145
constructor(scopes: string, names: string[], options: DecodeOptions) {
136146
this.#encodedScopes = scopes;
137147
this.#names = names;
@@ -140,7 +150,7 @@ class Decoder {
140150
this.#rangeState.column = options.generatedOffset.column;
141151
}
142152

143-
decode(): ScopeInfo {
153+
decode(): DecodedScopeInfo {
144154
const iter = new TokenIterator(this.#encodedScopes);
145155

146156
while (iter.hasNext()) {
@@ -175,6 +185,7 @@ class Decoder {
175185
}
176186

177187
this.#handleOriginalScopeVariablesItem(variableIdxs);
188+
this.#seenOriginalScopeVariables = true;
178189
break;
179190
}
180191
case Tag.ORIGINAL_SCOPE_END: {
@@ -224,6 +235,7 @@ class Decoder {
224235
}
225236

226237
this.#handleGeneratedRangeBindingsItem(valueIdxs);
238+
this.#seenGeneratedRangeBindings = true;
227239
break;
228240
}
229241
case Tag.GENERATED_RANGE_SUBRANGE_BINDING: {
@@ -239,6 +251,7 @@ class Decoder {
239251
}
240252

241253
this.#recordGeneratedSubRangeBindingItem(variableIndex, bindings);
254+
this.#seenGeneratedRangeBindings = true;
242255
break;
243256
}
244257
case Tag.GENERATED_RANGE_CALL_SITE: {
@@ -275,11 +288,18 @@ class Decoder {
275288
);
276289
}
277290

278-
const info = { scopes: this.#scopes, ranges: this.#ranges };
291+
const info = {
292+
scopes: this.#scopes,
293+
ranges: this.#ranges,
294+
hasVariableAndBindingInfo: this.#seenOriginalScopeVariables &&
295+
this.#seenGeneratedRangeBindings,
296+
};
279297

280298
this.#scopes = [];
281299
this.#ranges = [];
282300
this.#flatOriginalScopes = [];
301+
this.#seenOriginalScopeVariables = false;
302+
this.#seenGeneratedRangeBindings = false;
283303

284304
return info;
285305
}

src/mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
export type {
66
Binding,
7+
DecodedScopeInfo,
78
GeneratedRange,
89
OriginalPosition,
910
OriginalScope,

src/roundtrip.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import { assertEquals } from "@std/assert";
1515
import { ScopeInfoBuilder } from "./builder/builder.ts";
1616

1717
function assertCodec(scopesInfo: ScopeInfo): void {
18-
assertEquals(decode(encode(scopesInfo)), scopesInfo);
18+
const { scopes, ranges } = decode(encode(scopesInfo));
19+
assertEquals({ scopes, ranges }, scopesInfo);
1920
}
2021

2122
describe("round trip", () => {

src/scopes.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// found in the LICENSE file.
44

55
/**
6-
* The decoded scopes information found in a source map.
6+
* The scopes information found in a source map.
77
*/
88
export interface ScopeInfo {
99
/**
@@ -17,6 +17,17 @@ export interface ScopeInfo {
1717
ranges: GeneratedRange[];
1818
}
1919

20+
/**
21+
* The scopes information produced by the decoder.
22+
*/
23+
export interface DecodedScopeInfo extends ScopeInfo {
24+
/**
25+
* When the encoded scopes contained variable and binding expression items.
26+
* Note that a value of `true` also indicates "partial" info and does not guarantee comprehensiveness.
27+
*/
28+
hasVariableAndBindingInfo: boolean;
29+
}
30+
2031
/**
2132
* A scope in the authored source.
2233
*/

0 commit comments

Comments
 (0)