diff --git a/packages/sourcemap-codec/benchmark/index.js b/packages/sourcemap-codec/benchmark/index.js index c252110..e2d4632 100644 --- a/packages/sourcemap-codec/benchmark/index.js +++ b/packages/sourcemap-codec/benchmark/index.js @@ -8,7 +8,6 @@ const latestSourcemapCodec = require('jridgewell-sourcemap-codec'); const originalSourcemapCodec = require('sourcemap-codec'); const sourceMap061 = require('source-map'); const sourceMapWasm = require('source-map-wasm'); -const sourcemapCodecVersion = require('jridgewell-sourcemap-codec/package.json').version; const ChromeMap = require('./chrome').SourceMap; const dir = relative(process.cwd(), __dirname); @@ -56,7 +55,7 @@ async function bench(file) { { console.log('Decode Memory Usage:'); const results = []; - track('@jridgewell/sourcemap-codec', results, () => { + track(diff ? 'local code' : '@jridgewell/sourcemap-codec', results, () => { return localCode.decode(encoded); }); if (diff) { @@ -90,22 +89,22 @@ async function bench(file) { console.log(''); console.log('Decode speed:'); - bench = new Benchmark.Suite().add('decode: @jridgewell/sourcemap-codec', () => { + bench = new Benchmark.Suite().add(diff ? 'local code' : '@jridgewell/sourcemap-codec', () => { localCode.decode(encoded); }); if (diff) { - bench = bench.add(`decode: @jridgewell/sourcemap-codec @latest`, () => { + bench = bench.add(`@jridgewell/sourcemap-codec @latest`, () => { latestSourcemapCodec.decode(encoded); }); } else { bench = bench - .add('decode: sourcemap-codec', () => { + .add('sourcemap-codec', () => { originalSourcemapCodec.decode(encoded); }) - .add('decode: source-map-0.6.1', () => { + .add('source-map-0.6.1', () => { consumer061._parseMappings(encoded, ''); }) - .add('decode: source-map-0.8.0', () => { + .add('source-map-0.8.0', () => { consumerWasm.destroy(); consumerWasm._parseMappings(encoded, ''); }) @@ -134,7 +133,7 @@ async function bench(file) { { console.log('Encode Memory Usage:'); const results = []; - track('@jridgewell/sourcemap-codec', results, () => { + track(diff ? 'local code' : '@jridgewell/sourcemap-codec', results, () => { return localCode.encode(decoded); }); if (diff) { @@ -162,22 +161,22 @@ async function bench(file) { console.log(''); console.log('Encode speed:'); - bench = new Benchmark.Suite().add('encode: local code', () => { + bench = new Benchmark.Suite().add(diff ? 'local code' : '@jridgewell/sourcemap-codec', () => { localCode.encode(decoded); }); if (diff) { - bench = bench.add(`encode: @jridgewell/sourcemap-codec ${sourcemapCodecVersion}`, () => { + bench = bench.add(`@jridgewell/sourcemap-codec @latest`, () => { latestSourcemapCodec.encode(decoded); }); } else { bench = bench - .add('encode: sourcemap-codec', () => { + .add('sourcemap-codec', () => { originalSourcemapCodec.encode(decoded); }) - .add('encode: source-map-0.6.1', () => { + .add('source-map-0.6.1', () => { generator061._serializeMappings(); }) - .add('encode: source-map-0.8.0', () => { + .add('source-map-0.8.0', () => { generatorWasm._serializeMappings(); }); } diff --git a/packages/sourcemap-codec/package.json b/packages/sourcemap-codec/package.json index 8efce32..d3eb7e8 100644 --- a/packages/sourcemap-codec/package.json +++ b/packages/sourcemap-codec/package.json @@ -1,6 +1,6 @@ { "name": "@jridgewell/sourcemap-codec", - "version": "1.4.15", + "version": "1.4.16-beta.0", "description": "Encode/decode sourcemap mappings", "keywords": [ "sourcemap", diff --git a/packages/sourcemap-codec/src/scopes.ts b/packages/sourcemap-codec/src/scopes.ts new file mode 100644 index 0000000..d194c2f --- /dev/null +++ b/packages/sourcemap-codec/src/scopes.ts @@ -0,0 +1,345 @@ +import { StringReader, StringWriter } from './strings'; +import { comma, decodeInteger, encodeInteger, hasMoreVlq, semicolon } from './vlq'; + +const EMPTY: any[] = []; + +type Line = number; +type Column = number; +type Kind = number; +type Name = number; +type Var = number; +type SourcesIndex = number; +type ScopesIndex = number; + +type Mix = (A & O) | (B & O); + +export type OriginalScope = Mix< + [Line, Column, Line, Column, Kind], + [Line, Column, Line, Column, Kind, Name], + { vars: Var[] } +>; + +export type GeneratedRange = Mix< + [Line, Column, Line, Column], + [Line, Column, Line, Column, SourcesIndex, ScopesIndex], + { + callsite: CallSite | null; + bindings: Binding[]; + isScope: boolean; + } +>; +export type CallSite = [SourcesIndex, Line, Column]; +type Binding = BindingExpressionRange[]; +export type BindingExpressionRange = [Name] | [Name, Line, Column]; + +export function decodeOriginalScopes(input: string): OriginalScope[] { + const { length } = input; + const reader = new StringReader(input); + const scopes: OriginalScope[] = []; + const stack: OriginalScope[] = []; + let line = 0; + + for (; reader.pos < length; reader.pos++) { + line = decodeInteger(reader, line); + const column = decodeInteger(reader, 0); + + if (!hasMoreVlq(reader, length)) { + const last = stack.pop()!; + last[2] = line; + last[3] = column; + continue; + } + + const kind = decodeInteger(reader, 0); + const fields = decodeInteger(reader, 0); + const hasName = fields & 0b0001; + + const scope: OriginalScope = ( + hasName ? [line, column, 0, 0, kind, decodeInteger(reader, 0)] : [line, column, 0, 0, kind] + ) as OriginalScope; + + let vars: Var[] = EMPTY; + if (hasMoreVlq(reader, length)) { + vars = []; + do { + const varsIndex = decodeInteger(reader, 0); + vars.push(varsIndex); + } while (hasMoreVlq(reader, length)); + } + scope.vars = vars; + + scopes.push(scope); + stack.push(scope); + } + + return scopes; +} + +export function encodeOriginalScopes(scopes: OriginalScope[]): string { + const writer = new StringWriter(); + + for (let i = 0; i < scopes.length; ) { + i = _encodeOriginalScopes(scopes, i, writer, [0]); + } + + return writer.flush(); +} + +function _encodeOriginalScopes( + scopes: OriginalScope[], + index: number, + writer: StringWriter, + state: [ + number, // GenColumn + ], +): number { + const scope = scopes[index]; + const { 0: startLine, 1: startColumn, 2: endLine, 3: endColumn, 4: kind, vars } = scope; + + if (index > 0) writer.write(comma); + + state[0] = encodeInteger(writer, startLine, state[0]); + encodeInteger(writer, startColumn, 0); + encodeInteger(writer, kind, 0); + + const fields = scope.length === 6 ? 0b0001 : 0; + encodeInteger(writer, fields, 0); + if (scope.length === 6) encodeInteger(writer, scope[5], 0); + + for (const v of vars) { + encodeInteger(writer, v, 0); + } + + for (index++; index < scopes.length; ) { + const next = scopes[index]; + const { 0: l, 1: c } = next; + if (l > endLine || (l === endLine && c >= endColumn)) { + break; + } + index = _encodeOriginalScopes(scopes, index, writer, state); + } + + writer.write(comma); + state[0] = encodeInteger(writer, endLine, state[0]); + encodeInteger(writer, endColumn, 0); + + return index; +} + +export function decodeGeneratedRanges(input: string): GeneratedRange[] { + const { length } = input; + const reader = new StringReader(input); + const ranges: GeneratedRange[] = []; + const stack: GeneratedRange[] = []; + + let genLine = 0; + let definitionSourcesIndex = 0; + let definitionScopeIndex = 0; + let callsiteSourcesIndex = 0; + let callsiteLine = 0; + let callsiteColumn = 0; + let bindingLine = 0; + let bindingColumn = 0; + + do { + const semi = reader.indexOf(';'); + let genColumn = 0; + + for (; reader.pos < semi; reader.pos++) { + genColumn = decodeInteger(reader, genColumn); + + if (!hasMoreVlq(reader, semi)) { + const last = stack.pop()!; + last[2] = genLine; + last[3] = genColumn; + continue; + } + + const fields = decodeInteger(reader, 0); + const hasDefinition = fields & 0b0001; + const hasCallsite = fields & 0b0010; + const hasScope = fields & 0b0100; + + let callsite: CallSite | null = null; + let bindings: Binding[] = EMPTY; + let range: GeneratedRange; + if (hasDefinition) { + const defSourcesIndex = decodeInteger(reader, definitionSourcesIndex); + definitionScopeIndex = decodeInteger( + reader, + definitionSourcesIndex === defSourcesIndex ? definitionScopeIndex : 0, + ); + + definitionSourcesIndex = defSourcesIndex; + range = [genLine, genColumn, 0, 0, defSourcesIndex, definitionScopeIndex] as GeneratedRange; + } else { + range = [genLine, genColumn, 0, 0] as GeneratedRange; + } + + range.isScope = !!hasScope; + + if (hasCallsite) { + const prevCsi = callsiteSourcesIndex; + const prevLine = callsiteLine; + callsiteSourcesIndex = decodeInteger(reader, callsiteSourcesIndex); + const sameSource = prevCsi === callsiteSourcesIndex; + callsiteLine = decodeInteger(reader, sameSource ? callsiteLine : 0); + callsiteColumn = decodeInteger( + reader, + sameSource && prevLine === callsiteLine ? callsiteColumn : 0, + ); + + callsite = [callsiteSourcesIndex, callsiteLine, callsiteColumn]; + } + range.callsite = callsite; + + if (hasMoreVlq(reader, semi)) { + bindings = []; + do { + bindingLine = genLine; + bindingColumn = genColumn; + const expressionsCount = decodeInteger(reader, 0); + let expressionRanges: BindingExpressionRange[]; + if (expressionsCount < -1) { + expressionRanges = [[decodeInteger(reader, 0)]]; + for (let i = -1; i > expressionsCount; i--) { + const prevBl = bindingLine; + bindingLine = decodeInteger(reader, bindingLine); + bindingColumn = decodeInteger(reader, bindingLine === prevBl ? bindingColumn : 0); + const expression = decodeInteger(reader, 0); + expressionRanges.push([expression, bindingLine, bindingColumn]); + } + } else { + expressionRanges = [[expressionsCount]]; + } + bindings.push(expressionRanges); + } while (hasMoreVlq(reader, semi)); + } + range.bindings = bindings; + + ranges.push(range); + stack.push(range); + } + + genLine++; + reader.pos = semi + 1; + } while (reader.pos < length); + + return ranges; +} + +export function encodeGeneratedRanges(ranges: GeneratedRange[]): string { + if (ranges.length === 0) return ''; + + const writer = new StringWriter(); + + for (let i = 0; i < ranges.length; ) { + i = _encodeGeneratedRanges(ranges, i, writer, [0, 0, 0, 0, 0, 0, 0]); + } + + return writer.flush(); +} + +function _encodeGeneratedRanges( + ranges: GeneratedRange[], + index: number, + writer: StringWriter, + state: [ + number, // GenLine + number, // GenColumn + number, // DefSourcesIndex + number, // DefScopesIndex + number, // CallSourcesIndex + number, // CallLine + number, // CallColumn + ], +): number { + const range = ranges[index]; + const { + 0: startLine, + 1: startColumn, + 2: endLine, + 3: endColumn, + isScope, + callsite, + bindings, + } = range; + + if (state[0] < startLine) { + catchupLine(writer, state[0], startLine); + state[0] = startLine; + state[1] = 0; + } else if (index > 0) { + writer.write(comma); + } + + state[1] = encodeInteger(writer, range[1], state[1]); + + const fields = + (range.length === 6 ? 0b0001 : 0) | (callsite ? 0b0010 : 0) | (isScope ? 0b0100 : 0); + encodeInteger(writer, fields, 0); + + if (range.length === 6) { + const { 4: sourcesIndex, 5: scopesIndex } = range; + if (sourcesIndex !== state[2]) { + state[3] = 0; + } + state[2] = encodeInteger(writer, sourcesIndex, state[2]); + state[3] = encodeInteger(writer, scopesIndex, state[3]); + } + + if (callsite) { + const { 0: sourcesIndex, 1: callLine, 2: callColumn } = range.callsite!; + if (sourcesIndex !== state[4]) { + state[5] = 0; + state[6] = 0; + } else if (callLine !== state[5]) { + state[6] = 0; + } + state[4] = encodeInteger(writer, sourcesIndex, state[4]); + state[5] = encodeInteger(writer, callLine, state[5]); + state[6] = encodeInteger(writer, callColumn, state[6]); + } + + if (bindings) { + for (const binding of bindings) { + if (binding.length > 1) encodeInteger(writer, -binding.length, 0); + const expression = binding[0][0]; + encodeInteger(writer, expression, 0); + let bindingStartLine = startLine; + let bindingStartColumn = startColumn; + for (let i = 1; i < binding.length; i++) { + const expRange = binding[i]; + bindingStartLine = encodeInteger(writer, expRange[1]!, bindingStartLine); + bindingStartColumn = encodeInteger(writer, expRange[2]!, bindingStartColumn); + encodeInteger(writer, expRange[0]!, 0); + } + } + } + + for (index++; index < ranges.length; ) { + const next = ranges[index]; + const { 0: l, 1: c } = next; + if (l > endLine || (l === endLine && c >= endColumn)) { + break; + } + index = _encodeGeneratedRanges(ranges, index, writer, state); + } + + if (state[0] < endLine) { + catchupLine(writer, state[0], endLine); + state[0] = endLine; + state[1] = 0; + } else { + writer.write(comma); + } + state[1] = encodeInteger(writer, endColumn, state[1]); + + return index; +} + +function catchupLine(writer: StringWriter, lastLine: number, line: number) { + do { + writer.write(semicolon); + } while (++lastLine < line); +} diff --git a/packages/sourcemap-codec/src/sourcemap-codec.ts b/packages/sourcemap-codec/src/sourcemap-codec.ts index bd380bd..a81f894 100644 --- a/packages/sourcemap-codec/src/sourcemap-codec.ts +++ b/packages/sourcemap-codec/src/sourcemap-codec.ts @@ -1,15 +1,13 @@ -import { - decodeInteger, - encodeInteger, - comma, - semicolon, - hasMoreVlq, - posOut, - indexOf, - td, - maybeWrite, -} from './vlq'; -// export { decodeOriginalScopes } from './scopes'; +import { comma, decodeInteger, encodeInteger, hasMoreVlq, semicolon } from './vlq'; +import { StringWriter, StringReader } from './strings'; + +export { + decodeOriginalScopes, + encodeOriginalScopes, + decodeGeneratedRanges, + encodeGeneratedRanges, +} from './scopes'; +export type { OriginalScope, GeneratedRange, CallSite, BindingExpressionRange } from './scopes'; export type SourceMapSegment = | [number] @@ -19,6 +17,8 @@ export type SourceMapLine = SourceMapSegment[]; export type SourceMapMappings = SourceMapLine[]; export function decode(mappings: string): SourceMapMappings { + const { length } = mappings; + const reader = new StringReader(mappings); const decoded: SourceMapMappings = []; let genColumn = 0; let sourcesIndex = 0; @@ -26,28 +26,27 @@ export function decode(mappings: string): SourceMapMappings { let sourceColumn = 0; let namesIndex = 0; - let index = 0; do { - const semi = indexOf(mappings, ';', index); + const semi = reader.indexOf(';'); const line: SourceMapLine = []; let sorted = true; let lastCol = 0; genColumn = 0; - for (let i = index; i < semi; i = posOut + 1) { + while (reader.pos < semi) { let seg: SourceMapSegment; - genColumn = decodeInteger(mappings, i, genColumn); + genColumn = decodeInteger(reader, genColumn); if (genColumn < lastCol) sorted = false; lastCol = genColumn; - if (hasMoreVlq(mappings, posOut, semi)) { - sourcesIndex = decodeInteger(mappings, posOut, sourcesIndex); - sourceLine = decodeInteger(mappings, posOut, sourceLine); - sourceColumn = decodeInteger(mappings, posOut, sourceColumn); + if (hasMoreVlq(reader, semi)) { + sourcesIndex = decodeInteger(reader, sourcesIndex); + sourceLine = decodeInteger(reader, sourceLine); + sourceColumn = decodeInteger(reader, sourceColumn); - if (hasMoreVlq(mappings, posOut, semi)) { - namesIndex = decodeInteger(mappings, posOut, namesIndex); + if (hasMoreVlq(reader, semi)) { + namesIndex = decodeInteger(reader, namesIndex); seg = [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex]; } else { seg = [genColumn, sourcesIndex, sourceLine, sourceColumn]; @@ -57,12 +56,13 @@ export function decode(mappings: string): SourceMapMappings { } line.push(seg); + reader.pos++; } if (!sorted) sort(line); decoded.push(line); - index = semi + 1; - } while (index <= mappings.length); + reader.pos = semi + 1; + } while (reader.pos <= length); return decoded; } @@ -78,15 +78,7 @@ function sortComparator(a: SourceMapSegment, b: SourceMapSegment): number { export function encode(decoded: SourceMapMappings): string; export function encode(decoded: Readonly): string; export function encode(decoded: Readonly): string { - const bufLength = 1024 * 16; - // We can push up to 5 ints, each int can take at most 7 chars, and we - // may push a comma. - const subLength = bufLength - (7 * 5 + 1); - const buf = new Uint8Array(bufLength); - const sub = buf.subarray(0, subLength); - let pos = 0; - let out = ''; - let genColumn = 0; + const writer = new StringWriter(); let sourcesIndex = 0; let sourceLine = 0; let sourceColumn = 0; @@ -94,31 +86,26 @@ export function encode(decoded: Readonly): string { for (let i = 0; i < decoded.length; i++) { const line = decoded[i]; - out = maybeWrite(out, buf, pos, buf, bufLength); - pos = posOut; - if (i > 0) buf[pos++] = semicolon; - + if (i > 0) writer.write(semicolon); if (line.length === 0) continue; - genColumn = 0; + let genColumn = 0; - for (let j = 0; j < line.length; j++, pos = posOut) { + for (let j = 0; j < line.length; j++) { const segment = line[j]; - out = maybeWrite(out, sub, pos, buf, subLength); - pos = posOut; - if (j > 0) buf[pos++] = comma; + if (j > 0) writer.write(comma); - genColumn = encodeInteger(buf, pos, segment[0], genColumn); + genColumn = encodeInteger(writer, segment[0], genColumn); if (segment.length === 1) continue; - sourcesIndex = encodeInteger(buf, posOut, segment[1], sourcesIndex); - sourceLine = encodeInteger(buf, posOut, segment[2], sourceLine); - sourceColumn = encodeInteger(buf, posOut, segment[3], sourceColumn); + sourcesIndex = encodeInteger(writer, segment[1], sourcesIndex); + sourceLine = encodeInteger(writer, segment[2], sourceLine); + sourceColumn = encodeInteger(writer, segment[3], sourceColumn); if (segment.length === 4) continue; - namesIndex = encodeInteger(buf, posOut, segment[4], namesIndex); + namesIndex = encodeInteger(writer, segment[4], namesIndex); } } - return out + td.decode(buf.subarray(0, pos)); + return writer.flush(); } diff --git a/packages/sourcemap-codec/src/strings.ts b/packages/sourcemap-codec/src/strings.ts new file mode 100644 index 0000000..4ee16fa --- /dev/null +++ b/packages/sourcemap-codec/src/strings.ts @@ -0,0 +1,65 @@ +const bufLength = 1024 * 16; + +// Provide a fallback for older environments. +const td = + typeof TextDecoder !== 'undefined' + ? /* #__PURE__ */ new TextDecoder() + : typeof Buffer !== 'undefined' + ? { + decode(buf: Uint8Array): string { + const out = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength); + return out.toString(); + }, + } + : { + decode(buf: Uint8Array): string { + let out = ''; + for (let i = 0; i < buf.length; i++) { + out += String.fromCharCode(buf[i]); + } + return out; + }, + }; + +export class StringWriter { + pos = 0; + private out = ''; + private buffer = new Uint8Array(bufLength); + + write(v: number): void { + const { buffer } = this; + buffer[this.pos++] = v; + if (this.pos === bufLength) { + this.out += td.decode(buffer); + this.pos = 0; + } + } + + flush(): string { + const { buffer, out, pos } = this; + return pos > 0 ? out + td.decode(buffer.subarray(0, pos)) : out; + } +} + +export class StringReader { + pos = 0; + private declare buffer: string; + + constructor(buffer: string) { + this.buffer = buffer; + } + + next(): number { + return this.buffer.charCodeAt(this.pos++); + } + + peek(): number { + return this.buffer.charCodeAt(this.pos); + } + + indexOf(char: string): number { + const { buffer, pos } = this; + const idx = buffer.indexOf(char, pos); + return idx === -1 ? buffer.length : idx; + } +} diff --git a/packages/sourcemap-codec/src/vlq.ts b/packages/sourcemap-codec/src/vlq.ts index c88cda9..a42c681 100644 --- a/packages/sourcemap-codec/src/vlq.ts +++ b/packages/sourcemap-codec/src/vlq.ts @@ -1,3 +1,8 @@ +import type { StringReader, StringWriter } from './strings'; + +export const comma = ','.charCodeAt(0); +export const semicolon = ';'.charCodeAt(0); + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; const intToChar = new Uint8Array(64); // 64 possible chars. const charToInt = new Uint8Array(128); // z is 122 in ASCII @@ -8,27 +13,13 @@ for (let i = 0; i < chars.length; i++) { charToInt[c] = i; } -export const comma = ','.charCodeAt(0); -export const semicolon = ';'.charCodeAt(0); - -export function hasMoreVlq(mappings: string, i: number, length: number): boolean { - if (i >= length) return false; - return mappings.charCodeAt(i) !== comma; -} - -export function indexOf(mappings: string, char: string, index: number): number { - const idx = mappings.indexOf(char, index); - return idx === -1 ? mappings.length : idx; -} - -export let posOut = 0; -export function decodeInteger(mappings: string, pos: number, relative: number): number { +export function decodeInteger(reader: StringReader, relative: number): number { let value = 0; let shift = 0; let integer = 0; do { - const c = mappings.charCodeAt(pos++); + const c = reader.next(); integer = charToInt[c]; value |= (integer & 31) << shift; shift += 5; @@ -41,11 +32,10 @@ export function decodeInteger(mappings: string, pos: number, relative: number): value = -0x80000000 | -value; } - posOut = pos; return relative + value; } -export function encodeInteger(buf: Uint8Array, pos: number, num: number, relative: number): number { +export function encodeInteger(builder: StringWriter, num: number, relative: number): number { let delta = num - relative; delta = delta < 0 ? (-delta << 1) | 1 : delta << 1; @@ -53,47 +43,13 @@ export function encodeInteger(buf: Uint8Array, pos: number, num: number, relativ let clamped = delta & 0b011111; delta >>>= 5; if (delta > 0) clamped |= 0b100000; - buf[pos++] = intToChar[clamped]; + builder.write(intToChar[clamped]); } while (delta > 0); - posOut = pos; return num; } -export function maybeWrite( - build: string, - buf: Uint8Array, - pos: number, - copy: Uint8Array, - length: number, -): string { - if (pos < length) { - posOut = pos; - return build; - } - const out = td.decode(buf); - copy.copyWithin(0, length, pos); - posOut = pos - length; - return build + out; +export function hasMoreVlq(reader: StringReader, max: number) { + if (reader.pos >= max) return false; + return reader.peek() !== comma; } - -// Provide a fallback for older environments. -export const td = - typeof TextDecoder !== 'undefined' - ? /* #__PURE__ */ new TextDecoder() - : typeof Buffer !== 'undefined' - ? { - decode(buf: Uint8Array) { - const out = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength); - return out.toString(); - }, - } - : { - decode(buf: Uint8Array) { - let out = ''; - for (let i = 0; i < buf.length; i++) { - out += String.fromCharCode(buf[i]); - } - return out; - }, - }; diff --git a/packages/sourcemap-codec/test/codec.test.ts b/packages/sourcemap-codec/test/codec.test.ts index fe29a8b..cd174ef 100644 --- a/packages/sourcemap-codec/test/codec.test.ts +++ b/packages/sourcemap-codec/test/codec.test.ts @@ -1,6 +1,6 @@ /* eslint-env node, mocha */ -import { encode, decode, SourceMapMappings } from '../src/sourcemap-codec'; +import { encode, decode, type SourceMapMappings } from '../src/sourcemap-codec'; import { strict as assert } from 'assert'; import * as fs from 'fs'; import * as path from 'path'; diff --git a/packages/sourcemap-codec/test/scopes.test.ts b/packages/sourcemap-codec/test/scopes.test.ts new file mode 100644 index 0000000..9a4508d --- /dev/null +++ b/packages/sourcemap-codec/test/scopes.test.ts @@ -0,0 +1,224 @@ +/* eslint-env node, mocha */ + +import { + decodeGeneratedRanges, + decodeOriginalScopes, + encodeGeneratedRanges, + encodeOriginalScopes, +} from '../src/scopes'; +import { strict as assert } from 'assert'; +import type { GeneratedRange, OriginalScope } from '../src/scopes'; + +describe('scopes proposal', () => { + type Tuple = Pick>; + function init( + init: Tuple, + data: Pick>, + ): T { + return Object.assign(init, data) as T; + } + + describe('original scopes', () => { + let tests: { encoded: string[]; decoded: OriginalScope[][]; only?: true }[] = [ + { + encoded: ['AACAAC,AAECAE,EC,CAECCE,GC,E2C'], + decoded: [ + [ + init([0, 0, 8, 43, 1], { vars: [0, 1] }), + init([0, 0, 2, 1, 2, 0], { vars: [2] }), + init([3, 0, 6, 1, 2, 1], { vars: [2] }), + ], + ], + }, + { + encoded: ['AACAAC,GgBECEG,EG,CC', 'AACACIK,EkBECIG,EC,EkBECKG,EC,GY'], + decoded: [ + [init([0, 0, 6, 1, 1], { vars: [0, 1] }), init([3, 16, 5, 3, 2, 2], { vars: [3] })], + [ + init([0, 0, 11, 12, 1], { vars: [1, 4, 5] }), + init([2, 18, 4, 1, 2, 4], { vars: [3] }), + init([6, 18, 8, 1, 2, 5], { vars: [3] }), + ], + ], + }, + { + encoded: ['AACAAC,GmB', 'AACAEA,CqBECAG,EC,AC'], + decoded: [ + [init([0, 0, 3, 19, 1], { vars: [0, 1] })], + [init([0, 0, 3, 1, 1], { vars: [2, 0] }), init([1, 21, 3, 1, 2, 0], { vars: [3] })], + ], + }, + { + encoded: ['AACAACEG,EgBECCI,EC,EkBECEI,EC,EkBECGIK,GkBIA,EG,CC,GY'], + decoded: [ + [ + init([0, 0, 19, 12, 1], { vars: [0, 1, 2, 3] }), + init([2, 16, 4, 1, 2, 1], { vars: [4] }), + init([6, 18, 8, 1, 2, 2], { vars: [4] }), + init([10, 18, 16, 1, 2, 3], { vars: [4, 5] }), + init([13, 18, 15, 3, 4], { vars: [] }), + ], + ], + }, + { + encoded: ['AACAAC,AkBECAE,EC,IO'], + decoded: [ + [init([0, 0, 6, 7, 1], { vars: [0, 1] }), init([0, 18, 2, 1, 2, 0], { vars: [2] })], + ], + }, + { + encoded: ['AACA,AAIAA,GEIAA,GG,EC,AC'], + decoded: [ + [ + init([0, 0, 8, 1, 1], { vars: [] }), + init([0, 0, 8, 1, 4], { vars: [0] }), + init([3, 2, 6, 3, 4], { vars: [0] }), + ], + ], + }, + { + encoded: ['AACAA,AAECACEG,CEECCIK,GG,GC,CS'], + decoded: [ + [ + init([0, 0, 8, 9, 1], { vars: [0] }), + init([0, 0, 7, 1, 2, 0], { vars: [1, 2, 3] }), + init([1, 2, 4, 3, 2, 1], { vars: [4, 5] }), + ], + ], + }, + ]; + + const filtered = tests.filter((test) => { + return test.only; + }); + + tests = filtered.length ? filtered : tests; + + describe('decodeOriginalScopes()', () => { + tests.forEach((test, i) => { + test.encoded.forEach((encoded, j) => { + it(`decodes sample ${i}, source ${j}`, () => { + assert.deepEqual(decodeOriginalScopes(encoded), test.decoded[j]); + }); + }); + }); + }); + + describe('encodeOriginalScopes()', () => { + tests.forEach((test, i) => { + test.decoded.forEach((decoded, j) => { + it(`encodes sample ${i}, source ${j}`, () => { + assert.deepEqual(encodeOriginalScopes(decoded), test.encoded[j]); + }); + }); + }); + }); + }); + + describe('generated ranges', () => { + let tests: { encoded: string; decoded: GeneratedRange[]; only?: true }[] = [ + { + encoded: 'AKAADD,AGAEAOAG,AGADAHEI,2B;8B;2C', + decoded: [ + init([0, 0, 2, 43, 0, 0], { isScope: true, callsite: null, bindings: [[[-1]], [[-1]]] }), + init([0, 0, 1, 30, 0, 2], { isScope: false, callsite: [0, 7, 0], bindings: [[[3]]] }), + init([0, 0, 0, 27, 0, 1], { isScope: false, callsite: [0, 4, 2], bindings: [[[4]]] }), + ], + }, + { + encoded: + 'AKCADDD,ACDAMD,AGCECUAO,AGADAHEO,AGDCAJEO,gB,A,A,A,ACADMD,AGCEAQAQ,AGADAJEQ,AGDCAJEQ,kB,A,A,A,A', + decoded: [ + init([0, 0, 0, 34, 1, 0], { + isScope: true, + callsite: null, + bindings: [[[-1]], [[-1]], [[-1]]], + }), + init([0, 0, 0, 16, 0, 0], { isScope: false, callsite: null, bindings: [[[6]], [[-1]]] }), + init([0, 0, 0, 16, 1, 2], { isScope: false, callsite: [1, 10, 0], bindings: [[[7]]] }), + init([0, 0, 0, 16, 1, 1], { isScope: false, callsite: [1, 7, 2], bindings: [[[7]]] }), + init([0, 0, 0, 16, 0, 1], { isScope: false, callsite: [1, 3, 2], bindings: [[[7]]] }), + init([0, 16, 0, 34, 0, 0], { isScope: false, callsite: null, bindings: [[[6]], [[-1]]] }), + init([0, 16, 0, 34, 1, 2], { isScope: false, callsite: [1, 11, 0], bindings: [[[8]]] }), + init([0, 16, 0, 34, 1, 1], { isScope: false, callsite: [1, 7, 2], bindings: [[[8]]] }), + init([0, 16, 0, 34, 0, 1], { isScope: false, callsite: [1, 3, 2], bindings: [[[8]]] }), + ], + }, + { + encoded: 'AKAADI,ACCAKD;;AGACAEAM;;qB;iB,A', + decoded: [ + init([0, 0, 5, 17, 0, 0], { isScope: true, callsite: null, bindings: [[[-1]], [[4]]] }), + init([0, 0, 5, 17, 1, 0], { isScope: false, callsite: null, bindings: [[[5]], [[-1]]] }), + init([2, 0, 4, 21, 1, 1], { isScope: false, callsite: [0, 2, 0], bindings: [[[6]]] }), + ], + }, + { + encoded: 'AKAAMDDD,aKAGOQ,yDGADAcIO,AGADAPEO,c,A,C,c', + decoded: [ + init([0, 0, 0, 99, 0, 0], { + isScope: true, + callsite: null, + bindings: [[[6]], [[-1]], [[-1]], [[-1]]], + }), + init([0, 13, 0, 85, 0, 3], { isScope: true, callsite: null, bindings: [[[7]], [[8]]] }), + init([0, 70, 0, 84, 0, 2], { isScope: false, callsite: [0, 14, 4], bindings: [[[7]]] }), + init([0, 70, 0, 84, 0, 1], { isScope: false, callsite: [0, 7, 2], bindings: [[[7]]] }), + ], + }, + { + encoded: 'AKAADFGCAI,AGACAICG,mB;AGAAAEAI,mB,A', + decoded: [ + init([0, 0, 1, 19, 0, 0], { + isScope: true, + callsite: null, + bindings: [[[-1]], [[3], [4, 1, 0]]], + }), + init([0, 0, 0, 19, 0, 1], { isScope: false, callsite: [0, 4, 1], bindings: [[[3]]] }), + init([1, 0, 1, 19, 0, 1], { isScope: false, callsite: [0, 6, 0], bindings: [[[4]]] }), + ], + }, + { + encoded: 'AKAA,AKACC;;;ECACE;kB;;C,A', + decoded: [ + init([0, 0, 6, 1, 0, 0], { isScope: true, callsite: null, bindings: [] }), + init([0, 0, 6, 1, 0, 1], { isScope: true, callsite: null, bindings: [[[1]]] }), + init([3, 2, 4, 18, 0, 2], { isScope: false, callsite: null, bindings: [[[2]]] }), + ], + }, + { + encoded: 'AKAAM,AKACOQS;EKACQS;;;G;;;C;K', + decoded: [ + init([0, 0, 8, 5, 0, 0], { isScope: true, callsite: null, bindings: [[[6]]] }), + init([0, 0, 7, 1, 0, 1], { + isScope: true, + callsite: null, + bindings: [[[7]], [[8]], [[9]]], + }), + init([1, 2, 4, 3, 0, 2], { isScope: true, callsite: null, bindings: [[[8]], [[9]]] }), + ], + }, + ]; + + const filtered = tests.filter((test) => { + return test.only; + }); + + tests = filtered.length ? filtered : tests; + + describe('decodeGeneratedRanges()', () => { + tests.forEach((test, i) => { + it('decodes sample ' + i, () => { + assert.deepEqual(decodeGeneratedRanges(test.encoded), test.decoded); + }); + }); + }); + + describe('encodeGeneratedRanges()', () => { + tests.forEach((test, i) => { + it('encodes sample ' + i, () => { + assert.deepEqual(encodeGeneratedRanges(test.decoded), test.encoded); + }); + }); + }); + }); +});