Merge pull request #2966 from Microsoft/getTypeDefinitionAtPosition

Get type definition at position
This commit is contained in:
Mohamed Hegazy 2015-05-04 14:51:10 -07:00
commit 15f4b58d6c
18 changed files with 406 additions and 67 deletions

View File

@ -1570,6 +1570,28 @@ module FourSlash {
this.currentCaretPosition = definition.textSpan.start;
}
public goToTypeDefinition(definitionIndex: number) {
if (definitionIndex === 0) {
this.scenarioActions.push('<GoToTypeDefinition />');
}
else {
this.taoInvalidReason = 'GoToTypeDefinition not supported for non-zero definition indices';
}
var definitions = this.languageService.getTypeDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (!definitions || !definitions.length) {
this.raiseError('goToTypeDefinition failed - expected to at least one definition location but got 0');
}
if (definitionIndex >= definitions.length) {
this.raiseError('goToTypeDefinition failed - definitionIndex value (' + definitionIndex + ') exceeds definition list size (' + definitions.length + ')');
}
var definition = definitions[definitionIndex];
this.openFile(definition.fileName);
this.currentCaretPosition = definition.textSpan.start;
}
public verifyDefinitionLocationExists(negative: boolean) {
this.taoInvalidReason = 'verifyDefinitionLocationExists NYI';
@ -1589,8 +1611,18 @@ module FourSlash {
var assertFn = negative ? assert.notEqual : assert.equal;
var definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
var actualCount = definitions && definitions.length || 0;
assertFn(definitions.length, expectedCount, this.messageAtLastKnownMarker("Definitions Count"));
assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Definitions Count"));
}
public verifyTypeDefinitionsCount(negative: boolean, expectedCount: number) {
var assertFn = negative ? assert.notEqual : assert.equal;
var definitions = this.languageService.getTypeDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
var actualCount = definitions && definitions.length || 0;
assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Type definitions Count"));
}
public verifyDefinitionsName(negative: boolean, expectedName: string, expectedContainerName: string) {

View File

@ -342,6 +342,9 @@ module Harness.LanguageService {
getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] {
return unwrapJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position));
}
getTypeDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[]{
return unwrapJSONCallResult(this.shim.getTypeDefinitionAtPosition(fileName, position));
}
getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position));
}

View File

