Skip to content

Commit

Permalink
Refactor originalPositionFor to remove polymorphism
Browse files Browse the repository at this point in the history
  • Loading branch information
jridgewell committed Apr 23, 2022
1 parent 90a5b6b commit cfa1c9b
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 194 deletions.
70 changes: 34 additions & 36 deletions src/build-source-map-tree.ts
@@ -1,8 +1,8 @@
import { TraceMap } from '@jridgewell/trace-mapping';

import OriginalSource from './original-source';
import { SourceMapTree } from './source-map-tree';
import { OriginalSource, MapSource } from './source-map-tree';

import type { Sources } from './source-map-tree';
import type { SourceMapInput, SourceMapLoader, LoaderContext } from './types';

function asArray<T>(value: T | T[]): T[] {
Expand All @@ -24,7 +24,7 @@ function asArray<T>(value: T | T[]): T[] {
export default function buildSourceMapTree(
input: SourceMapInput | SourceMapInput[],
loader: SourceMapLoader
): SourceMapTree {
): Sources {
const maps = asArray(input).map((m) => new TraceMap(m, ''));
const map = maps.pop()!;

Expand All @@ -39,7 +39,7 @@ export default function buildSourceMapTree(

let tree = build(map, loader, '', 0);
for (let i = maps.length - 1; i >= 0; i--) {
tree = new SourceMapTree(maps[i], [tree]);
tree = MapSource(maps[i], [tree]);
}
return tree;
}
Expand All @@ -49,44 +49,42 @@ function build(
loader: SourceMapLoader,
importer: string,
importerDepth: number
): SourceMapTree {
): Sources {
const { resolvedSources, sourcesContent } = map;

const depth = importerDepth + 1;
const children = resolvedSources.map(
(sourceFile: string | null, i: number): SourceMapTree | OriginalSource => {
// The loading context gives the loader more information about why this file is being loaded
// (eg, from which importer). It also allows the loader to override the location of the loaded
// sourcemap/original source, or to override the content in the sourcesContent field if it's
// an unmodified source file.
const ctx: LoaderContext = {
importer,
depth,
source: sourceFile || '',
content: undefined,
};
const children = resolvedSources.map((sourceFile: string | null, i: number): Sources => {
// The loading context gives the loader more information about why this file is being loaded
// (eg, from which importer). It also allows the loader to override the location of the loaded
// sourcemap/original source, or to override the content in the sourcesContent field if it's
// an unmodified source file.
const ctx: LoaderContext = {
importer,
depth,
source: sourceFile || '',
content: undefined,
};

// Use the provided loader callback to retrieve the file's sourcemap.
// TODO: We should eventually support async loading of sourcemap files.
const sourceMap = loader(ctx.source, ctx);
// Use the provided loader callback to retrieve the file's sourcemap.
// TODO: We should eventually support async loading of sourcemap files.
const sourceMap = loader(ctx.source, ctx);

const { source, content } = ctx;
const { source, content } = ctx;

// If there is no sourcemap, then it is an unmodified source file.
if (!sourceMap) {
// The contents of this unmodified source file can be overridden via the loader context,
// allowing it to be explicitly null or a string. If it remains undefined, we fall back to
// the importing sourcemap's `sourcesContent` field.
const sourceContent =
content !== undefined ? content : sourcesContent ? sourcesContent[i] : null;
return new OriginalSource(source, sourceContent);
}

// Else, it's a real sourcemap, and we need to recurse into it to load its
// source files.
return build(new TraceMap(sourceMap, source), loader, source, depth);
// If there is no sourcemap, then it is an unmodified source file.
if (!sourceMap) {
// The contents of this unmodified source file can be overridden via the loader context,
// allowing it to be explicitly null or a string. If it remains undefined, we fall back to
// the importing sourcemap's `sourcesContent` field.
const sourceContent =
content !== undefined ? content : sourcesContent ? sourcesContent[i] : null;
return OriginalSource(source, sourceContent);
}
);

return new SourceMapTree(map, children);
// Else, it's a real sourcemap, and we need to recurse into it to load its
// source files.
return build(new TraceMap(sourceMap, source), loader, source, depth);
});

return MapSource(map, children);
}
23 changes: 0 additions & 23 deletions src/original-source.ts

This file was deleted.

109 changes: 75 additions & 34 deletions src/source-map-tree.ts
Expand Up @@ -2,20 +2,65 @@ import { FastStringArray, put } from './fast-string-array';
import { presortedDecodedMap, traceSegment, decodedMappings } from '@jridgewell/trace-mapping';

import type { TraceMap } from '@jridgewell/trace-mapping';
import type OriginalSource from './original-source';
import type { SourceMapSegment, SourceMapSegmentObject } from './types';

type Sources = OriginalSource | SourceMapTree;

const INVALID_MAPPING = undefined;
const SOURCELESS_MAPPING = null;
const EMPTY_SOURCES: Sources[] = [];

type MappingSource = SourceMapSegmentObject | typeof INVALID_MAPPING | typeof SOURCELESS_MAPPING;

type OriginalSource = {
map: TraceMap;
sources: Sources[];
source: string;
content: string | null;
};

type MapSource = {
map: TraceMap;
sources: Sources[];
source: string;
content: string | null;
};

export type Sources = OriginalSource | MapSource;

function Source<M extends TraceMap | null>(
map: TraceMap | null,
sources: Sources[],
source: string,
content: string | null
): M extends null ? OriginalSource : MapSource {
return {
map,
sources,
source,
content,
} as any;
}

/**
* MapSource represents a single sourcemap, with the ability to trace mappings into its child nodes
* (which may themselves be SourceMapTrees).
*/
export function MapSource(map: TraceMap, sources: Sources[]): MapSource {
return Source(map, sources, '', null);
}

/**
* A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive
* segment tracing ends at the `OriginalSource`.
*/
export function OriginalSource(source: string, content: string | null): OriginalSource {
return Source(null, EMPTY_SOURCES, source, content);
}

/**
* 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 function traceMappings(tree: SourceMapTree): TraceMap {
export function traceMappings(tree: Sources): TraceMap {
const mappings: SourceMapSegment[][] = [];
const names = FastStringArray();
const sources = FastStringArray();
Expand All @@ -41,7 +86,8 @@ export function traceMappings(tree: SourceMapTree): TraceMap {
// to gather from it.
if (segment.length !== 1) {
const source = rootSources[segment[1]];
traced = source.originalPositionFor(
traced = originalPositionFor(
source,
segment[2],
segment[3],
segment.length === 5 ? rootNames[segment[4]] : ''
Expand Down Expand Up @@ -117,36 +163,31 @@ export function traceMappings(tree: SourceMapTree): TraceMap {
}

/**
* SourceMapTree represents a single sourcemap, with the ability to trace
* mappings into its child nodes (which may themselves be SourceMapTrees).
* originalPositionFor is only called on children SourceMapTrees. It recurses down into its own
* child SourceMapTrees, until we find the original source map.
*/
export class SourceMapTree {
declare map: TraceMap;
declare sources: Sources[];

constructor(map: TraceMap, sources: Sources[]) {
this.map = map;
this.sources = sources;
export function originalPositionFor(
source: Sources,
line: number,
column: number,
name: string
): MappingSource {
if (!source.map) {
return { column, line, name, source: source.source, content: source.content };
}

/**
* originalPositionFor is only called on children SourceMapTrees. It recurses down
* into its own child SourceMapTrees, until we find the original source map.
*/
originalPositionFor(line: number, column: number, name: string): MappingSource {
const segment = traceSegment(this.map, line, column);

// If we couldn't find a segment, then this doesn't exist in the sourcemap.
if (segment == null) return INVALID_MAPPING;
// 1-length segments only move the current generated column, there's no source information
// to gather from it.
if (segment.length === 1) return SOURCELESS_MAPPING;

const source = this.sources[segment[1]];
return source.originalPositionFor(
segment[2],
segment[3],
segment.length === 5 ? this.map.names[segment[4]] : name
);
}
const segment = traceSegment(source.map, line, column);

// If we couldn't find a segment, then this doesn't exist in the sourcemap.
if (segment == null) return INVALID_MAPPING;
// 1-length segments only move the current generated column, there's no source information
// to gather from it.
if (segment.length === 1) return SOURCELESS_MAPPING;

return originalPositionFor(
source.sources[segment[1]],
segment[2],
segment[3],
segment.length === 5 ? source.map.names[segment[4]] : name
);
}
61 changes: 0 additions & 61 deletions test/unit/original-source.ts

This file was deleted.

0 comments on commit cfa1c9b

Please sign in to comment.