Skip to content

Commit b217c39

Browse files
authored
Merge pull request #15935 from chuckjaz/external-file-source-map
Add support for external file references in source maps
2 parents 5888804 + ce1d1c8 commit b217c39

File tree

7 files changed

+89
-28
lines changed

7 files changed

+89
-28
lines changed

src/compiler/core.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2249,6 +2249,7 @@ namespace ts {
22492249
getSymbolConstructor(): new (flags: SymbolFlags, name: string) => Symbol;
22502250
getTypeConstructor(): new (checker: TypeChecker, flags: TypeFlags) => Type;
22512251
getSignatureConstructor(): new (checker: TypeChecker) => Signature;
2252+
getSourceMapSourceConstructor(): new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource;
22522253
}
22532254

22542255
function Symbol(this: Symbol, flags: SymbolFlags, name: string) {
@@ -2279,14 +2280,21 @@ namespace ts {
22792280
this.original = undefined;
22802281
}
22812282

2283+
function SourceMapSource(this: SourceMapSource, fileName: string, text: string, skipTrivia?: (pos: number) => number) {
2284+
this.fileName = fileName;
2285+
this.text = text;
2286+
this.skipTrivia = skipTrivia || (pos => pos);
2287+
}
2288+
22822289
export let objectAllocator: ObjectAllocator = {
22832290
getNodeConstructor: () => <any>Node,
22842291
getTokenConstructor: () => <any>Node,
22852292
getIdentifierConstructor: () => <any>Node,
22862293
getSourceFileConstructor: () => <any>Node,
22872294
getSymbolConstructor: () => <any>Symbol,
22882295
getTypeConstructor: () => <any>Type,
2289-
getSignatureConstructor: () => <any>Signature
2296+
getSignatureConstructor: () => <any>Signature,
2297+
getSourceMapSourceConstructor: () => <any>SourceMapSource,
22902298
};
22912299

22922300
export const enum AssertionLevel {

src/compiler/factory.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2314,15 +2314,24 @@ namespace ts {
23142314
/**
23152315
* Sets a custom text range to use when emitting source maps.
23162316
*/
2317-
export function setSourceMapRange<T extends Node>(node: T, range: TextRange | undefined) {
2317+
export function setSourceMapRange<T extends Node>(node: T, range: SourceMapRange | undefined) {
23182318
getOrCreateEmitNode(node).sourceMapRange = range;
23192319
return node;
23202320
}
23212321

2322+
let SourceMapSource: new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource;
2323+
2324+
/**
2325+
* Create an external source map source file reference
2326+
*/
2327+
export function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): SourceMapSource {
2328+
return new (SourceMapSource || (SourceMapSource = objectAllocator.getSourceMapSourceConstructor()))(fileName, text, skipTrivia);
2329+
}
2330+
23222331
/**
23232332
* Gets the TextRange to use for source maps for a token of a node.
23242333
*/
2325-
export function getTokenSourceMapRange(node: Node, token: SyntaxKind): TextRange | undefined {
2334+
export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined {
23262335
const emitNode = node.emitNode;
23272336
const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges;
23282337
return tokenSourceMapRanges && tokenSourceMapRanges[token];
@@ -2331,7 +2340,7 @@ namespace ts {
23312340
/**
23322341
* Sets the TextRange to use for source maps for a token of a node.
23332342
*/
2334-
export function setTokenSourceMapRange<T extends Node>(node: T, token: SyntaxKind, range: TextRange | undefined) {
2343+
export function setTokenSourceMapRange<T extends Node>(node: T, token: SyntaxKind, range: SourceMapRange | undefined) {
23352344
const emitNode = getOrCreateEmitNode(node);
23362345
const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = []);
23372346
tokenSourceMapRanges[token] = range;

src/compiler/scanner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ namespace ts {
363363
};
364364
}
365365

366-
export function getLineAndCharacterOfPosition(sourceFile: SourceFile, position: number): LineAndCharacter {
366+
export function getLineAndCharacterOfPosition(sourceFile: SourceFileLike, position: number): LineAndCharacter {
367367
return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position);
368368
}
369369

src/compiler/sourcemap.ts

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace ts {
2222
*
2323
* @param sourceFile The source file.
2424
*/
25-
setSourceFile(sourceFile: SourceFile): void;
25+
setSourceFile(sourceFile: SourceMapSource): void;
2626

2727
/**
2828
* Emits a mapping.
@@ -81,7 +81,7 @@ namespace ts {
8181
export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter {
8282
const compilerOptions = host.getCompilerOptions();
8383
const extendedDiagnostics = compilerOptions.extendedDiagnostics;
84-
let currentSourceFile: SourceFile;
84+
let currentSource: SourceMapSource;
8585
let currentSourceText: string;
8686
let sourceMapDir: string; // The directory in which sourcemap will be
8787

@@ -109,6 +109,13 @@ namespace ts {
109109
getSourceMappingURL,
110110
};
111111

112+
/**
113+
* Skips trivia such as comments and white-space that can optionally overriden by the source map source
114+
*/
115+
function skipSourceTrivia(pos: number): number {
116+
return currentSource.skipTrivia ? currentSource.skipTrivia(pos) : skipTrivia(currentSourceText, pos);
117+
}
118+
112119
/**
113120
* Initialize the SourceMapWriter for a new output file.
114121
*
@@ -125,7 +132,7 @@ namespace ts {
125132
reset();
126133
}
127134

128-
currentSourceFile = undefined;
135+
currentSource = undefined;
129136
currentSourceText = undefined;
130137

131138
// Current source map file and its index in the sources list
@@ -192,7 +199,7 @@ namespace ts {
192199
return;
193200
}
194201

195-
currentSourceFile = undefined;
202+
currentSource = undefined;
196203
sourceMapDir = undefined;
197204
sourceMapSourceIndex = undefined;
198205
lastRecordedSourceMapSpan = undefined;
@@ -263,7 +270,7 @@ namespace ts {
263270
performance.mark("beforeSourcemap");
264271
}
265272

266-
const sourceLinePos = getLineAndCharacterOfPosition(currentSourceFile, pos);
273+
const sourceLinePos = getLineAndCharacterOfPosition(currentSource, pos);
267274

268275
// Convert the location to be one-based.
269276
sourceLinePos.line++;
@@ -320,14 +327,22 @@ namespace ts {
320327
if (node) {
321328
const emitNode = node.emitNode;
322329
const emitFlags = emitNode && emitNode.flags;
323-
const { pos, end } = emitNode && emitNode.sourceMapRange || node;
330+
const range = emitNode && emitNode.sourceMapRange;
331+
const { pos, end } = range || node;
332+
let source = range && range.source;
333+
const oldSource = currentSource;
334+
if (source === oldSource) source = undefined;
335+
336+
if (source) setSourceFile(source);
324337

325338
if (node.kind !== SyntaxKind.NotEmittedStatement
326339
&& (emitFlags & EmitFlags.NoLeadingSourceMap) === 0
327340
&& pos >= 0) {
328-
emitPos(skipTrivia(currentSourceText, pos));
341+
emitPos(skipSourceTrivia(pos));
329342
}
330343

344+
if (source) setSourceFile(oldSource);
345+
331346
if (emitFlags & EmitFlags.NoNestedSourceMaps) {
332347
disabled = true;
333348
emitCallback(hint, node);
@@ -337,11 +352,15 @@ namespace ts {
337352
emitCallback(hint, node);
338353
}
339354

355+
if (source) setSourceFile(source);
356+
340357
if (node.kind !== SyntaxKind.NotEmittedStatement
341358
&& (emitFlags & EmitFlags.NoTrailingSourceMap) === 0
342359
&& end >= 0) {
343360
emitPos(end);
344361
}
362+
363+
if (source) setSourceFile(oldSource);
345364
}
346365
}
347366

@@ -362,7 +381,7 @@ namespace ts {
362381
const emitFlags = emitNode && emitNode.flags;
363382
const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token];
364383

365-
tokenPos = skipTrivia(currentSourceText, range ? range.pos : tokenPos);
384+
tokenPos = skipSourceTrivia(range ? range.pos : tokenPos);
366385
if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) {
367386
emitPos(tokenPos);
368387
}
@@ -382,21 +401,21 @@ namespace ts {
382401
*
383402
* @param sourceFile The source file.
384403
*/
385-
function setSourceFile(sourceFile: SourceFile) {
404+
function setSourceFile(sourceFile: SourceMapSource) {
386405
if (disabled) {
387406
return;
388407
}
389408

390-
currentSourceFile = sourceFile;
391-
currentSourceText = currentSourceFile.text;
409+
currentSource = sourceFile;
410+
currentSourceText = currentSource.text;
392411

393412
// Add the file to tsFilePaths
394413
// If sourceroot option: Use the relative path corresponding to the common directory path
395414
// otherwise source locations relative to map file location
396415
const sourcesDirectoryPath = compilerOptions.sourceRoot ? host.getCommonSourceDirectory() : sourceMapDir;
397416

398417
const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath,
399-
currentSourceFile.fileName,
418+
currentSource.fileName,
400419
host.getCurrentDirectory(),
401420
host.getCanonicalFileName,
402421
/*isAbsolutePathAnUrl*/ true);
@@ -407,10 +426,10 @@ namespace ts {
407426
sourceMapData.sourceMapSources.push(source);
408427

409428
// The one that can be used from program to get the actual source file
410-
sourceMapData.inputSourceFileNames.push(currentSourceFile.fileName);
429+
sourceMapData.inputSourceFileNames.push(currentSource.fileName);
411430

412431
if (compilerOptions.inlineSources) {
413-
sourceMapData.sourceMapSourcesContent.push(currentSourceFile.text);
432+
sourceMapData.sourceMapSourcesContent.push(currentSource.text);
414433
}
415434
}
416435
}

src/compiler/types.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4013,18 +4013,29 @@ namespace ts {
40134013
ES2015FunctionSyntaxMask = ContainsCapturedLexicalThis | ContainsDefaultValueAssignments,
40144014
}
40154015

4016+
export interface SourceMapRange extends TextRange {
4017+
source?: SourceMapSource;
4018+
}
4019+
4020+
export interface SourceMapSource {
4021+
fileName: string;
4022+
text: string;
4023+
/* @internal */ lineMap: number[];
4024+
skipTrivia?: (pos: number) => number;
4025+
}
4026+
40164027
/* @internal */
40174028
export interface EmitNode {
4018-
annotatedNodes?: Node[]; // Tracks Parse-tree nodes with EmitNodes for eventual cleanup.
4019-
flags?: EmitFlags; // Flags that customize emit
4020-
leadingComments?: SynthesizedComment[]; // Synthesized leading comments
4029+
annotatedNodes?: Node[]; // Tracks Parse-tree nodes with EmitNodes for eventual cleanup.
4030+
flags?: EmitFlags; // Flags that customize emit
4031+
leadingComments?: SynthesizedComment[]; // Synthesized leading comments
40214032
trailingComments?: SynthesizedComment[]; // Synthesized trailing comments
4022-
commentRange?: TextRange; // The text range to use when emitting leading or trailing comments
4023-
sourceMapRange?: TextRange; // The text range to use when emitting leading or trailing source mappings
4024-
tokenSourceMapRanges?: TextRange[]; // The text range to use when emitting source mappings for tokens
4025-
constantValue?: string | number; // The constant value of an expression
4026-
externalHelpersModuleName?: Identifier; // The local name for an imported helpers module
4027-
helpers?: EmitHelper[]; // Emit helpers for the node
4033+
commentRange?: TextRange; // The text range to use when emitting leading or trailing comments
4034+
sourceMapRange?: SourceMapRange; // The text range to use when emitting leading or trailing source mappings
4035+
tokenSourceMapRanges?: SourceMapRange[]; // The text range to use when emitting source mappings for tokens
4036+
constantValue?: string | number; // The constant value of an expression
4037+
externalHelpersModuleName?: Identifier; // The local name for an imported helpers module
4038+
helpers?: EmitHelper[]; // Emit helpers for the node
40284039
}
40294040

40304041
export const enum EmitFlags {

src/services/services.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,15 @@ namespace ts {
732732
}
733733
}
734734

735+
class SourceMapSourceObject implements SourceMapSource {
736+
lineMap: number[];
737+
constructor (public fileName: string, public text: string, public skipTrivia?: (pos: number) => number) {}
738+
739+
public getLineAndCharacterOfPosition(pos: number): LineAndCharacter {
740+
return ts.getLineAndCharacterOfPosition(this, pos);
741+
}
742+
}
743+
735744
function getServicesObjectAllocator(): ObjectAllocator {
736745
return {
737746
getNodeConstructor: () => NodeObject,
@@ -742,6 +751,7 @@ namespace ts {
742751
getSymbolConstructor: () => SymbolObject,
743752
getTypeConstructor: () => TypeObject,
744753
getSignatureConstructor: () => SignatureObject,
754+
getSourceMapSourceConstructor: () => SourceMapSourceObject,
745755
};
746756
}
747757

src/services/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ namespace ts {
7171
getLineAndCharacterOfPosition(pos: number): LineAndCharacter;
7272
}
7373

74+
export interface SourceMapSource {
75+
getLineAndCharacterOfPosition(pos: number): LineAndCharacter;
76+
}
77+
7478
/**
7579
* Represents an immutable snapshot of a script at a specified time.Once acquired, the
7680
* snapshot is observably immutable. i.e. the same calls with the same parameters will return

0 commit comments

Comments
 (0)