@ -300,6 +300,32 @@ module ts.server {
});
}
getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
var lineOffset = this.positionToOneBasedLineOffset(fileName, position);
var args: protocol.FileLocationRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
};
var request = this.processRequest<protocol.TypeDefinitionRequest>(CommandNames.TypeDefinition, args);
var response = this.processResponse<protocol.TypeDefinitionResponse>(request);
return response.body.map(entry => {
var fileName = entry.file;
var start = this.lineOffsetToPosition(fileName, entry.start);
var end = this.lineOffsetToPosition(fileName, entry.end);
return {
containerKind: "",
containerName: "",
fileName: fileName,
textSpan: ts.createTextSpanFromBounds(start, end),
kind: "",
name: ""
};
});
}
findReferences(fileName: string, position: number): ReferencedSymbol[]{
// Not yet implemented.
return [];

View File

@ -773,7 +773,7 @@ module ts.server {
findConfigFile(searchPath: string): string {
while (true) {
var fileName = ts.combinePaths(searchPath, "tsconfig.json");
if (sys.fileExists(fileName)) {
if (this.host.fileExists(fileName)) {
return fileName;
}
var parentPath = ts.getDirectoryPath(searchPath);
@ -923,7 +923,7 @@ module ts.server {
var proj = this.createProject(configFilename, projectOptions);
for (var i = 0, len = parsedCommandLine.fileNames.length; i < len; i++) {
var rootFilename = parsedCommandLine.fileNames[i];
if (ts.sys.fileExists(rootFilename)) {
if (this.host.fileExists(rootFilename)) {
var info = this.openFile(rootFilename, clientFileName == rootFilename);
proj.addRoot(info);
}

View File

@ -125,6 +125,14 @@ declare module ts.server.protocol {
export interface DefinitionRequest extends FileLocationRequest {
}
/**
* Go to type request; value of command field is
* "typeDefinition". Return response giving the file locations that
* define the type for the symbol found in file at location line, col.
*/
export interface TypeDefinitionRequest extends FileLocationRequest {
}
/**
* Location in source code expressed as (one-based) line and character offset.
*/
@ -165,6 +173,13 @@ declare module ts.server.protocol {
body?: FileSpan[];
}
/**
* Definition response message. Gives text range for definition.
*/
export interface TypeDefinitionResponse extends Response {
body?: FileSpan[];
}
/**
* Get occurrences request; value of command field is
* "occurrences". Return response giving spans that are relevant

View File

@ -97,6 +97,7 @@ module ts.server {
export var Rename = "rename";
export var Saveto = "saveto";
export var SignatureHelp = "signatureHelp";
export var TypeDefinition = "typeDefinition";
export var Unknown = "unknown";
}
@ -285,7 +286,29 @@ module ts.server {
}));
}
getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[] {
getTypeDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] {
var file = ts.normalizePath(fileName);
var project = this.projectService.getProjectForFile(file);
if (!project) {
throw Errors.NoProject;
}
var compilerService = project.compilerService;
var position = compilerService.host.lineOffsetToPosition(file, line, offset);
var definitions = compilerService.languageService.getTypeDefinitionAtPosition(file, position);
if (!definitions) {
return undefined;
}
return definitions.map(def => ({
file: def.fileName,
start: compilerService.host.positionToLineOffset(def.fileName, def.textSpan.start),
end: compilerService.host.positionToLineOffset(def.fileName, ts.textSpanEnd(def.textSpan))
}));
}
getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[]{
fileName = ts.normalizePath(fileName);
let project = this.projectService.getProjectForFile(fileName);
@ -817,6 +840,11 @@ module ts.server {
response = this.getDefinition(defArgs.line, defArgs.offset, defArgs.file);
break;
}
case CommandNames.TypeDefinition: {
var defArgs = <protocol.FileLocationRequestArgs>request.arguments;
response = this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file);
break;
}
case CommandNames.References: {
var refArgs = <protocol.FileLocationRequestArgs>request.arguments;
response = this.getReferences(refArgs.line, refArgs.offset, refArgs.file);

View File

@ -1001,6 +1001,8 @@ module ts {
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[];
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[];
getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[];
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[];
findReferences(fileName: string, position: number): ReferencedSymbol[];
getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[];
@ -3502,19 +3504,6 @@ module ts {
return ScriptElementKind.unknown;
}
function getTypeKind(type: Type): string {
let flags = type.getFlags();
if (flags & TypeFlags.Enum) return ScriptElementKind.enumElement;
if (flags & TypeFlags.Class) return ScriptElementKind.classElement;
if (flags & TypeFlags.Interface) return ScriptElementKind.interfaceElement;
if (flags & TypeFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
if (flags & TypeFlags.Intrinsic) return ScriptElementKind.primitiveType;
if (flags & TypeFlags.StringLiteral) return ScriptElementKind.primitiveType;
return ScriptElementKind.unknown;
}
function getSymbolModifiers(symbol: Symbol): string {
return symbol && symbol.declarations && symbol.declarations.length > 0
? getNodeModifiers(symbol.declarations[0])
@ -3929,6 +3918,71 @@ module ts {
};
}
function getDefinitionFromSymbol(symbol: Symbol, node: Node): DefinitionInfo[] {
let typeChecker = program.getTypeChecker();
let result: DefinitionInfo[] = [];
let declarations = symbol.getDeclarations();
let symbolName = typeChecker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol
let symbolKind = getSymbolKind(symbol, node);
let containerSymbol = symbol.parent;
let containerName = containerSymbol ? typeChecker.symbolToString(containerSymbol, node) : "";
if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) &&
!tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) {
// Just add all the declarations.
forEach(declarations, declaration => {
result.push(createDefinitionInfo(declaration, symbolKind, symbolName, containerName));
});
}
return result;
function tryAddConstructSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
// Applicable only if we are in a new expression, or we are on a constructor declaration
// and in either case the symbol has a construct signature definition, i.e. class
if (isNewExpressionTarget(location) || location.kind === SyntaxKind.ConstructorKeyword) {
if (symbol.flags & SymbolFlags.Class) {
let classDeclaration = <ClassDeclaration>symbol.getDeclarations()[0];
Debug.assert(classDeclaration && classDeclaration.kind === SyntaxKind.ClassDeclaration);
return tryAddSignature(classDeclaration.members, /*selectConstructors*/ true, symbolKind, symbolName, containerName, result);
}
}
return false;
}
function tryAddCallSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
if (isCallExpressionTarget(location) || isNewExpressionTarget(location) || isNameOfFunctionDeclaration(location)) {
return tryAddSignature(symbol.declarations, /*selectConstructors*/ false, symbolKind, symbolName, containerName, result);
}
return false;
}
function tryAddSignature(signatureDeclarations: Declaration[], selectConstructors: boolean, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
let declarations: Declaration[] = [];
let definition: Declaration;
forEach(signatureDeclarations, d => {
if ((selectConstructors && d.kind === SyntaxKind.Constructor) ||
(!selectConstructors && (d.kind === SyntaxKind.FunctionDeclaration || d.kind === SyntaxKind.MethodDeclaration || d.kind === SyntaxKind.MethodSignature))) {
declarations.push(d);
if ((<FunctionLikeDeclaration>d).body) definition = d;
}
});
if (definition) {
result.push(createDefinitionInfo(definition, symbolKind, symbolName, containerName));
return true;
}
else if (declarations.length) {
result.push(createDefinitionInfo(lastOrUndefined(declarations), symbolKind, symbolName, containerName));
return true;
}
return false;
}
}
/// Goto definition
function getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
synchronizeHostData();
@ -4003,67 +4057,47 @@ module ts {
declaration => createDefinitionInfo(declaration, shorthandSymbolKind, shorthandSymbolName, shorthandContainerName));
}
let result: DefinitionInfo[] = [];
let declarations = symbol.getDeclarations();
let symbolName = typeChecker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol
let symbolKind = getSymbolKind(symbol, node);
let containerSymbol = symbol.parent;
let containerName = containerSymbol ? typeChecker.symbolToString(containerSymbol, node) : "";
return getDefinitionFromSymbol(symbol, node);
}
if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) &&
!tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) {
// Just add all the declarations.
forEach(declarations, declaration => {
result.push(createDefinitionInfo(declaration, symbolKind, symbolName, containerName));
});
/// Goto type
function getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
synchronizeHostData();
let sourceFile = getValidSourceFile(fileName);
let node = getTouchingPropertyName(sourceFile, position);
if (!node) {
return undefined;
}
return result;
let typeChecker = program.getTypeChecker();
function tryAddConstructSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
// Applicable only if we are in a new expression, or we are on a constructor declaration
// and in either case the symbol has a construct signature definition, i.e. class
if (isNewExpressionTarget(location) || location.kind === SyntaxKind.ConstructorKeyword) {
if (symbol.flags & SymbolFlags.Class) {
let classDeclaration = <ClassDeclaration>symbol.getDeclarations()[0];
Debug.assert(classDeclaration && classDeclaration.kind === SyntaxKind.ClassDeclaration);
return tryAddSignature(classDeclaration.members, /*selectConstructors*/ true, symbolKind, symbolName, containerName, result);
}
}
return false;
let symbol = typeChecker.getSymbolAtLocation(node);
if (!symbol) {
return undefined;
}
function tryAddCallSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
if (isCallExpressionTarget(location) || isNewExpressionTarget(location) || isNameOfFunctionDeclaration(location)) {
return tryAddSignature(symbol.declarations, /*selectConstructors*/ false, symbolKind, symbolName, containerName, result);
}
return false;
let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
if (!type) {
return undefined;
}
function tryAddSignature(signatureDeclarations: Declaration[], selectConstructors: boolean, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
let declarations: Declaration[] = [];
let definition: Declaration;
forEach(signatureDeclarations, d => {
if ((selectConstructors && d.kind === SyntaxKind.Constructor) ||
(!selectConstructors && (d.kind === SyntaxKind.FunctionDeclaration || d.kind === SyntaxKind.MethodDeclaration || d.kind === SyntaxKind.MethodSignature))) {
declarations.push(d);
if ((<FunctionLikeDeclaration>d).body) definition = d;
if (type.flags & TypeFlags.Union) {
var result: DefinitionInfo[] = [];
forEach((<UnionType>type).types, t => {
if (t.symbol) {
result.push(...getDefinitionFromSymbol(t.symbol, node));
}
});
if (definition) {
result.push(createDefinitionInfo(definition, symbolKind, symbolName, containerName));
return true;
}
else if (declarations.length) {
result.push(createDefinitionInfo(lastOrUndefined(declarations), symbolKind, symbolName, containerName));
return true;
}
return false;
return result;
}
if (!type.symbol) {
return undefined;
}
return getDefinitionFromSymbol(type.symbol, node);
}
function getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
@ -6496,6 +6530,7 @@ module ts {
getSignatureHelpItems,
getQuickInfoAtPosition,
getDefinitionAtPosition,
getTypeDefinitionAtPosition,
getReferencesAtPosition,
findReferences,
getOccurrencesAtPosition,

View File

@ -132,6 +132,14 @@ module ts {
*/
getDefinitionAtPosition(fileName: string, position: number): string;
/**
* Returns a JSON-encoded value of the type:
* { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string }
*
* Or undefined value if no definition can be found.
*/
getTypeDefinitionAtPosition(fileName: string, position: number): string;
/**
* Returns a JSON-encoded value of the type:
* { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean }[]
@ -589,6 +597,20 @@ module ts {
});
}
/// GOTO Type
/**
* Computes the definition location of the type of the symbol
* at the requested position.
*/
public getTypeDefinitionAtPosition(fileName: string, position: number): string {
return this.forwardJSONCall(
"getTypeDefinitionAtPosition('" + fileName + "', " + position + ")",
() => {
return this.languageService.getTypeDefinitionAtPosition(fileName, position);
});
}
public getRenameInfo(fileName: string, position: number): string {
return this.forwardJSONCall(
"getRenameInfo('" + fileName + "', " + position + ")",

View File

@ -119,6 +119,10 @@ module FourSlashInterface {
FourSlash.currentTestState.goToDefinition(definitionIndex);
}
public type(definitionIndex: number = 0) {
FourSlash.currentTestState.goToTypeDefinition(definitionIndex);
}
public position(position: number, fileIndex?: number);
public position(position: number, fileName?: string);
public position(position: number, fileNameOrIndex?: any) {
@ -234,6 +238,10 @@ module FourSlashInterface {
FourSlash.currentTestState.verifyDefinitionsCount(this.negative, expectedCount);
}
public typeDefinitionCountIs(expectedCount: number) {
FourSlash.currentTestState.verifyTypeDefinitionsCount(this.negative, expectedCount);
}
public definitionLocationExists() {
FourSlash.currentTestState.verifyDefinitionLocationExists(this.negative);
}

View File

@ -0,0 +1,14 @@
/// <reference path='fourslash.ts' />
// @Filename: goToTypeDefinition_Definition.ts
/////*definition*/class C {
//// p;
////}
////var c: C;
// @Filename: goToTypeDefinition_Consumption.ts
/////*reference*/c = undefined;
goTo.marker('reference');
goTo.type();
verify.caretAtMarker('definition');

View File

@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />
// @Filename: goToTypeDefinition2_Definition.ts
/////*definition*/interface I1 {
//// p;
////}
////type propertyType = I1;
////interface I2 {
//// property: propertyType;
////}
// @Filename: goToTypeDefinition2_Consumption.ts
////var i2: I2;
////i2.prop/*reference*/erty;
goTo.marker('reference');
goTo.type();
verify.caretAtMarker('definition');

View File

@ -0,0 +1,24 @@
/// <reference path='fourslash.ts' />
// @Filename: goToTypeDefinitioAliases_module1.ts
/////*definition*/interface I {
//// p;
////}
////export {I as I2};
// @Filename: goToTypeDefinitioAliases_module2.ts
////import {I2 as I3} from "goToTypeDefinitioAliases_module1";
////var v1: I3;
////export {v1 as v2};
// @Filename: goToTypeDefinitioAliases_module3.ts
////import {/*reference1*/v2 as v3} from "goToTypeDefinitioAliases_module2";
/////*reference2*/v3;
goTo.marker('reference1');
goTo.type();
verify.caretAtMarker('definition');
goTo.marker('reference2');
goTo.type();
verify.caretAtMarker('definition');

View File

@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
/////*definition*/enum E {
//// value1,
//// value2
////}
////var x = E.value2;
////
/////*reference*/x;
goTo.marker('reference');
goTo.type();
verify.caretAtMarker('definition');

View File

@ -0,0 +1,19 @@
/// <reference path='fourslash.ts' />
// @Filename: goToTypeDefinitioAliases_module1.ts
/////*definition*/module M {
//// export var p;
////}
////var m: typeof M;
// @Filename: goToTypeDefinitioAliases_module3.ts
/////*reference1*/M;
/////*reference2*/m;
goTo.marker('reference1');
goTo.type();
verify.caretAtMarker('definition');
goTo.marker('reference2');
goTo.type();
verify.caretAtMarker('definition');

View File

@ -0,0 +1,25 @@
/// <reference path='fourslash.ts' />
// @Filename: module1.ts
////var w: {a: number};
////var x = "string";
////var y: number | string;
////var z; // any
// @Filename: module2.ts
////w./*reference1*/a;
/////*reference2*/x;
/////*reference3*/y;
/////*reference4*/y;
goTo.marker('reference1');
verify.typeDefinitionCountIs(0);
goTo.marker('reference1');
verify.typeDefinitionCountIs(0);
goTo.marker('reference2');
verify.typeDefinitionCountIs(0);
goTo.marker('reference4');
verify.typeDefinitionCountIs(0);

View File

@ -0,0 +1,31 @@
/// <reference path='fourslash.ts' />
/////*definition0*/class C {
//// p;
////}
////
/////*definition1*/interface I {
//// x;
////}
////
////module M {
//// /*definition2*/export interface I {
//// y;
//// }
////}
////
////var x: C | I | M.I;
////
/////*reference*/x;
goTo.marker('reference');
goTo.type(0);
verify.caretAtMarker('definition0');
goTo.marker('reference');
goTo.type(1);
verify.caretAtMarker('definition1');
goTo.marker('reference');
goTo.type(2);
verify.caretAtMarker('definition2');

View File

@ -0,0 +1,12 @@
/// <reference path="../fourslash.ts"/>
// @Filename: b.ts
////import n = require('a');
////var x/*1*/ = new n.Foo();
// @Filename: a.ts
//// /*2*/export class Foo {}
goTo.marker('1');
goTo.type();
verify.caretAtMarker('2');

View File

@ -0,0 +1,14 @@
/// <reference path='fourslash.ts' />
// @Filename: goToTypeDefinition_Definition.ts
/////*definition*/class C {
//// p;
////}
////var c: C;
// @Filename: goToTypeDefinition_Consumption.ts
/////*reference*/c = undefined;
goTo.marker('reference');
goTo.type();
verify.caretAtMarker('definition');