Skip to content

Commit 449bd8f

Browse files
committed
feat(server): Definitions for sources compiled with declarationMap go to original source
The TypeScript language service has the ability to map definitions back to their original source when the library code was compiled with `declarationMap`. However, this functionality is not exposed as public API. As a result, these methods could change in the future without our knowledge. resolves #1588
1 parent 3bd10ac commit 449bd8f

File tree

2 files changed

+76
-3
lines changed

2 files changed

+76
-3
lines changed

server/src/session.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {readNgCompletionData, tsCompletionEntryToLspCompletionItem} from './comp
2020
import {tsDiagnosticToLspDiagnostic} from './diagnostic';
2121
import {resolveAndRunNgcc} from './ngcc';
2222
import {ServerHost} from './server_host';
23-
import {filePathToUri, isConfiguredProject, isDebugMode, lspPositionToTsPosition, lspRangeToTsPositions, MruTracker, tsDisplayPartsToText, tsTextSpanToLspRange, uriToFilePath} from './utils';
23+
import {filePathToUri, getMappedDefinitionInfo, isConfiguredProject, isDebugMode, lspPositionToTsPosition, lspRangeToTsPositions, MruTracker, tsDisplayPartsToText, tsTextSpanToLspRange, uriToFilePath} from './utils';
2424

2525
export interface SessionOptions {
2626
host: ServerHost;
@@ -924,9 +924,16 @@ export class Session {
924924
if (!scriptInfo && d.textSpan.length > 0) {
925925
continue;
926926
}
927-
const range = scriptInfo ? tsTextSpanToLspRange(scriptInfo, d.textSpan) : EMPTY_RANGE;
928927

929-
const targetUri = filePathToUri(d.fileName);
928+
let mappedInfo = d;
929+
let range = EMPTY_RANGE;
930+
if (scriptInfo) {
931+
const project = this.getDefaultProjectForScriptInfo(scriptInfo);
932+
mappedInfo = project ? getMappedDefinitionInfo(d, project) : mappedInfo;
933+
range = tsTextSpanToLspRange(scriptInfo, mappedInfo.textSpan);
934+
}
935+
936+
const targetUri = filePathToUri(mappedInfo.fileName);
930937
results.push({
931938
originSelectionRange,
932939
targetUri,

server/src/utils.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,69 @@ export class MruTracker {
143143
export function tsDisplayPartsToText(parts: ts.SymbolDisplayPart[]): string {
144144
return parts.map(dp => dp.text).join('');
145145
}
146+
147+
interface DocumentPosition {
148+
fileName: string;
149+
pos: number;
150+
}
151+
152+
/**
153+
*
154+
* This function attempts to use *internal* TypeScript APIs to find the original source spans for
155+
* the `ts.DefinitionInfo` using source maps. If it fails, this function returns the same
156+
* `ts.DefinitionInfo` that was passed in.
157+
*
158+
* @see https://github.com/angular/vscode-ng-language-service/issues/1588
159+
*/
160+
export function getMappedDefinitionInfo(
161+
info: ts.DefinitionInfo, project: ts.server.Project): ts.DefinitionInfo {
162+
try {
163+
const mappedDocumentSpan = getMappedDocumentSpan(info, project);
164+
return {...info, ...mappedDocumentSpan};
165+
} catch {
166+
return info;
167+
}
168+
}
169+
170+
function getMappedDocumentSpan(
171+
documentSpan: ts.DocumentSpan, project: ts.server.Project): ts.DocumentSpan|undefined {
172+
const newPosition = getMappedLocation(documentSpanLocation(documentSpan), project);
173+
if (!newPosition) return undefined;
174+
return {
175+
fileName: newPosition.fileName,
176+
textSpan: {start: newPosition.pos, length: documentSpan.textSpan.length},
177+
originalFileName: documentSpan.fileName,
178+
originalTextSpan: documentSpan.textSpan,
179+
contextSpan: getMappedContextSpan(documentSpan, project),
180+
originalContextSpan: documentSpan.contextSpan
181+
};
182+
}
183+
184+
function getMappedLocation(
185+
location: DocumentPosition, project: ts.server.Project): DocumentPosition|undefined {
186+
const mapsTo = (project as any).getSourceMapper().tryGetSourcePosition(location);
187+
return mapsTo &&
188+
(project.projectService as any).fileExists(ts.server.toNormalizedPath(mapsTo.fileName)) ?
189+
mapsTo :
190+
undefined;
191+
}
192+
193+
function documentSpanLocation({fileName, textSpan}: ts.DocumentSpan): DocumentPosition {
194+
return {fileName, pos: textSpan.start};
195+
}
196+
197+
function getMappedContextSpan(
198+
documentSpan: ts.DocumentSpan, project: ts.server.Project): ts.TextSpan|undefined {
199+
const contextSpanStart = documentSpan.contextSpan &&
200+
getMappedLocation({fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start},
201+
project);
202+
const contextSpanEnd = documentSpan.contextSpan &&
203+
getMappedLocation({
204+
fileName: documentSpan.fileName,
205+
pos: documentSpan.contextSpan.start + documentSpan.contextSpan.length
206+
},
207+
project);
208+
return contextSpanStart && contextSpanEnd ?
209+
{start: contextSpanStart.pos, length: contextSpanEnd.pos - contextSpanStart.pos} :
210+
undefined;
211+
}

0 commit comments

Comments
 (0)