Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract GenMapping #171

Merged
merged 3 commits into from Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2,075 changes: 999 additions & 1,076 deletions package-lock.json

Large diffs are not rendered by default.

30 changes: 15 additions & 15 deletions package.json
Expand Up @@ -41,23 +41,23 @@
"test:watch": "jest --coverage --watch"
},
"devDependencies": {
"@rollup/plugin-typescript": "8.3.0",
"@types/jest": "27.4.0",
"@typescript-eslint/eslint-plugin": "5.10.2",
"@typescript-eslint/parser": "5.10.2",
"eslint": "8.8.0",
"eslint-config-prettier": "8.3.0",
"jest": "27.4.7",
"jest-config": "27.4.7",
"@rollup/plugin-typescript": "8.3.2",
"@types/jest": "27.4.1",
"@typescript-eslint/eslint-plugin": "5.20.0",
"@typescript-eslint/parser": "5.20.0",
"eslint": "8.14.0",
"eslint-config-prettier": "8.5.0",
"jest": "27.5.1",
"jest-config": "27.5.1",
"npm-run-all": "4.1.5",
"prettier": "2.5.1",
"rollup": "2.67.0",
"ts-jest": "27.1.3",
"tslib": "2.3.1",
"typescript": "4.5.5"
"prettier": "2.6.2",
"rollup": "2.70.2",
"ts-jest": "27.1.4",
"tslib": "2.4.0",
"typescript": "4.6.3"
},
"dependencies": {
"@jridgewell/set-array": "^1.0.0",
"@jridgewell/trace-mapping": "^0.3.0"
"@jridgewell/gen-mapping": "^0.1.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
}
1 change: 1 addition & 0 deletions rollup.config.js
Expand Up @@ -12,6 +12,7 @@ function configure(esm) {
entryFileNames: '[name].umd.js',
sourcemap: true,
globals: {
'@jridgewell/gen-mapping': 'genMapping',
'@jridgewell/trace-mapping': 'traceMapping',
},
},
Expand Down
6 changes: 3 additions & 3 deletions src/build-source-map-tree.ts
Expand Up @@ -2,7 +2,7 @@ import { TraceMap } from '@jridgewell/trace-mapping';

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

import type { Sources } from './source-map-tree';
import type { Sources, MapSource as MapSourceType } 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
): Sources {
): MapSourceType {
const maps = asArray(input).map((m) => new TraceMap(m, ''));
const map = maps.pop()!;

Expand All @@ -49,7 +49,7 @@ function build(
loader: SourceMapLoader,
importer: string,
importerDepth: number
): Sources {
): MapSourceType {
const { resolvedSources, sourcesContent } = map;

const depth = importerDepth + 1;
Expand Down
117 changes: 43 additions & 74 deletions src/source-map-tree.ts
@@ -1,23 +1,41 @@
import { SetArray, put } from '@jridgewell/set-array';
import { presortedDecodedMap, traceSegment, decodedMappings } from '@jridgewell/trace-mapping';
import { GenMapping, addSegment, setSourceContent } from '@jridgewell/gen-mapping';
import { traceSegment, decodedMappings } from '@jridgewell/trace-mapping';

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

const INVALID_MAPPING = undefined;
const SOURCELESS_MAPPING = null;
export type SourceMapSegmentObject =
| {
column: number;
line: number;
name: string;
source: string;
content: string | null;
}
| {
column: null;
line: null;
name: null;
source: null;
content: null;
};

const SOURCELESS_MAPPING = {
source: null,
column: null,
line: null,
name: null,
content: null,
};
const EMPTY_SOURCES: Sources[] = [];

type MappingSource = SourceMapSegmentObject | typeof INVALID_MAPPING | typeof SOURCELESS_MAPPING;

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

type MapSource = {
export type MapSource = {
map: TraceMap;
sources: Sources[];
source: string;
Expand Down Expand Up @@ -60,28 +78,24 @@ export function OriginalSource(source: string, content: string | null): Original
* 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: Sources): TraceMap {
const mappings: SourceMapSegment[][] = [];
const names = new SetArray();
const sources = new SetArray();
const sourcesContent: (string | null)[] = [];
export function traceMappings(tree: MapSource): GenMapping {
const gen = new GenMapping({ file: tree.map.file });
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;
let lastSource = null;
let lastSourceLine = null;
let lastSourceColumn = null;

for (let j = 0; j < segments.length; j++) {
const segment = segments[j];
const genCol = segment[0];
let traced: SourceMapSegmentObject | null = SOURCELESS_MAPPING;

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) {
Expand All @@ -95,71 +109,26 @@ export function traceMappings(tree: Sources): TraceMap {

// 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;
if (traced == null) 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.
if (line === lastSourceLine && column === lastSourceColumn && source === lastSource) {
continue;
}
lastLineWithSegment = i;
lastSourcesIndex = sourcesIndex;
lastSourceLine = line;
lastSourceColumn = column;
lastSource = source;

// 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]
);
// Sigh, TypeScript can't figure out source/line/column are either all null, or all non-null...
(addSegment as any)(gen, i, genCol, source, line, column, name);
if (content != null) setSourceContent(gen, source, content);
}

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,
})
);
return gen;
}

/**
Expand All @@ -171,15 +140,15 @@ export function originalPositionFor(
line: number,
column: number,
name: string
): MappingSource {
): SourceMapSegmentObject | null {
if (!source.map) {
return { column, line, name, source: source.source, content: source.content };
}

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;
if (segment == null) return null;
// 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;
Expand Down
25 changes: 12 additions & 13 deletions src/source-map.ts
@@ -1,6 +1,6 @@
import { encodedMappings, decodedMappings } from '@jridgewell/trace-mapping';
import { decodedMap, encodedMap } from '@jridgewell/gen-mapping';

import type { TraceMap } from '@jridgewell/trace-mapping';
import type { GenMapping } from '@jridgewell/gen-mapping';
import type { DecodedSourceMap, EncodedSourceMap, Options } from './types';

/**
Expand All @@ -16,19 +16,18 @@ export default class SourceMap {
declare sourcesContent?: (string | null)[];
declare version: 3;

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) as DecodedSourceMap['mappings'])
: encodedMappings(map);
this.names = map.names;
constructor(map: GenMapping, options: Options) {
const out = options.decodedMappings ? decodedMap(map) : encodedMap(map);
this.version = out.version; // SourceMap spec says this should be first.
this.file = out.file;
this.mappings = out.mappings as SourceMap['mappings'];
this.names = out.names as SourceMap['names'];

this.sourceRoot = map.sourceRoot;
this.sourceRoot = out.sourceRoot;

this.sources = map.sources;
if (!options.excludeContent && 'sourcesContent' in map) {
this.sourcesContent = map.sourcesContent;
this.sources = out.sources as SourceMap['sources'];
if (!options.excludeContent) {
this.sourcesContent = out.sourcesContent as SourceMap['sourcesContent'];
}
}

Expand Down
8 changes: 0 additions & 8 deletions src/types.ts
Expand Up @@ -8,14 +8,6 @@ export type {

export type { SourceMapInput };

export interface SourceMapSegmentObject {
column: number;
line: number;
name: string;
source: string;
content: string | null;
}

export type LoaderContext = {
readonly importer: string;
readonly depth: number;
Expand Down