Skip to content

Commit d3f87eb

Browse files
committed
Add sub-range decoding
1 parent 910999f commit d3f87eb

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

src/decode/decode.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,4 +428,68 @@ describe("decode", () => {
428428

429429
assertEquals(info.ranges, []);
430430
});
431+
432+
it("throws for multiple GENERATED_RANGE_SUBRANGE_BINDING items for the same variable in strict mode", () => {
433+
const encoder = new ItemEncoder();
434+
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_START, 0, 0).finishItem();
435+
// Sub-range binding for variable 0
436+
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_SUBRANGE_BINDING, 0, 1, 1, 0)
437+
.finishItem();
438+
// Duplicate sub-range binding for variable 0
439+
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_SUBRANGE_BINDING, 0, 1, 2, 0)
440+
.finishItem();
441+
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_END, 2, 0).finishItem();
442+
const map = createMap(encoder.encode(), ["foo"]);
443+
444+
assertThrows(
445+
() => decode(map, { mode: DecodeMode.STRICT }),
446+
Error,
447+
"Encountered multiple GENERATED_RANGE_SUBRANGE_BINDING items for the same variable",
448+
);
449+
});
450+
451+
it("ignores multiple GENERATED_RANGE_SUBRANGE_BINDING items for the same variable in lax mode", () => {
452+
const encoder = new ItemEncoder();
453+
// Original scope with 1 variable.
454+
encoder.addUnsignedVLQs(Tag.ORIGINAL_SCOPE_START, 0, 0, 0).finishItem();
455+
encoder.addUnsignedVLQs(Tag.ORIGINAL_SCOPE_VARIABLES, 0).finishItem();
456+
encoder.addUnsignedVLQs(Tag.ORIGINAL_SCOPE_END, 1, 0).finishItem();
457+
458+
// Generated range from 0,0 to 3,0, referencing the original scope.
459+
encoder.addUnsignedVLQs(
460+
Tag.GENERATED_RANGE_START,
461+
GeneratedRangeFlags.HAS_DEFINITION,
462+
0,
463+
0,
464+
).addSignedVLQs(0).finishItem();
465+
// Initial binding for the variable is "bar" (index 2).
466+
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_BINDINGS, 2).finishItem();
467+
468+
// 1st sub-range binding for variable 0. from 1,0, value is "var1" (index 1)
469+
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_SUBRANGE_BINDING, 0, 1, 1, 0)
470+
.finishItem();
471+
// 2nd sub-range binding for variable 0. from 2,0, value is "baz" (index 3)
472+
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_SUBRANGE_BINDING, 0, 3, 1, 0)
473+
.finishItem();
474+
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_END, 3, 0).finishItem();
475+
const map = createMap(encoder.encode(), ["var1", "bar", "baz"]);
476+
477+
const info = decode(map, { mode: DecodeMode.LAX });
478+
479+
assertExists(info.ranges[0]);
480+
assertEquals(info.ranges[0].values, [
481+
[
482+
{
483+
value: "bar",
484+
from: { line: 0, column: 0 },
485+
to: { line: 1, column: 0 },
486+
},
487+
{
488+
value: "var1",
489+
from: { line: 1, column: 0 },
490+
to: { line: 3, column: 0 },
491+
},
492+
],
493+
]);
494+
});
431495
});

src/decode/decode.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
OriginalScope,
1515
ScopeInfo,
1616
SourceMapJson,
17+
SubRangeBinding,
1718
} from "../scopes.d.ts";
1819
import { TokenIterator } from "../vlq.ts";
1920

