diff --git a/src/server/session.ts b/src/server/session.ts index 8d6defa1562..d0c58b20922 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -665,9 +665,8 @@ namespace ts.server { if (simplifiedResult) { return this.mapDefinitionInfo(definitions, project); } - else { - return definitions; - } + + return definitions.map(Session.mapToOriginalLocation); } private getDefinitionAndBoundSpan(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.DefinitionInfoAndBoundSpan | DefinitionInfoAndBoundSpan { @@ -691,13 +690,37 @@ namespace ts.server { }; } - return definitionAndBoundSpan; + return { + ...definitionAndBoundSpan, + definitions: definitionAndBoundSpan.definitions.map(Session.mapToOriginalLocation) + }; } private mapDefinitionInfo(definitions: ReadonlyArray, project: Project): ReadonlyArray { return definitions.map(def => this.toFileSpan(def.fileName, def.textSpan, project)); } + /* + * When we map a .d.ts location to .ts, Visual Studio gets confused because there's no associated Roslyn Document in + * the same project which corresponds to the file. VS Code has no problem with this, and luckily we have two protocols. + * This retains the existing behavior for the "simplified" (VS Code) protocol but stores the .d.ts location in a + * set of additional fields, and does the reverse for VS (store the .d.ts location where + * it used to be and stores the .ts location in the additional fields). + */ + private static mapToOriginalLocation(def: T): T { + if (def.originalFileName) { + Debug.assert(def.originalTextSpan !== undefined, "originalTextSpan should be present if originalFileName is"); + return { + ...def, + fileName: def.originalFileName, + textSpan: def.originalTextSpan, + targetFileName: def.fileName, + targetTextSpan: def.textSpan + }; + } + return def; + } + private toFileSpan(fileName: string, textSpan: TextSpan, project: Project): protocol.FileSpan { const ls = project.getLanguageService(); const start = ls.toLineColumnOffset(fileName, textSpan.start); @@ -732,9 +755,8 @@ namespace ts.server { if (simplifiedResult) { return implementations.map(({ fileName, textSpan }) => this.toFileSpan(fileName, textSpan, project)); } - else { - return implementations; - } + + return implementations.map(Session.mapToOriginalLocation); } private getOccurrences(args: protocol.FileLocationRequestArgs): ReadonlyArray { diff --git a/src/services/services.ts b/src/services/services.ts index 700b07085a0..3fcd4e8e151 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1600,10 +1600,10 @@ namespace ts { function makeGetTargetOfMappedPosition( extract: (original: TIn) => sourcemaps.SourceMappableLocation, - create: (result: sourcemaps.SourceMappableLocation, original: TIn) => TIn + create: (result: sourcemaps.SourceMappableLocation, unmapped: TIn, original: TIn) => TIn ) { return getTargetOfMappedPosition; - function getTargetOfMappedPosition(input: TIn): TIn { + function getTargetOfMappedPosition(input: TIn, original = input): TIn { const info = extract(input); if (endsWith(info.fileName, Extension.Dts)) { let file: SourceFileLike = program.getSourceFile(info.fileName); @@ -1617,7 +1617,7 @@ namespace ts { const mapper = getSourceMapper(info.fileName, file); const newLoc = mapper.getOriginalPosition(info); if (newLoc === info) return input; - return getTargetOfMappedPosition(create(newLoc, input)); + return getTargetOfMappedPosition(create(newLoc, input, original), original); } return input; } @@ -1625,7 +1625,7 @@ namespace ts { const getTargetOfMappedDeclarationInfo = makeGetTargetOfMappedPosition( (info: DefinitionInfo) => ({ fileName: info.fileName, position: info.textSpan.start }), - (newLoc, info) => ({ + (newLoc, info, original) => ({ containerKind: info.containerKind, containerName: info.containerName, fileName: newLoc.fileName, @@ -1634,12 +1634,14 @@ namespace ts { textSpan: { start: newLoc.position, length: info.textSpan.length - } + }, + originalFileName: original.fileName, + originalTextSpan: original.textSpan }) ); function getTargetOfMappedDeclarationFiles(infos: ReadonlyArray): DefinitionInfo[] { - return map(infos, getTargetOfMappedDeclarationInfo); + return map(infos, d => getTargetOfMappedDeclarationInfo(d)); } /// Goto definition @@ -1678,12 +1680,14 @@ namespace ts { textSpan: { start: newLoc.position, length: info.textSpan.length - } + }, + originalFileName: info.fileName, + originalTextSpan: info.textSpan }) ); function getTargetOfMappedImplementationLocations(infos: ReadonlyArray): ImplementationLocation[] { - return map(infos, getTargetOfMappedImplementationLocation); + return map(infos, d => getTargetOfMappedImplementationLocation(d)); } function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { diff --git a/src/services/types.ts b/src/services/types.ts index cf2e58a3712..0d15213cb12 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -542,6 +542,13 @@ namespace ts { export interface DocumentSpan { textSpan: TextSpan; fileName: string; + + /** + * If the span represents a location that was remapped (e.g. via a .d.ts.map file), + * then the original filename and span will be specified here + */ + originalTextSpan?: TextSpan; + originalFileName?: string; } export interface RenameLocation extends DocumentSpan { @@ -654,9 +661,7 @@ namespace ts { indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; } - export interface DefinitionInfo { - fileName: string; - textSpan: TextSpan; + export interface DefinitionInfo extends DocumentSpan { kind: ScriptElementKind; name: string; containerKind: ScriptElementKind; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 04279014ecc..3801b18938f 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4703,6 +4703,12 @@ declare namespace ts { interface DocumentSpan { textSpan: TextSpan; fileName: string; + /** + * If the span represents a location that was remapped (e.g. via a .d.ts.map file), + * then the original filename and span will be specified here + */ + originalTextSpan?: TextSpan; + originalFileName?: string; } interface RenameLocation extends DocumentSpan { } @@ -4800,9 +4806,7 @@ declare namespace ts { insertSpaceBeforeTypeAnnotation?: boolean; indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; } - interface DefinitionInfo { - fileName: string; - textSpan: TextSpan; + interface DefinitionInfo extends DocumentSpan { kind: ScriptElementKind; name: string; containerKind: ScriptElementKind; @@ -8412,6 +8416,7 @@ declare namespace ts.server { private getDefinition; private getDefinitionAndBoundSpan; private mapDefinitionInfo; + private static mapToOriginalLocation; private toFileSpan; private getTypeDefinition; private getImplementation; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c637bec4998..2b4f6fe5beb 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4703,6 +4703,12 @@ declare namespace ts { interface DocumentSpan { textSpan: TextSpan; fileName: string; + /** + * If the span represents a location that was remapped (e.g. via a .d.ts.map file), + * then the original filename and span will be specified here + */ + originalTextSpan?: TextSpan; + originalFileName?: string; } interface RenameLocation extends DocumentSpan { } @@ -4800,9 +4806,7 @@ declare namespace ts { insertSpaceBeforeTypeAnnotation?: boolean; indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; } - interface DefinitionInfo { - fileName: string; - textSpan: TextSpan; + interface DefinitionInfo extends DocumentSpan { kind: ScriptElementKind; name: string; containerKind: ScriptElementKind;