Skip to content

Commit 6af764c

Browse files
authored
Declaration maps and transparent goto definition using them (#22658)
* Add compiler option to enable declaration sourcemaps * Transparent goto definition for sourcemapped declaration files * Post-rebase touchups * Rename API methods * Fix lints * Fix typo in name XD * Log sourcemap decode errors * Share the cache more, but also invalidate it more * Remove todo * Enable mapping on go to implementation as well * Allow fourslash to test declaration maps mroe easily * more test * Handle sourceRoot * Add tests documenting current behavior with other sourcemapping flags * Ignore inline options for declaration file maps, simplify dispatch in emitter * Change program diagnostic * Fix nit * Use charCodeAt * Rename internal methods + veriables * Avoid filter * span -> position * Use character codes * Dont parse our sourcemap names until we need to start using them * zero-index parsed positions * Handle sourceMappingURL comments, including base64 encoded ones * Unittest b64 decoder, make mroe robust to handle unicode properly * Fix lint * declarationMaps -> declarationMap * Even more feedback * USE Mroe lenient combined regexp * only match base64 characters * Fix nit
1 parent fe8f239 commit 6af764c

File tree

83 files changed

+3473
-87
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+3473
-87
lines changed

Jakefile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ var harnessCoreSources = [
115115
});
116116

117117
var harnessSources = harnessCoreSources.concat([
118+
"base64.ts",
118119
"incrementalParser.ts",
119120
"jsDocParsing.ts",
120121
"services/colorization.ts",

src/compiler/commandLineParser.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,13 @@ namespace ts {
194194
category: Diagnostics.Basic_Options,
195195
description: Diagnostics.Generates_corresponding_d_ts_file,
196196
},
197+
{
198+
name: "declarationMap",
199+
type: "boolean",
200+
showInSimplifiedHelpView: true,
201+
category: Diagnostics.Basic_Options,
202+
description: Diagnostics.Generates_a_sourcemap_for_each_corresponding_d_ts_file,
203+
},
197204
{
198205
name: "emitDeclarationOnly",
199206
type: "boolean",

src/compiler/core.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,6 +2048,10 @@ namespace ts {
20482048
return moduleResolution;
20492049
}
20502050

2051+
export function getAreDeclarationMapsEnabled(options: CompilerOptions) {
2052+
return !!(options.declaration && options.declarationMap);
2053+
}
2054+
20512055
export function getAllowSyntheticDefaultImports(compilerOptions: CompilerOptions) {
20522056
const moduleKind = getEmitModuleKind(compilerOptions);
20532057
return compilerOptions.allowSyntheticDefaultImports !== undefined

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2767,7 +2767,15 @@
27672767
"category": "Error",
27682768
"code": 5068
27692769
},
2770+
"Option '{0}' cannot be specified without specifying option '{1}' or option '{2}'.": {
2771+
"category": "Error",
2772+
"code": 5069
2773+
},
27702774

2775+
"Generates a sourcemap for each corresponding '.d.ts' file.": {
2776+
"category": "Message",
2777+
"code": 6000
2778+
},
27712779
"Concatenate and emit output to single file.": {
27722780
"category": "Message",
27732781
"code": 6001

src/compiler/emitter.ts

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,22 @@ namespace ts {
4949
const jsFilePath = options.outFile || options.out;
5050
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
5151
const declarationFilePath = (forceDtsPaths || options.declaration) ? removeFileExtension(jsFilePath) + Extension.Dts : undefined;
52-
return { jsFilePath, sourceMapFilePath, declarationFilePath };
52+
const declarationMapPath = getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
53+
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath };
5354
}
5455
else {
5556
const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, getOutputExtension(sourceFile, options));
5657
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
5758
// For legacy reasons (ie, we have baselines capturing the behavior), js files don't report a .d.ts output path - this would only matter if `declaration` and `allowJs` were both on, which is currently an error
5859
const isJs = isSourceFileJavaScript(sourceFile);
5960
const declarationFilePath = ((forceDtsPaths || options.declaration) && !isJs) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined;
60-
return { jsFilePath, sourceMapFilePath, declarationFilePath };
61+
const declarationMapPath = getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
62+
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath };
6163
}
6264
}
6365

6466
function getSourceMapFilePath(jsFilePath: string, options: CompilerOptions) {
65-
return options.sourceMap ? jsFilePath + ".map" : undefined;
67+
return (options.sourceMap && !options.inlineSourceMap) ? jsFilePath + ".map" : undefined;
6668
}
6769

