Skip to content

Commit

Permalink
Declaration maps and transparent goto definition using them (#22658)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
weswigham authored Mar 26, 2018
1 parent fe8f239 commit 6af764c
Show file tree
Hide file tree
Showing 83 changed files with 3,473 additions and 87 deletions.
1 change: 1 addition & 0 deletions Jakefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ var harnessCoreSources = [
});

var harnessSources = harnessCoreSources.concat([
"base64.ts",
"incrementalParser.ts",
"jsDocParsing.ts",
"services/colorization.ts",
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ namespace ts {
category: Diagnostics.Basic_Options,
description: Diagnostics.Generates_corresponding_d_ts_file,
},
{
name: "declarationMap",
type: "boolean",
showInSimplifiedHelpView: true,
category: Diagnostics.Basic_Options,
description: Diagnostics.Generates_a_sourcemap_for_each_corresponding_d_ts_file,
},
{
name: "emitDeclarationOnly",
type: "boolean",
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2048,6 +2048,10 @@ namespace ts {
return moduleResolution;
}

export function getAreDeclarationMapsEnabled(options: CompilerOptions) {
return !!(options.declaration && options.declarationMap);
}

export function getAllowSyntheticDefaultImports(compilerOptions: CompilerOptions) {
const moduleKind = getEmitModuleKind(compilerOptions);
return compilerOptions.allowSyntheticDefaultImports !== undefined
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2767,7 +2767,15 @@
"category": "Error",
"code": 5068
},
"Option '{0}' cannot be specified without specifying option '{1}' or option '{2}'.": {
"category": "Error",
"code": 5069
},

"Generates a sourcemap for each corresponding '.d.ts' file.": {
"category": "Message",
"code": 6000
},
"Concatenate and emit output to single file.": {
"category": "Message",
"code": 6001
Expand Down
59 changes: 36 additions & 23 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,22 @@ namespace ts {
const jsFilePath = options.outFile || options.out;
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
const declarationFilePath = (forceDtsPaths || options.declaration) ? removeFileExtension(jsFilePath) + Extension.Dts : undefined;
return { jsFilePath, sourceMapFilePath, declarationFilePath };
const declarationMapPath = getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath };
}
else {
const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, getOutputExtension(sourceFile, options));
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
// 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
const isJs = isSourceFileJavaScript(sourceFile);
const declarationFilePath = ((forceDtsPaths || options.declaration) && !isJs) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined;
return { jsFilePath, sourceMapFilePath, declarationFilePath };
const declarationMapPath = getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath };
}
}

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

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

let currentSourceFile: SourceFile;
let bundledHelpers: Map<boolean>;
Expand All @@ -113,9 +122,9 @@ namespace ts {
sourceMaps: sourceMapDataList
};

function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle) {
function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle) {
emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath);
emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath);
emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath);

if (!emitSkipped && emittedFilesList) {
if (!emitOnlyDtsFiles) {
Expand Down Expand Up @@ -162,13 +171,13 @@ namespace ts {
onSetSourceFile: setSourceFile,
});

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

// Clean up emit nodes on parse tree
transform.dispose();
}

function emitDeclarationFileOrBundle(sourceFileOrBundle: SourceFile | Bundle, declarationFilePath: string | undefined) {
function emitDeclarationFileOrBundle(sourceFileOrBundle: SourceFile | Bundle, declarationFilePath: string | undefined, declarationMapPath: string | undefined) {
if (!(declarationFilePath && !isInJavaScriptFile(sourceFileOrBundle))) {
return;
}
Expand All @@ -186,25 +195,29 @@ namespace ts {
// resolver hooks
hasGlobalName: resolver.hasGlobalName,

// sourcemap hooks
onEmitSourceMapOfNode: declarationSourceMap.emitNodeWithSourceMap,
onEmitSourceMapOfToken: declarationSourceMap.emitTokenWithSourceMap,
onEmitSourceMapOfPosition: declarationSourceMap.emitPos,
onSetSourceFile: setSourceFileForDeclarationSourceMaps,

// transform hooks
onEmitNode: declarationTransform.emitNodeWithNotification,
substituteNode: declarationTransform.substituteNode,
});
const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit;
emitSkipped = emitSkipped || declBlocked;
if (!declBlocked || emitOnlyDtsFiles) {
const previousState = sourceMap.setState(/*disabled*/ true);
printSourceFileOrBundle(declarationFilePath, /*sourceMapFilePath*/ undefined, declarationTransform.transformed[0], declarationPrinter, /*shouldSkipSourcemap*/ true);
sourceMap.setState(previousState);
printSourceFileOrBundle(declarationFilePath, declarationMapPath, declarationTransform.transformed[0], declarationPrinter, declarationSourceMap);
}
declarationTransform.dispose();
}

