diff --git a/README.md b/README.md index 7ab0fff58..f2488f36a 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ This is a language server for JavaScript and TypeScript that adheres to the [Lan - Hovers - Goto definition + - Goto type definition - Find all references - Document symbols - Workspace symbol search diff --git a/src/test/typescript-service-helpers.ts b/src/test/typescript-service-helpers.ts index 0099c6525..36458c42d 100644 --- a/src/test/typescript-service-helpers.ts +++ b/src/test/typescript-service-helpers.ts @@ -143,6 +143,8 @@ export function describeTypeScriptService( 'let target = i.target;', ].join('\n'), ], + [rootUri + 'foo/f.ts', ['import {Foo} from "./b";', '', 'let foo: Foo = Object({});'].join('\n')], + [rootUri + 'foo/g.ts', ['class Foo = {}', '', 'let foo: Foo = Object({});'].join('\n')], ]) ) ) @@ -224,6 +226,68 @@ export function describeTypeScriptService( ]) }) }) + + describe('textDocumentTypeDefinition()', function(this: TestContext & ISuiteCallbackContext): void { + specify('in other file', async function(this: TestContext & ITestCallbackContext): Promise { + const result: Location[] = await this.service + .textDocumentTypeDefinition({ + textDocument: { + uri: rootUri + 'foo/f.ts', + }, + position: { + line: 2, + character: 5, + }, + }) + .reduce(applyReducer, null as any) + .toPromise() + assert.deepEqual(result, [ + { + uri: rootUri + 'foo/b.ts', + range: { + start: { + line: 1, + character: 13, + }, + end: { + line: 1, + character: 16, + }, + }, + }, + ]) + }) + specify('in same file', async function(this: TestContext & ITestCallbackContext): Promise { + const result: Location[] = await this.service + .textDocumentTypeDefinition({ + textDocument: { + uri: rootUri + 'foo/g.ts', + }, + position: { + line: 2, + character: 5, + }, + }) + .reduce(applyReducer, null as any) + .toPromise() + assert.deepEqual(result, [ + { + uri: rootUri + 'foo/g.ts', + range: { + start: { + line: 0, + character: 6, + }, + end: { + line: 0, + character: 9, + }, + }, + }, + ]) + }) + }) + describe('textDocumentXdefinition()', function(this: TestContext & ISuiteCallbackContext): void { specify('on interface field reference', async function( this: TestContext & ITestCallbackContext diff --git a/src/typescript-service.ts b/src/typescript-service.ts index a09b00315..fbb00aa98 100644 --- a/src/typescript-service.ts +++ b/src/typescript-service.ts @@ -297,6 +297,7 @@ export class TypeScriptService { triggerCharacters: ['(', ','], }, definitionProvider: true, + typeDefinitionProvider: true, referencesProvider: true, documentSymbolProvider: true, workspaceSymbolProvider: true, @@ -362,7 +363,19 @@ export class TypeScriptService { */ public textDocumentDefinition(params: TextDocumentPositionParams, span = new Span()): Observable { - return this._getDefinitionLocations(params, span) + return this._getDefinitionLocations(params, span, false) + .map((location: Location): Operation => ({ op: 'add', path: '/-', value: location })) + .startWith({ op: 'add', path: '', value: [] }) + } + + /** + * The goto type definition request is sent from the client to the server to resolve the type + * location of a symbol at a given text document position. + * + * @return Observable of JSON Patches that build a `Location[]` result + */ + public textDocumentTypeDefinition(params: TextDocumentPositionParams, span = new Span()): Observable { + return this._getDefinitionLocations(params, span, true) .map((location: Location): Operation => ({ op: 'add', path: '/-', value: location })) .startWith({ op: 'add', path: '', value: [] }) } @@ -370,7 +383,11 @@ export class TypeScriptService { /** * Returns an Observable of all definition locations found for a symbol. */ - protected _getDefinitionLocations(params: TextDocumentPositionParams, span = new Span()): Observable { + protected _getDefinitionLocations( + params: TextDocumentPositionParams, + span = new Span(), + goToType = false + ): Observable { const uri = normalizeUri(params.textDocument.uri) // Fetch files needed to resolve definition @@ -392,9 +409,9 @@ export class TypeScriptService { params.position.line, params.position.character ) - const definitions: ts.DefinitionInfo[] | undefined = configuration - .getService() - .getDefinitionAtPosition(fileName, offset) + const definitions: ts.DefinitionInfo[] | undefined = goToType + ? configuration.getService().getTypeDefinitionAtPosition(fileName, offset) + : configuration.getService().getDefinitionAtPosition(fileName, offset) return Observable.from(definitions || []).map((definition): Location => { const sourceFile = this._getSourceFile(configuration, definition.fileName, span)