6870
// JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also.
@@ -88,12 +90,19 @@ namespace ts {
8890
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean, transformers?: TransformerFactory<SourceFile>[]): EmitResult {
8991
const compilerOptions = host.getCompilerOptions();
9092
const moduleKind = getEmitModuleKind(compilerOptions);
91-
const sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? [] : undefined;
93+
const sourceMapDataList: SourceMapData[] = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined;
9294
const emittedFilesList: string[] = compilerOptions.listEmittedFiles ? [] : undefined;
9395
const emitterDiagnostics = createDiagnosticCollection();
9496
const newLine = host.getNewLine();
9597
const writer = createTextWriter(newLine);
9698
const sourceMap = createSourceMapWriter(host, writer);
99+
const declarationSourceMap = createSourceMapWriter(host, writer, {
100+
sourceMap: compilerOptions.declarationMap,
101+
sourceRoot: compilerOptions.sourceRoot,
102+
mapRoot: compilerOptions.mapRoot,
103+
extendedDiagnostics: compilerOptions.extendedDiagnostics,
104+
// Explicitly do not passthru either `inline` option
105+
});
97106

98107
let currentSourceFile: SourceFile;
99108
let bundledHelpers: Map<boolean>;
@@ -113,9 +122,9 @@ namespace ts {
113122
sourceMaps: sourceMapDataList
114123
};
115124

116-
function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle) {
125+
function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle) {
117126
emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath);
118-
emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath);
127+
emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath);
119128

120129
if (!emitSkipped && emittedFilesList) {
121130
if (!emitOnlyDtsFiles) {
@@ -162,13 +171,13 @@ namespace ts {
162171
onSetSourceFile: setSourceFile,
163172
});
164173

165-
printSourceFileOrBundle(jsFilePath, sourceMapFilePath, isSourceFile(sourceFileOrBundle) ? transform.transformed[0] : createBundle(transform.transformed), printer);
174+
printSourceFileOrBundle(jsFilePath, sourceMapFilePath, isSourceFile(sourceFileOrBundle) ? transform.transformed[0] : createBundle(transform.transformed), printer, sourceMap);
166175

167176
// Clean up emit nodes on parse tree
168177
transform.dispose();
169178
}
170179

171-
function emitDeclarationFileOrBundle(sourceFileOrBundle: SourceFile | Bundle, declarationFilePath: string | undefined) {
180+
function emitDeclarationFileOrBundle(sourceFileOrBundle: SourceFile | Bundle, declarationFilePath: string | undefined, declarationMapPath: string | undefined) {
172181
if (!(declarationFilePath && !isInJavaScriptFile(sourceFileOrBundle))) {
173182
return;
174183
}
@@ -186,25 +195,29 @@ namespace ts {
186195
// resolver hooks
187196
hasGlobalName: resolver.hasGlobalName,
188197

198+
// sourcemap hooks
199+
onEmitSourceMapOfNode: declarationSourceMap.emitNodeWithSourceMap,
200+
onEmitSourceMapOfToken: declarationSourceMap.emitTokenWithSourceMap,
201+
onEmitSourceMapOfPosition: declarationSourceMap.emitPos,
202+
onSetSourceFile: setSourceFileForDeclarationSourceMaps,
203+
189204
// transform hooks
190205
onEmitNode: declarationTransform.emitNodeWithNotification,
191206
substituteNode: declarationTransform.substituteNode,
192207
});
193208
const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit;
194209
emitSkipped = emitSkipped || declBlocked;
195210
if (!declBlocked || emitOnlyDtsFiles) {
196-
const previousState = sourceMap.setState(/*disabled*/ true);
197-
printSourceFileOrBundle(declarationFilePath, /*sourceMapFilePath*/ undefined, declarationTransform.transformed[0], declarationPrinter, /*shouldSkipSourcemap*/ true);
198-
sourceMap.setState(previousState);
211+
printSourceFileOrBundle(declarationFilePath, declarationMapPath, declarationTransform.transformed[0], declarationPrinter, declarationSourceMap);
199212
}
200213
declarationTransform.dispose();
201214
}
202215

203-
function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string, sourceFileOrBundle: SourceFile | Bundle, printer: Printer, shouldSkipSourcemap = false) {
216+
function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, printer: Printer, mapRecorder: SourceMapWriter) {
204217
const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined;
205218
const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined;
206219
const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile];
207-
sourceMap.initialize(jsFilePath, sourceMapFilePath, sourceFileOrBundle);
220+
mapRecorder.initialize(jsFilePath, sourceMapFilePath || "", sourceFileOrBundle, sourceMapDataList);
208221