function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string, sourceFileOrBundle: SourceFile | Bundle, printer: Printer, shouldSkipSourcemap = false) {
function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, printer: Printer, mapRecorder: SourceMapWriter) {
const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined;
const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined;
const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile];
sourceMap.initialize(jsFilePath, sourceMapFilePath, sourceFileOrBundle);
mapRecorder.initialize(jsFilePath, sourceMapFilePath || "", sourceFileOrBundle, sourceMapDataList);

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

writer.writeLine();

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

// Write the source map
if (!shouldSkipSourcemap && compilerOptions.sourceMap && !compilerOptions.inlineSourceMap) {
writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap.getText(), /*writeByteOrderMark*/ false, sourceFiles);
}

// Record source map data for the test harness.
if (!shouldSkipSourcemap && sourceMapDataList) {
sourceMapDataList.push(sourceMap.getSourceMapData());
if (sourceMapFilePath) {
writeFile(host, emitterDiagnostics, sourceMapFilePath, mapRecorder.getText(), /*writeByteOrderMark*/ false, sourceFiles);
}

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

// Reset state
sourceMap.reset();
mapRecorder.reset();
writer.clear();

currentSourceFile = undefined;
Expand All @@ -250,6 +258,11 @@ namespace ts {
sourceMap.setSourceFile(node);
}

function setSourceFileForDeclarationSourceMaps(node: SourceFile) {
currentSourceFile = node;
declarationSourceMap.setSourceFile(node);
}

function emitHelpers(node: Node, writeLines: (text: string) => void) {
let helpersEmitted = false;
const bundle = node.kind === SyntaxKind.Bundle ? <Bundle>node : undefined;
Expand Down
22 changes: 13 additions & 9 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2113,9 +2113,9 @@ namespace ts {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile");
}

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

if (options.declarationDir) {
Expand All @@ -2127,6 +2127,10 @@ namespace ts {
}
}

if (options.declarationMap && !options.declaration) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "declarationMap", "declaration");
}

if (options.lib && options.noLib) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib");
}
Expand Down Expand Up @@ -2301,21 +2305,21 @@ namespace ts {
return emptyArray;
}

function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string) {
createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2);
function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string, option3?: string) {
createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2, option3);
}

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

function createDiagnosticForOption(onKey: boolean, option1: string, option2: string, message: DiagnosticMessage, arg0: string | number, arg1?: string | number) {
function createDiagnosticForOption(onKey: boolean, option1: string, option2: string, message: DiagnosticMessage, arg0: string | number, arg1?: string | number, arg2?: string | number) {
const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax();
const needCompilerDiagnostic = !compilerOptionsObjectLiteralSyntax ||
!createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1);
!createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1, arg2);

if (needCompilerDiagnostic) {
programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1));
programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2));
}
}

Expand All @@ -2334,10 +2338,10 @@ namespace ts {
return _compilerOptionsObjectLiteralSyntax;
}

function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string, message: DiagnosticMessage, arg0: string | number, arg1?: string | number): boolean {
function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string, message: DiagnosticMessage, arg0: string | number, arg1?: string | number, arg2?: string | number): boolean {
const props = getPropertyAssignment(objectLiteral, key1, key2);
for (const prop of props) {
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile, onKey ? prop.name : prop.initializer, message, arg0, arg1));
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile, onKey ? prop.name : prop.initializer, message, arg0, arg1, arg2));
}
return !!props.length;
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ namespace ts {
return result;
}

export function getPositionOfLineAndCharacter(sourceFile: SourceFile, line: number, character: number): number {
export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number {
return computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text);
}

Expand Down
Loading

0 comments on commit 6af764c

Please sign in to comment.