diff --git a/src/remapping.ts b/src/remapping.ts index 710ffd0..23d6238 100644 --- a/src/remapping.ts +++ b/src/remapping.ts @@ -5,7 +5,8 @@ import SourceMap from './source-map'; import type { SourceMapInput, SourceMapLoader, Options } from './types'; export type { SourceMapSegment, - RawSourceMap, + EncodedSourceMap, + EncodedSourceMap as RawSourceMap, DecodedSourceMap, SourceMapInput, SourceMapLoader, diff --git a/src/source-map-tree.ts b/src/source-map-tree.ts index 876d312..78e3b21 100644 --- a/src/source-map-tree.ts +++ b/src/source-map-tree.ts @@ -15,7 +15,106 @@ type MappingSource = SourceMapSegmentObject | typeof INVALID_MAPPING | typeof SO * traceMappings is only called on the root level SourceMapTree, and begins the process of * resolving each mapping in terms of the original source files. */ -export let traceMappings: (tree: SourceMapTree) => TraceMap; +export function traceMappings(tree: SourceMapTree): TraceMap { + const mappings: SourceMapSegment[][] = []; + const names = new FastStringArray(); + const sources = new FastStringArray(); + const sourcesContent: (string | null)[] = []; + const { sources: rootSources, map } = tree; + const rootNames = map.names; + const rootMappings = decodedMappings(map); + + let lastLineWithSegment = -1; + for (let i = 0; i < rootMappings.length; i++) { + const segments = rootMappings[i]; + const tracedSegments: SourceMapSegment[] = []; + + let lastSourcesIndex = -1; + let lastSourceLine = -1; + let lastSourceColumn = -1; + + for (let j = 0; j < segments.length; j++) { + const segment = segments[j]; + + let traced: MappingSource = SOURCELESS_MAPPING; + // 1-length segments only move the current generated column, there's no source information + // to gather from it. + if (segment.length !== 1) { + const source = rootSources[segment[1]]; + traced = source.originalPositionFor( + segment[2], + segment[3], + segment.length === 5 ? rootNames[segment[4]] : '' + ); + + // If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a + // respective segment into an original source. + if (traced === INVALID_MAPPING) continue; + } + + const genCol = segment[0]; + if (traced === SOURCELESS_MAPPING) { + if (lastSourcesIndex === -1) { + // This is a consecutive source-less segment, which doesn't carry any new information. + continue; + } + lastSourcesIndex = lastSourceLine = lastSourceColumn = -1; + tracedSegments.push([genCol]); + continue; + } + + // So we traced a segment down into its original source file. Now push a + // new segment pointing to this location. + const { column, line, name, content, source } = traced; + + // Store the source location, and ensure we keep sourcesContent up to + // date with the sources array. + const sourcesIndex = put(sources, source); + sourcesContent[sourcesIndex] = content; + + if ( + lastSourcesIndex === sourcesIndex && + lastSourceLine === line && + lastSourceColumn === column + ) { + // This is a duplicate mapping pointing at the exact same starting point in the source + // file. It doesn't carry any new information, and only bloats the sourcemap. + continue; + } + lastLineWithSegment = i; + lastSourcesIndex = sourcesIndex; + lastSourceLine = line; + lastSourceColumn = column; + + // This looks like unnecessary duplication, but it noticeably increases performance. If we + // were to push the nameIndex onto length-4 array, v8 would internally allocate 22 slots! + // That's 68 wasted bytes! Array literals have the same capacity as their length, saving + // memory. + tracedSegments.push( + name + ? [genCol, sourcesIndex, line, column, put(names, name)] + : [genCol, sourcesIndex, line, column] + ); + } + + mappings.push(tracedSegments); + } + + if (mappings.length > lastLineWithSegment + 1) { + mappings.length = lastLineWithSegment + 1; + } + + return presortedDecodedMap( + Object.assign({}, tree.map, { + mappings, + // TODO: Make all sources relative to the sourceRoot. + sourceRoot: undefined, + names: names.array, + sources: sources.array, + sourcesContent, + }) + ); +} /** * SourceMapTree represents a single sourcemap, with the ability to trace @@ -30,109 +129,6 @@ export class SourceMapTree { this.sources = sources; } - static { - traceMappings = (tree) => { - const mappings: SourceMapSegment[][] = []; - const names = new FastStringArray(); - const sources = new FastStringArray(); - const sourcesContent: (string | null)[] = []; - const { sources: rootSources, map } = tree; - const rootNames = map.names; - const rootMappings = decodedMappings(map); - - let lastLineWithSegment = -1; - for (let i = 0; i < rootMappings.length; i++) { - const segments = rootMappings[i]; - const tracedSegments: SourceMapSegment[] = []; - - let lastSourcesIndex = -1; - let lastSourceLine = -1; - let lastSourceColumn = -1; - - for (let j = 0; j < segments.length; j++) { - const segment = segments[j]; - - let traced: MappingSource = SOURCELESS_MAPPING; - // 1-length segments only move the current generated column, there's no source information - // to gather from it. - if (segment.length !== 1) { - const source = rootSources[segment[1]]; - traced = source.originalPositionFor( - segment[2], - segment[3], - segment.length === 5 ? rootNames[segment[4]] : '' - ); - - // If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a - // respective segment into an original source. - if (traced === INVALID_MAPPING) continue; - } - - const genCol = segment[0]; - if (traced === SOURCELESS_MAPPING) { - if (lastSourcesIndex === -1) { - // This is a consecutive source-less segment, which doesn't carry any new information. - continue; - } - lastSourcesIndex = lastSourceLine = lastSourceColumn = -1; - tracedSegments.push([genCol]); - continue; - } - - // So we traced a segment down into its original source file. Now push a - // new segment pointing to this location. - const { column, line, name, content, source } = traced; - - // Store the source location, and ensure we keep sourcesContent up to - // date with the sources array. - const sourcesIndex = put(sources, source); - sourcesContent[sourcesIndex] = content; - - if ( - lastSourcesIndex === sourcesIndex && - lastSourceLine === line && - lastSourceColumn === column - ) { - // This is a duplicate mapping pointing at the exact same starting point in the source - // file. It doesn't carry any new information, and only bloats the sourcemap. - continue; - } - lastLineWithSegment = i; - lastSourcesIndex = sourcesIndex; - lastSourceLine = line; - lastSourceColumn = column; - - // This looks like unnecessary duplication, but it noticeably increases performance. If we - // were to push the nameIndex onto length-4 array, v8 would internally allocate 22 slots! - // That's 68 wasted bytes! Array literals have the same capacity as their length, saving - // memory. - tracedSegments.push( - name - ? [genCol, sourcesIndex, line, column, put(names, name)] - : [genCol, sourcesIndex, line, column] - ); - } - - mappings.push(tracedSegments); - } - - if (mappings.length > lastLineWithSegment + 1) { - mappings.length = lastLineWithSegment + 1; - } - - return presortedDecodedMap( - Object.assign({}, tree.map, { - mappings, - // TODO: Make all sources relative to the sourceRoot. - sourceRoot: undefined, - names: names.array, - sources: sources.array, - sourcesContent, - }) - ); - }; - } - /** * originalPositionFor is only called on children SourceMapTrees. It recurses down * into its own child SourceMapTrees, until we find the original source map. diff --git a/src/source-map.ts b/src/source-map.ts index 2df7e76..435159f 100644 --- a/src/source-map.ts +++ b/src/source-map.ts @@ -1,7 +1,7 @@ import { encodedMappings, decodedMappings } from '@jridgewell/trace-mapping'; import type { TraceMap } from '@jridgewell/trace-mapping'; -import type { DecodedSourceMap, RawSourceMap, Options } from './types'; +import type { DecodedSourceMap, EncodedSourceMap, Options } from './types'; /** * A SourceMap v3 compatible sourcemap, which only includes fields that were @@ -9,7 +9,7 @@ import type { DecodedSourceMap, RawSourceMap, Options } from './types'; */ export default class SourceMap { declare file?: string | null; - declare mappings: RawSourceMap['mappings'] | DecodedSourceMap['mappings']; + declare mappings: EncodedSourceMap['mappings'] | DecodedSourceMap['mappings']; declare sourceRoot?: string; declare names: string[]; declare sources: (string | null)[]; @@ -19,7 +19,9 @@ export default class SourceMap { constructor(map: TraceMap, options: Options) { this.version = 3; // SourceMap spec says this should be first. this.file = map.file; - this.mappings = options.decodedMappings ? decodedMappings(map) : encodedMappings(map); + this.mappings = options.decodedMappings + ? (decodedMappings(map) as DecodedSourceMap['mappings']) + : encodedMappings(map); this.names = map.names; this.sourceRoot = map.sourceRoot; diff --git a/src/types.ts b/src/types.ts index 3ae1107..a94c45d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,30 +1,12 @@ -interface SourceMapV3 { - file?: string | null; - names: string[]; - sourceRoot?: string; - sources: (string | null)[]; - sourcesContent?: (string | null)[]; - version: 3; -} - -type Column = number; -type SourcesIndex = number; -type SourceLine = number; -type SourceColumn = number; -type NamesIndex = number; - -export type SourceMapSegment = - | [Column] - | [Column, SourcesIndex, SourceLine, SourceColumn] - | [Column, SourcesIndex, SourceLine, SourceColumn, NamesIndex]; +import type { SourceMapInput } from '@jridgewell/trace-mapping'; -export interface RawSourceMap extends SourceMapV3 { - mappings: string; -} +export type { + SourceMapSegment, + DecodedSourceMap, + EncodedSourceMap, +} from '@jridgewell/trace-mapping'; -export interface DecodedSourceMap extends SourceMapV3 { - mappings: SourceMapSegment[][]; -} +export type { SourceMapInput }; export interface SourceMapSegmentObject { column: number; @@ -34,8 +16,6 @@ export interface SourceMapSegmentObject { content: string | null; } -export type SourceMapInput = string | RawSourceMap | DecodedSourceMap; - export type LoaderContext = { readonly importer: string; readonly depth: number; @@ -46,7 +26,7 @@ export type LoaderContext = { export type SourceMapLoader = ( file: string, ctx: LoaderContext -) => SourceMapInput | null | undefined; +) => SourceMapInput | null | undefined | void; export type Options = { excludeContent?: boolean; diff --git a/test/unit/build-source-map-tree.ts b/test/unit/build-source-map-tree.ts index c3fe90f..67f7513 100644 --- a/test/unit/build-source-map-tree.ts +++ b/test/unit/build-source-map-tree.ts @@ -1,8 +1,8 @@ import buildSourceMapTree from '../../src/build-source-map-tree'; -import type { DecodedSourceMap, RawSourceMap } from '../../src/types'; +import type { DecodedSourceMap, EncodedSourceMap } from '../../src/types'; describe('buildSourceMapTree', () => { - const rawMap: RawSourceMap = { + const rawMap: EncodedSourceMap = { mappings: 'AAAA', names: [], sources: ['helloworld.js'], diff --git a/test/unit/remapping.ts b/test/unit/remapping.ts index 1dfe066..c11fbf8 100644 --- a/test/unit/remapping.ts +++ b/test/unit/remapping.ts @@ -1,8 +1,8 @@ import remapping from '../../src/remapping'; -import type { RawSourceMap } from '../../src/types'; +import type { EncodedSourceMap } from '../../src/types'; describe('remapping', () => { - const rawMap: RawSourceMap = { + const rawMap: EncodedSourceMap = { file: 'transpiled.min.js', // 0th column of 1st line of output file translates into the 1st source // file, line 2, column 1, using 1st name. @@ -12,7 +12,7 @@ describe('remapping', () => { sourcesContent: ['1+1'], version: 3, }; - const transpiledMap: RawSourceMap = { + const transpiledMap: EncodedSourceMap = { // 1st column of 2nd line of output file translates into the 1st source // file, line 3, column 2 mappings: ';CAEE', @@ -21,7 +21,7 @@ describe('remapping', () => { sourcesContent: ['\n\n 1 + 1;'], version: 3, }; - const translatedMap: RawSourceMap = { + const translatedMap: EncodedSourceMap = { file: 'transpiled.min.js', // 0th column of 1st line of output file translates into the 1st source // file, line 3, column 2, using first name