209222
if (bundle) {
210223
bundledHelpers = createMap<boolean>();
@@ -218,26 +231,21 @@ namespace ts {
218231

219232
writer.writeLine();
220233

221-
const sourceMappingURL = sourceMap.getSourceMappingURL();
222-
if (!shouldSkipSourcemap && sourceMappingURL) {
234+
const sourceMappingURL = mapRecorder.getSourceMappingURL();
235+
if (sourceMappingURL) {
223236
writer.write(`//# ${"sourceMappingURL"}=${sourceMappingURL}`); // Sometimes tools can sometimes see this line as a source mapping url comment
224237
}
225238

226239
// Write the source map
227-
if (!shouldSkipSourcemap && compilerOptions.sourceMap && !compilerOptions.inlineSourceMap) {
228-
writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap.getText(), /*writeByteOrderMark*/ false, sourceFiles);
229-
}
230-
231-
// Record source map data for the test harness.
232-
if (!shouldSkipSourcemap && sourceMapDataList) {
233-
sourceMapDataList.push(sourceMap.getSourceMapData());
240+
if (sourceMapFilePath) {
241+
writeFile(host, emitterDiagnostics, sourceMapFilePath, mapRecorder.getText(), /*writeByteOrderMark*/ false, sourceFiles);
234242
}
235243

236244
// Write the output file
237245
writeFile(host, emitterDiagnostics, jsFilePath, writer.getText(), compilerOptions.emitBOM, sourceFiles);
238246

239247
// Reset state
240-
sourceMap.reset();
248+
mapRecorder.reset();
241249
writer.clear();
242250

243251
currentSourceFile = undefined;
@@ -250,6 +258,11 @@ namespace ts {
250258
sourceMap.setSourceFile(node);
251259
}
252260

261+
function setSourceFileForDeclarationSourceMaps(node: SourceFile) {
262+
currentSourceFile = node;
263+
declarationSourceMap.setSourceFile(node);
264+
}
265+
253266
function emitHelpers(node: Node, writeLines: (text: string) => void) {
254267
let helpersEmitted = false;
255268
const bundle = node.kind === SyntaxKind.Bundle ? <Bundle>node : undefined;

src/compiler/program.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2113,9 +2113,9 @@ namespace ts {
21132113
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile");
21142114
}
21152115

2116-
if (options.mapRoot && !options.sourceMap) {
2116+
if (options.mapRoot && !(options.sourceMap || options.declarationMap)) {
21172117
// Error to specify --mapRoot without --sourcemap
2118-
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "mapRoot", "sourceMap");
2118+
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "mapRoot", "sourceMap", "declarationMap");
21192119
}
21202120

21212121
if (options.declarationDir) {
@@ -2127,6 +2127,10 @@ namespace ts {
21272127
}
21282128
}
21292129

2130+
if (options.declarationMap && !options.declaration) {
2131+
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "declarationMap", "declaration");
2132+
}
2133+
21302134
if (options.lib && options.noLib) {
21312135
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib");
21322136
}
@@ -2301,21 +2305,21 @@ namespace ts {
23012305
return emptyArray;
23022306
}
23032307

2304-
function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string) {
2305-
createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2);
2308+
function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string, option3?: string) {
2309+
createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2, option3);
23062310
}
23072311

23082312
function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0: string) {
23092313
createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0);
23102314
}
23112315

2312-
function createDiagnosticForOption(onKey: boolean, option1: string, option2: string, message: DiagnosticMessage, arg0: string | number, arg1?: string | number) {
2316+
function createDiagnosticForOption(onKey: boolean, option1: string, option2: string, message: DiagnosticMessage, arg0: string | number, arg1?: string | number, arg2?: string | number) {
23132317
const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax();
23142318
const needCompilerDiagnostic = !compilerOptionsObjectLiteralSyntax ||
2315-
!createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1);
2319+
!createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1, arg2);
23162320

23172321
if (needCompilerDiagnostic) {
2318-
programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1));
2322+
programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2));
23192323
}
23202324
}
23212325

@@ -2334,10 +2338,10 @@ namespace ts {
23342338
return _compilerOptionsObjectLiteralSyntax;
23352339
}
23362340

2337-
function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string, message: DiagnosticMessage, arg0: string | number, arg1?: string | number): boolean {
2341+
function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string, message: DiagnosticMessage, arg0: string | number, arg1?: string | number, arg2?: string | number): boolean {
23382342
const props = getPropertyAssignment(objectLiteral, key1, key2);
23392343
for (const prop of props) {
2340-
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile, onKey ? prop.name : prop.initializer, message, arg0, arg1));
2344+
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile, onKey ? prop.name : prop.initializer, message, arg0, arg1, arg2));
23412345
}
23422346
return !!props.length;
23432347
}

src/compiler/scanner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ namespace ts {
330330
return result;
331331
}
332332

333-
export function getPositionOfLineAndCharacter(sourceFile: SourceFile, line: number, character: number): number {
333+
export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number {
334334
return computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text);
335335
}
336336

0 commit comments

Comments
 (0)