Skip to content

Commit

Permalink
Sourcemap types (#2985)
Browse files Browse the repository at this point in the history
* Distinguish between a ExistingRawSourceMap and ExistingDecodedSourceMap

* Start work on DecodedSourceMapOrMissing type

* Add decodedSourcemap helper

* Fix collapseSourcemaps types

* Specify Link|Source type

* Handle missing map in collapseSourcemaps

* Remove remaining any casts

* Handle single item SourceMapSegmentVector

* Fix tests

* Fix lint issues

* Handle null sourcemaps meaning no change in code

* Handle a no-mappings sourcemap

* Remove sourcemap-coded declaration

It has types now

* Undo comment changes

* Use sourcemap-codec's types

* Get rid of TODO

* Use our own SourceMapSegment tuple type
  • Loading branch information
jridgewell authored and lukastaegert committed Jul 7, 2019
1 parent 8fe1385 commit e66d7be
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 104 deletions.
10 changes: 8 additions & 2 deletions src/Chunk.ts
Expand Up @@ -14,7 +14,13 @@ import ExternalModule from './ExternalModule';
import finalisers from './finalisers/index';
import Graph from './Graph';
import Module from './Module';
import { GlobalsOption, OutputOptions, RawSourceMap, RenderedChunk, RenderedModule } from './rollup/types';
import {
DecodedSourceMapOrMissing,
GlobalsOption,
OutputOptions,
RenderedChunk,
RenderedModule
} from './rollup/types';
import { Addons } from './utils/addons';
import { toBase64 } from './utils/base64';
import collapseSourcemaps from './utils/collapseSourcemaps';
Expand Down Expand Up @@ -676,7 +682,7 @@ export default class Chunk {
timeEnd('render format', 3);

let map: SourceMap = null as any;
const chunkSourcemapChain: RawSourceMap[] = [];
const chunkSourcemapChain: DecodedSourceMapOrMissing[] = [];

return renderChunk({
chunk: this,
Expand Down
9 changes: 5 additions & 4 deletions src/Module.ts
Expand Up @@ -32,9 +32,10 @@ import ExternalModule from './ExternalModule';
import Graph from './Graph';
import {
Asset,
DecodedSourceMapOrMissing,
EmittedChunk,
ExistingDecodedSourceMap,
ModuleJSON,
RawSourceMap,
ResolvedIdMap,
RollupError,
RollupWarning,
Expand Down Expand Up @@ -199,11 +200,11 @@ export default class Module {
manualChunkAlias: string = null as any;
moduleSideEffects: boolean;
originalCode!: string;
originalSourcemap!: RawSourceMap | null;
originalSourcemap!: ExistingDecodedSourceMap | null;
reexports: { [name: string]: ReexportDescription } = Object.create(null);
resolvedIds!: ResolvedIdMap;
scope!: ModuleScope;
sourcemapChain!: RawSourceMap[];
sourcemapChain!: DecodedSourceMapOrMissing[];
sources: string[] = [];
transformAssets?: Asset[];
transformChunks?: EmittedChunk[];
Expand Down Expand Up @@ -540,7 +541,7 @@ export default class Module {
this.code = code;
this.originalCode = originalCode;
this.originalSourcemap = originalSourcemap;
this.sourcemapChain = sourcemapChain as RawSourceMap[];
this.sourcemapChain = sourcemapChain;
if (transformAssets) {
this.transformAssets = transformAssets;
}
Expand Down
39 changes: 31 additions & 8 deletions src/rollup/types.d.ts
Expand Up @@ -38,6 +38,21 @@ export interface RollupLogProps {
url?: string;
}

export type SourceMapSegment =
| [number]
| [number, number, number, number]
| [number, number, number, number, number];

export interface ExistingDecodedSourceMap {
file?: string;
mappings: SourceMapSegment[][];
names: string[];
sourceRoot?: string;
sources: string[];
sourcesContent?: string[];
version: number;
}

export interface ExistingRawSourceMap {
file?: string;
mappings: string;
Expand All @@ -48,7 +63,13 @@ export interface ExistingRawSourceMap {
version: number;
}

export type RawSourceMap = { mappings: '' } | ExistingRawSourceMap;
export type DecodedSourceMapOrMissing =
| {
mappings?: never;
missing: true;
plugin: string;
}
| ExistingDecodedSourceMap;

export interface SourceMap {
file: string;
Expand All @@ -61,10 +82,12 @@ export interface SourceMap {
toUrl(): string;
}

export type SourceMapInput = ExistingRawSourceMap | string | null | { mappings: '' };

export interface SourceDescription {
ast?: ESTree.Program;
code: string;
map?: string | RawSourceMap;
map?: SourceMapInput;
moduleSideEffects?: boolean | null;
}

Expand All @@ -79,9 +102,9 @@ export interface TransformModuleJSON {
customTransformCache: boolean;
moduleSideEffects: boolean | null;
originalCode: string;
originalSourcemap: RawSourceMap | null;
originalSourcemap: ExistingDecodedSourceMap | null;
resolvedIds?: ResolvedIdMap;
sourcemapChain: (RawSourceMap | { missing: true; plugin: string })[];
sourcemapChain: DecodedSourceMapOrMissing[];
transformDependencies: string[] | null;
}

Expand Down Expand Up @@ -207,8 +230,8 @@ export type TransformChunkHook = (
code: string,
options: OutputOptions
) =>
| Promise<{ code: string; map: RawSourceMap } | null | undefined>
| { code: string; map: RawSourceMap }
| Promise<{ code: string; map?: SourceMapInput } | null | undefined>
| { code: string; map?: SourceMapInput }
| null
| undefined;

Expand All @@ -218,8 +241,8 @@ export type RenderChunkHook = (
chunk: RenderedChunk,
options: OutputOptions
) =>
| Promise<{ code: string; map: RawSourceMap } | null>
| { code: string; map: RawSourceMap }
| Promise<{ code: string; map?: SourceMapInput } | null>
| { code: string; map?: SourceMapInput }
| string
| null;

Expand Down
108 changes: 54 additions & 54 deletions src/utils/collapseSourcemaps.ts
@@ -1,7 +1,11 @@
import { DecodedSourceMap, SourceMap } from 'magic-string';
import Chunk from '../Chunk';
import Module from '../Module';
import { ExistingRawSourceMap, RawSourceMap } from '../rollup/types';
import {
DecodedSourceMapOrMissing,
ExistingDecodedSourceMap,
SourceMapSegment
} from '../rollup/types';
import { error } from './error';
import { basename, dirname, relative, resolve } from './path';

Expand All @@ -21,10 +25,6 @@ class Source {
}
}

type SourceMapSegmentVector =
| [number, number, number, number, number]
| [number, number, number, number];

interface SourceMapSegmentObject {
column: number;
line: number;
Expand All @@ -33,11 +33,14 @@ interface SourceMapSegmentObject {
}

class Link {
mappings: SourceMapSegmentVector[][];
mappings: SourceMapSegment[][];
names: string[];
sources: Source[];
sources: (Source | Link)[];

constructor(map: { mappings: SourceMapSegmentVector[][]; names: string[] }, sources: Source[]) {
constructor(
map: { mappings: SourceMapSegment[][]; names: string[] },
sources: (Source | Link)[]
) {
this.sources = sources;
this.names = map.names;
this.mappings = map.mappings;
Expand All @@ -51,16 +54,17 @@ class Link {
const mappings = [];

for (const line of this.mappings) {
const tracedLine: SourceMapSegmentVector[] = [];
const tracedLine: SourceMapSegment[] = [];

for (const segment of line) {
if (segment.length == 1) continue;
const source = this.sources[segment[1]];
if (!source) continue;

const traced = source.traceSegment(
segment[2],
segment[3],
this.names[segment[4] as number]
segment.length === 5 ? this.names[segment[4]] : ''
);

if (traced) {
Expand All @@ -77,13 +81,11 @@ class Link {
sourcesContent[sourceIndex] !== traced.source.content
) {
error({
message: `Multiple conflicting contents for sourcemap source ${
traced.source.filename
}`
message: `Multiple conflicting contents for sourcemap source ${traced.source.filename}`
});
}

const tracedSegment: SourceMapSegmentVector = [
const tracedSegment: SourceMapSegment = [
segment[0],
sourceIndex,
traced.line,
Expand All @@ -97,7 +99,7 @@ class Link {
names.push(traced.name);
}

(tracedSegment as SourceMapSegmentVector)[4] = nameIndex;
(tracedSegment as SourceMapSegment)[4] = nameIndex;
}

tracedLine.push(tracedSegment);
Expand All @@ -110,7 +112,7 @@ class Link {
return { sources, sourcesContent, names, mappings };
}

traceSegment(line: number, column: number, name: string) {
traceSegment(line: number, column: number, name: string): SourceMapSegmentObject | null {
const segments = this.mappings[line];
if (!segments) return null;

Expand All @@ -122,13 +124,14 @@ class Link {
const m = (i + j) >> 1;
const segment = segments[m];
if (segment[0] === column) {
if (segment.length == 1) return null;
const source = this.sources[segment[1]];
if (!source) return null;

return source.traceSegment(
segment[2],
segment[3],
this.names[segment[4] as number] || name
segment.length === 5 ? this.names[segment[4]] : name
);
}
if (segment[0] > column) {
Expand All @@ -142,72 +145,69 @@ class Link {
}
}

// TODO TypeScript: Fix <any> typecasts
export default function collapseSourcemaps(
bundle: Chunk,
file: string,
map: DecodedSourceMap,
modules: Module[],
bundleSourcemapChain: RawSourceMap[],
bundleSourcemapChain: DecodedSourceMapOrMissing[],
excludeContent: boolean
) {
function linkMap(source: Source, map: any) {
if (map.missing) {
bundle.graph.warn({
code: 'SOURCEMAP_BROKEN',
message: `Sourcemap is likely to be incorrect: a plugin${
map.plugin ? ` ('${map.plugin}')` : ``
} was used to transform files, but didn't generate a sourcemap for the transformation. Consult the plugin documentation for help`,
plugin: map.plugin,
url: `https://rollupjs.org/guide/en/#warning-sourcemap-is-likely-to-be-incorrect`
});

map = {
mappings: '',
names: []
};
function linkMap(source: Source | Link, map: DecodedSourceMapOrMissing) {
if (map.mappings) {
return new Link(map, [source]);
}

return new Link(map, [source]);
bundle.graph.warn({
code: 'SOURCEMAP_BROKEN',
message: `Sourcemap is likely to be incorrect: a plugin${
map.plugin ? ` ('${map.plugin}')` : ``
} was used to transform files, but didn't generate a sourcemap for the transformation. Consult the plugin documentation for help`,
plugin: map.plugin,
url: `https://rollupjs.org/guide/en/#warning-sourcemap-is-likely-to-be-incorrect`
});

return new Link(
{
mappings: [],
names: []
},
[source]
);
}

const moduleSources = modules
.filter(module => !module.excludeFromSourcemap)
.map(module => {
let sourcemapChain = module.sourcemapChain;

let source: Source;
const originalSourcemap = module.originalSourcemap as ExistingRawSourceMap;
let source: Source | Link;
const originalSourcemap = module.originalSourcemap;
if (!originalSourcemap) {
source = new Source(module.id, module.originalCode);
} else {
const sources = originalSourcemap.sources;
const sourcesContent = originalSourcemap.sourcesContent || [];

if (sources == null || (sources.length <= 1 && sources[0] == null)) {
source = new Source(module.id, sourcesContent[0]);
sourcemapChain = [originalSourcemap as RawSourceMap].concat(sourcemapChain);
} else {
// TODO indiscriminately treating IDs and sources as normal paths is probably bad.
const directory = dirname(module.id) || '.';
const sourceRoot = originalSourcemap.sourceRoot || '.';
// TODO indiscriminately treating IDs and sources as normal paths is probably bad.
const directory = dirname(module.id) || '.';
const sourceRoot = originalSourcemap.sourceRoot || '.';

const baseSources = sources.map(
(source, i) => new Source(resolve(directory, sourceRoot, source), sourcesContent[i])
);
const baseSources = sources.map(
(source, i) => new Source(resolve(directory, sourceRoot, source), sourcesContent[i])
);

source = new Link(originalSourcemap as any, baseSources) as any;
}
source = new Link(originalSourcemap, baseSources);
}

source = sourcemapChain.reduce(linkMap as any, source);
source = module.sourcemapChain.reduce(linkMap, source);

return source;
});

let source = new Link(map as any, moduleSources);
// DecodedSourceMap (from magic-string) uses a number[] instead of the more
// correct SourceMapSegment tuples. Cast it here to gain type safety.
let source = new Link(map as ExistingDecodedSourceMap, moduleSources);

source = bundleSourcemapChain.reduce(linkMap as any, source);
source = bundleSourcemapChain.reduce(linkMap, source);

let { sources, sourcesContent, names, mappings } = source.traceMappings();

Expand Down
29 changes: 29 additions & 0 deletions src/utils/decodedSourcemap.ts
@@ -0,0 +1,29 @@
import { decode } from 'sourcemap-codec';
import { ExistingDecodedSourceMap, ExistingRawSourceMap, SourceMapInput } from '../rollup/types';

type Input = SourceMapInput | ExistingDecodedSourceMap | undefined;

export function decodedSourcemap(map: Input): ExistingDecodedSourceMap | null {
if (!map) return null;

if (typeof map === 'string') {
map = JSON.parse(map) as ExistingRawSourceMap;
}
if (map.mappings === '') {
return {
mappings: [],
names: [],
sources: [],
version: 3
};
}

let mappings;
if (typeof map.mappings === 'string') {
mappings = decode(map.mappings);
} else {
mappings = map.mappings;
}

return { ...(map as ExistingRawSourceMap | ExistingDecodedSourceMap), mappings };
}

0 comments on commit e66d7be

Please sign in to comment.