@@ -74,6 +75,7 @@ class Decoder {
7475
readonly #rangeStack: GeneratedRange[] = [];
7576

7677
#flatOriginalScopes: OriginalScope[] = [];
78+
#subRangeBindingsForRange = new Map<number, [number, number, number][]>();
7779

7880
constructor(scopes: string, names: string[], options?: { mode: DecodeMode }) {
7981
this.#encodedScopes = scopes;
@@ -169,6 +171,21 @@ class Decoder {
169171
this.#handleGeneratedRangeBindingsItem(valueIdxs);
170172
break;
171173
}
174+
case Tag.GENERATED_RANGE_SUBRANGE_BINDING: {
175+
const variableIndex = iter.nextUnsignedVLQ();
176+
const bindings: [number, number, number][] = [];
177+
178+
while (iter.hasNext() && iter.peek() !== ",") {
179+
bindings.push([
180+
iter.nextUnsignedVLQ(),
181+
iter.nextUnsignedVLQ(),
182+
iter.nextUnsignedVLQ(),
183+
]);
184+
}
185+
186+
this.#recordGeneratedSubRangeBindingItem(variableIndex, bindings);
187+
break;
188+
}
172189
case Tag.GENERATED_RANGE_CALL_SITE: {
173190
this.#handleGeneratedRangeCallSite(
174191
iter.nextUnsignedVLQ(),
@@ -331,6 +348,7 @@ class Decoder {
331348
}
332349

333350
this.#rangeStack.push(range);
351+
this.#subRangeBindingsForRange.clear();
334352
}
335353

336354
#handleGeneratedRangeBindingsItem(valueIdxs: number[]) {
@@ -351,6 +369,19 @@ class Decoder {
351369
}
352370
}
353371

372+
#recordGeneratedSubRangeBindingItem(
373+
variableIndex: number,
374+
bindings: [number, number, number][],
375+
) {
376+
if (this.#subRangeBindingsForRange.has(variableIndex)) {
377+
this.#throwInStrictMode(
378+
"Encountered multiple GENERATED_RANGE_SUBRANGE_BINDING items for the same variable",
379+
);
380+
return;
381+
}
382+
this.#subRangeBindingsForRange.set(variableIndex, bindings);
383+
}
384+
354385
#handleGeneratedRangeCallSite(
355386
sourceIndex: number,
356387
line: number,
@@ -392,6 +423,8 @@ class Decoder {
392423
column: this.#rangeState.column,
393424
};
394425

426+
this.#handleGeneratedRangeSubRangeBindings(range);
427+
395428
if (this.#rangeStack.length > 0) {
396429
const parent = this.#rangeStack.at(-1)!;
397430
range.parent = parent;
@@ -401,6 +434,48 @@ class Decoder {
401434
}
402435
}
403436

437+
#handleGeneratedRangeSubRangeBindings(range: GeneratedRange) {
438+
for (const [variableIndex, bindings] of this.#subRangeBindingsForRange) {
439+
const value = range.values[variableIndex];
440+
const subRanges: SubRangeBinding[] = [];
441+
range.values[variableIndex] = subRanges;
442+
443+
let lastLine = range.start.line;
444+
let lastColumn = range.start.column;
445+
446+
subRanges.push({
447+
from: { line: lastLine, column: lastColumn },
448+
to: { line: 0, column: 0 },
449+
value: value as string | undefined,
450+
});
451+
452+
for (const [binding, line, column] of bindings) {
453+
lastLine += line;
454+
if (line === 0) {
455+
lastColumn += column;
456+
} else {
457+
lastColumn = column;
458+
}
459+
460+
subRanges.push({
461+
from: { line: lastLine, column: lastColumn },
462+
to: { line: 0, column: 0 }, // This will be fixed in the post-processing step.
463+
value: binding === 0 ? undefined : this.#resolveName(binding - 1),
464+
});
465+
}
466+
}
467+
468+
for (const value of range.values) {
469+
if (Array.isArray(value)) {
470+
const subRanges = value;
471+
for (let i = 0; i < subRanges.length - 1; ++i) {
472+
subRanges[i].to = subRanges[i + 1].from;
473+
}
474+
subRanges[subRanges.length - 1].to = range.end;
475+
}
476+
}
477+
}
478+
404479
#resolveName(index: number): string {
405480
if (index < 0 || index >= this.#names.length) {
406481
this.#throwInStrictMode("Illegal index into the 'names' array");

0 commit comments

Comments
 (0)