diff --git a/packages/gen-mapping/package.json b/packages/gen-mapping/package.json index 036f9b7..bccceea 100644 --- a/packages/gen-mapping/package.json +++ b/packages/gen-mapping/package.json @@ -47,6 +47,7 @@ "test:format": "prettier --check '{src,test}/**/*.ts'", "test:only": "mocha", "test:types": "eslint '{src,test}/**/*.ts'", + "test:watch": "mocha --watch --watch-files 'src/**/*.ts,test/**/*.ts'", "lint": "run-s -n lint:types lint:format", "lint:format": "npm run test:format -- --write", "lint:types": "npm run test:types -- --fix", diff --git a/packages/remapping/package.json b/packages/remapping/package.json index ed00441..c1552ed 100644 --- a/packages/remapping/package.json +++ b/packages/remapping/package.json @@ -48,6 +48,7 @@ "test:format": "prettier --check '{src,test}/**/*.ts'", "test:only": "mocha", "test:types": "eslint '{src,test}/**/*.ts'", + "test:watch": "mocha --watch --watch-files 'src/**/*.ts,test/**/*.ts'", "lint": "run-s -n lint:types lint:format", "lint:format": "npm run test:format -- --write", "lint:types": "npm run test:types -- --fix", diff --git a/packages/source-map/package.json b/packages/source-map/package.json index 03ca7df..41d788a 100644 --- a/packages/source-map/package.json +++ b/packages/source-map/package.json @@ -48,6 +48,7 @@ "test:format": "prettier --check '{src,test}/**/*.ts'", "test:only": "mocha", "test:types": "eslint '{src,test}/**/*.ts'", + "test:watch": "mocha --watch --watch-files 'src/**/*.ts,test/**/*.ts'", "lint": "run-s -n lint:types lint:format", "lint:format": "npm run test:format -- --write", "lint:types": "npm run test:types -- --fix", diff --git a/packages/sourcemap-codec/package.json b/packages/sourcemap-codec/package.json index da55137..0001bd2 100644 --- a/packages/sourcemap-codec/package.json +++ b/packages/sourcemap-codec/package.json @@ -47,6 +47,7 @@ "test:format": "prettier --check '{src,test}/**/*.ts'", "test:only": "mocha", "test:types": "eslint '{src,test}/**/*.ts'", + "test:watch": "mocha --watch --watch-files 'src/**/*.ts,test/**/*.ts'", "lint": "run-s -n lint:types lint:format", "lint:format": "npm run test:format -- --write", "lint:types": "npm run test:types -- --fix", @@ -60,4 +61,4 @@ }, "author": "Justin Ridgewell ", "license": "MIT" -} +} \ No newline at end of file diff --git a/packages/sourcemap-codec/src/range-mappings.ts b/packages/sourcemap-codec/src/range-mappings.ts new file mode 100644 index 0000000..4648326 --- /dev/null +++ b/packages/sourcemap-codec/src/range-mappings.ts @@ -0,0 +1,47 @@ +import { StringReader, StringWriter } from './strings'; +import { decodeInteger, encodeInteger, semicolon } from './vlq'; + +export type MappingIndex = number; +export type RangeMappings = MappingIndex[][]; + +export function decodeRangeMappings(input: string): RangeMappings { + const { length } = input; + const reader = new StringReader(input); + const rangeMappings: RangeMappings = []; + + do { + const semi = reader.indexOf(';'); + const indices: MappingIndex[] = []; + let index = -1; + + while (reader.pos < semi) { + index += decodeInteger(reader); + indices.push(index); + } + + rangeMappings.push(indices); + reader.pos = semi + 1; + } while (reader.pos <= length); + + return rangeMappings; +} + +export function encodeRangeMappings(decoded: RangeMappings): string { + if (decoded.length === 0) return ''; + + const writer = new StringWriter(); + + for (let i = 0; i < decoded.length; i++) { + const indices = decoded[i]; + if (i > 0) writer.write(semicolon); + + let index = -1; + for (let j = 0; j < indices.length; j++) { + const offset = indices[j]; + encodeInteger(writer, offset - index); + index = offset; + } + } + + return writer.flush(); +} diff --git a/packages/sourcemap-codec/src/scopes.ts b/packages/sourcemap-codec/src/scopes.ts index d194c2f..9e8d4d5 100644 --- a/packages/sourcemap-codec/src/scopes.ts +++ b/packages/sourcemap-codec/src/scopes.ts @@ -1,5 +1,13 @@ import { StringReader, StringWriter } from './strings'; -import { comma, decodeInteger, encodeInteger, hasMoreVlq, semicolon } from './vlq'; +import { + comma, + decodeInteger, + decodeSign, + encodeInteger, + encodeSign, + hasMoreVlq, + semicolon, +} from './vlq'; const EMPTY: any[] = []; @@ -40,8 +48,8 @@ export function decodeOriginalScopes(input: string): OriginalScope[] { let line = 0; for (; reader.pos < length; reader.pos++) { - line = decodeInteger(reader, line); - const column = decodeInteger(reader, 0); + line += decodeSign(decodeInteger(reader)); + const column = decodeSign(decodeInteger(reader)); if (!hasMoreVlq(reader, length)) { const last = stack.pop()!; @@ -50,19 +58,21 @@ export function decodeOriginalScopes(input: string): OriginalScope[] { continue; } - const kind = decodeInteger(reader, 0); - const fields = decodeInteger(reader, 0); + const kind = decodeSign(decodeInteger(reader)); + const fields = decodeSign(decodeInteger(reader)); const hasName = fields & 0b0001; const scope: OriginalScope = ( - hasName ? [line, column, 0, 0, kind, decodeInteger(reader, 0)] : [line, column, 0, 0, kind] + hasName + ? [line, column, 0, 0, kind, decodeSign(decodeInteger(reader))] + : [line, column, 0, 0, kind] ) as OriginalScope; let vars: Var[] = EMPTY; if (hasMoreVlq(reader, length)) { vars = []; do { - const varsIndex = decodeInteger(reader, 0); + const varsIndex = decodeSign(decodeInteger(reader)); vars.push(varsIndex); } while (hasMoreVlq(reader, length)); } @@ -98,16 +108,17 @@ function _encodeOriginalScopes( if (index > 0) writer.write(comma); - state[0] = encodeInteger(writer, startLine, state[0]); - encodeInteger(writer, startColumn, 0); - encodeInteger(writer, kind, 0); + encodeInteger(writer, encodeSign(startLine - state[0])); + state[0] = startLine; + encodeInteger(writer, encodeSign(startColumn)); + encodeInteger(writer, encodeSign(kind)); const fields = scope.length === 6 ? 0b0001 : 0; - encodeInteger(writer, fields, 0); - if (scope.length === 6) encodeInteger(writer, scope[5], 0); + encodeInteger(writer, encodeSign(fields)); + if (scope.length === 6) encodeInteger(writer, encodeSign(scope[5])); for (const v of vars) { - encodeInteger(writer, v, 0); + encodeInteger(writer, encodeSign(v)); } for (index++; index < scopes.length; ) { @@ -120,8 +131,9 @@ function _encodeOriginalScopes( } writer.write(comma); - state[0] = encodeInteger(writer, endLine, state[0]); - encodeInteger(writer, endColumn, 0); + encodeInteger(writer, encodeSign(endLine - state[0])); + state[0] = endLine; + encodeInteger(writer, encodeSign(endColumn)); return index; } @@ -146,7 +158,7 @@ export function decodeGeneratedRanges(input: string): GeneratedRange[] { let genColumn = 0; for (; reader.pos < semi; reader.pos++) { - genColumn = decodeInteger(reader, genColumn); + genColumn += decodeSign(decodeInteger(reader)); if (!hasMoreVlq(reader, semi)) { const last = stack.pop()!; @@ -155,7 +167,7 @@ export function decodeGeneratedRanges(input: string): GeneratedRange[] { continue; } - const fields = decodeInteger(reader, 0); + const fields = decodeSign(decodeInteger(reader)); const hasDefinition = fields & 0b0001; const hasCallsite = fields & 0b0010; const hasScope = fields & 0b0100; @@ -164,11 +176,10 @@ export function decodeGeneratedRanges(input: string): GeneratedRange[] { let bindings: Binding[] = EMPTY; let range: GeneratedRange; if (hasDefinition) { - const defSourcesIndex = decodeInteger(reader, definitionSourcesIndex); - definitionScopeIndex = decodeInteger( - reader, - definitionSourcesIndex === defSourcesIndex ? definitionScopeIndex : 0, - ); + const defSourcesIndex = definitionSourcesIndex + decodeSign(decodeInteger(reader)); + definitionScopeIndex = + decodeSign(decodeInteger(reader)) + + (definitionSourcesIndex === defSourcesIndex ? definitionScopeIndex : 0); definitionSourcesIndex = defSourcesIndex; range = [genLine, genColumn, 0, 0, defSourcesIndex, definitionScopeIndex] as GeneratedRange; @@ -181,13 +192,12 @@ export function decodeGeneratedRanges(input: string): GeneratedRange[] { if (hasCallsite) { const prevCsi = callsiteSourcesIndex; const prevLine = callsiteLine; - callsiteSourcesIndex = decodeInteger(reader, callsiteSourcesIndex); + callsiteSourcesIndex += decodeSign(decodeInteger(reader)); const sameSource = prevCsi === callsiteSourcesIndex; - callsiteLine = decodeInteger(reader, sameSource ? callsiteLine : 0); - callsiteColumn = decodeInteger( - reader, - sameSource && prevLine === callsiteLine ? callsiteColumn : 0, - ); + callsiteLine = (sameSource ? callsiteLine : 0) + decodeSign(decodeInteger(reader)); + callsiteColumn = + (sameSource && prevLine === callsiteLine ? callsiteColumn : 0) + + decodeSign(decodeInteger(reader)); callsite = [callsiteSourcesIndex, callsiteLine, callsiteColumn]; } @@ -198,15 +208,16 @@ export function decodeGeneratedRanges(input: string): GeneratedRange[] { do { bindingLine = genLine; bindingColumn = genColumn; - const expressionsCount = decodeInteger(reader, 0); + const expressionsCount = decodeSign(decodeInteger(reader)); let expressionRanges: BindingExpressionRange[]; if (expressionsCount < -1) { - expressionRanges = [[decodeInteger(reader, 0)]]; + expressionRanges = [[decodeSign(decodeInteger(reader))]]; 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); + bindingLine += decodeSign(decodeInteger(reader)); + bindingColumn = + (bindingLine === prevBl ? bindingColumn : 0) + decodeSign(decodeInteger(reader)); + const expression = decodeSign(decodeInteger(reader)); expressionRanges.push([expression, bindingLine, bindingColumn]); } } else { @@ -273,19 +284,22 @@ function _encodeGeneratedRanges( writer.write(comma); } - state[1] = encodeInteger(writer, range[1], state[1]); + encodeInteger(writer, encodeSign(range[1] - state[1])); + state[1] = range[1]; const fields = (range.length === 6 ? 0b0001 : 0) | (callsite ? 0b0010 : 0) | (isScope ? 0b0100 : 0); - encodeInteger(writer, fields, 0); + encodeInteger(writer, encodeSign(fields)); 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]); + encodeInteger(writer, encodeSign(sourcesIndex - state[2])); + state[2] = sourcesIndex; + encodeInteger(writer, encodeSign(scopesIndex - state[3])); + state[3] = scopesIndex; } if (callsite) { @@ -296,23 +310,28 @@ function _encodeGeneratedRanges( } 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]); + encodeInteger(writer, encodeSign(sourcesIndex - state[4])); + state[4] = sourcesIndex; + encodeInteger(writer, encodeSign(callLine - state[5])); + state[5] = callLine; + encodeInteger(writer, encodeSign(callColumn - state[6])); + state[6] = callColumn; } if (bindings) { for (const binding of bindings) { - if (binding.length > 1) encodeInteger(writer, -binding.length, 0); + if (binding.length > 1) encodeInteger(writer, encodeSign(-binding.length)); const expression = binding[0][0]; - encodeInteger(writer, expression, 0); + encodeInteger(writer, encodeSign(expression)); 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); + encodeInteger(writer, encodeSign(expRange[1]! - bindingStartLine)); + bindingStartLine = expRange[1]!; + encodeInteger(writer, encodeSign(expRange[2]! - bindingStartColumn)); + bindingStartColumn = expRange[2]!; + encodeInteger(writer, encodeSign(expRange[0]!)); } } } @@ -333,7 +352,8 @@ function _encodeGeneratedRanges( } else { writer.write(comma); } - state[1] = encodeInteger(writer, endColumn, state[1]); + encodeInteger(writer, encodeSign(endColumn - state[1])); + state[1] = endColumn; return index; } diff --git a/packages/sourcemap-codec/src/sourcemap-codec.ts b/packages/sourcemap-codec/src/sourcemap-codec.ts index a81f894..dd02c6f 100644 --- a/packages/sourcemap-codec/src/sourcemap-codec.ts +++ b/packages/sourcemap-codec/src/sourcemap-codec.ts @@ -1,4 +1,12 @@ -import { comma, decodeInteger, encodeInteger, hasMoreVlq, semicolon } from './vlq'; +import { + comma, + decodeInteger, + decodeSign, + encodeInteger, + encodeSign, + hasMoreVlq, + semicolon, +} from './vlq'; import { StringWriter, StringReader } from './strings'; export { @@ -9,6 +17,9 @@ export { } from './scopes'; export type { OriginalScope, GeneratedRange, CallSite, BindingExpressionRange } from './scopes'; +export { decodeRangeMappings, encodeRangeMappings } from './range-mappings'; +export type { MappingIndex, RangeMappings } from './range-mappings'; + export type SourceMapSegment = | [number] | [number, number, number, number] @@ -36,17 +47,17 @@ export function decode(mappings: string): SourceMapMappings { while (reader.pos < semi) { let seg: SourceMapSegment; - genColumn = decodeInteger(reader, genColumn); + genColumn += decodeSign(decodeInteger(reader)); if (genColumn < lastCol) sorted = false; lastCol = genColumn; if (hasMoreVlq(reader, semi)) { - sourcesIndex = decodeInteger(reader, sourcesIndex); - sourceLine = decodeInteger(reader, sourceLine); - sourceColumn = decodeInteger(reader, sourceColumn); + sourcesIndex += decodeSign(decodeInteger(reader)); + sourceLine += decodeSign(decodeInteger(reader)); + sourceColumn += decodeSign(decodeInteger(reader)); if (hasMoreVlq(reader, semi)) { - namesIndex = decodeInteger(reader, namesIndex); + namesIndex += decodeSign(decodeInteger(reader)); seg = [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex]; } else { seg = [genColumn, sourcesIndex, sourceLine, sourceColumn]; @@ -95,15 +106,20 @@ export function encode(decoded: Readonly): string { const segment = line[j]; if (j > 0) writer.write(comma); - genColumn = encodeInteger(writer, segment[0], genColumn); + encodeInteger(writer, encodeSign(segment[0] - genColumn)); + genColumn = segment[0]; if (segment.length === 1) continue; - sourcesIndex = encodeInteger(writer, segment[1], sourcesIndex); - sourceLine = encodeInteger(writer, segment[2], sourceLine); - sourceColumn = encodeInteger(writer, segment[3], sourceColumn); + encodeInteger(writer, encodeSign(segment[1] - sourcesIndex)); + encodeInteger(writer, encodeSign(segment[2] - sourceLine)); + encodeInteger(writer, encodeSign(segment[3] - sourceColumn)); + sourcesIndex = segment[1]; + sourceLine = segment[2]; + sourceColumn = segment[3]; if (segment.length === 4) continue; - namesIndex = encodeInteger(writer, segment[4], namesIndex); + encodeInteger(writer, encodeSign(segment[4] - namesIndex)); + namesIndex = segment[4]; } } diff --git a/packages/sourcemap-codec/src/vlq.ts b/packages/sourcemap-codec/src/vlq.ts index a42c681..e2f047e 100644 --- a/packages/sourcemap-codec/src/vlq.ts +++ b/packages/sourcemap-codec/src/vlq.ts @@ -13,7 +13,7 @@ for (let i = 0; i < chars.length; i++) { charToInt[c] = i; } -export function decodeInteger(reader: StringReader, relative: number): number { +export function decodeInteger(reader: StringReader): number { let value = 0; let shift = 0; let integer = 0; @@ -25,28 +25,24 @@ export function decodeInteger(reader: StringReader, relative: number): number { shift += 5; } while (integer & 32); - const shouldNegate = value & 1; - value >>>= 1; - - if (shouldNegate) { - value = -0x80000000 | -value; - } - - return relative + value; + return value; } -export function encodeInteger(builder: StringWriter, num: number, relative: number): number { - let delta = num - relative; +export function decodeSign(num: number): number { + return num & 1 ? -0x80000000 | -(num >>> 1) : num >>> 1; +} - delta = delta < 0 ? (-delta << 1) | 1 : delta << 1; +export function encodeInteger(builder: StringWriter, num: number) { do { - let clamped = delta & 0b011111; - delta >>>= 5; - if (delta > 0) clamped |= 0b100000; + let clamped = num & 0b011111; + num >>>= 5; + if (num > 0) clamped |= 0b100000; builder.write(intToChar[clamped]); - } while (delta > 0); + } while (num > 0); +} - return num; +export function encodeSign(num: number): number { + return num < 0 ? (-num << 1) | 1 : num << 1; } export function hasMoreVlq(reader: StringReader, max: number) { diff --git a/packages/sourcemap-codec/test/range-mappings.test.ts b/packages/sourcemap-codec/test/range-mappings.test.ts new file mode 100644 index 0000000..8158f23 --- /dev/null +++ b/packages/sourcemap-codec/test/range-mappings.test.ts @@ -0,0 +1,55 @@ +import { + decodeRangeMappings, + encodeRangeMappings, + type RangeMappings, +} from '../src/range-mappings'; +import assert from 'node:assert/strict'; + +describe('range mappings proposal', () => { + describe('encoding', () => { + let tests: { encoded: string; decoded: RangeMappings; only?: true }[] = [ + { + encoded: '', + decoded: [[]], + }, + { + encoded: 'B', + decoded: [[0]], + }, + { + encoded: ';BC;', + decoded: [[], [0, 2], []], + }, + { + encoded: 'C;BC;E', + decoded: [[1], [0, 2], [3]], + }, + { + encoded: 'nF;BPP;9I', + decoded: [[166], [0, 15, 30], [284]], + }, + ]; + + const filtered = tests.filter((test) => { + return test.only; + }); + + tests = filtered.length ? filtered : tests; + + describe('decodeRangeMappings()', () => { + tests.forEach((test, i) => { + it('decodes sample ' + i, () => { + assert.deepEqual(decodeRangeMappings(test.encoded), test.decoded); + }); + }); + }); + + describe('encodeRangeMappings()', () => { + tests.forEach((test, i) => { + it('encodes sample ' + i, () => { + assert.deepEqual(encodeRangeMappings(test.decoded), test.encoded); + }); + }); + }); + }); +}); diff --git a/packages/trace-mapping/package.json b/packages/trace-mapping/package.json index 9d3a1c0..36fafaa 100644 --- a/packages/trace-mapping/package.json +++ b/packages/trace-mapping/package.json @@ -47,6 +47,7 @@ "test:format": "prettier --check '{src,test}/**/*.ts'", "test:only": "mocha", "test:types": "eslint '{src,test}/**/*.ts'", + "test:watch": "mocha --watch --watch-files 'src/**/*.ts,test/**/*.ts'", "lint": "run-s -n lint:types lint:format", "lint:format": "npm run test:format -- --write", "lint:types": "npm run test:types -- --fix", diff --git a/turbo.json b/turbo.json index 768e6e8..d4b19a1 100644 --- a/turbo.json +++ b/turbo.json @@ -12,6 +12,17 @@ "inputs": ["../../prettier.config.js", "../../tsconfig.json", "src/**"], "outputs": ["src/**"] }, + "lint:format": { + "inputs": [ + "../../prettier.config.js", + "src/**", + "test/**" + ], + "outputs": [ + "src/**", + "test/**" + ] + }, "test": { "dependsOn": ["^build"] }