Go to Implementation

This commit is contained in:
Richard Knoll 2016-08-22 13:57:40 -07:00
parent a0137597f9
commit 111e50921c
61 changed files with 1674 additions and 33 deletions

View File

@ -103,7 +103,8 @@ namespace ts {
getJsxElementAttributesType,
getJsxIntrinsicTagNames,
isOptionalParameter
isOptionalParameter,
isTypeAssignableTo
};
const tupleTypes = createMap<TupleType>();

View File

@ -1889,6 +1889,7 @@ namespace ts {
getJsxElementAttributesType(elementNode: JsxOpeningLikeElement): Type;
getJsxIntrinsicTagNames(): Symbol[];
isOptionalParameter(node: ParameterDeclaration): boolean;
isTypeAssignableTo(source: Type, target: Type): boolean;
// Should not be called directly. Should only be accessed through the Program instance.
/* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];

View File

@ -1669,6 +1669,11 @@ namespace ts {
return false;
}
export function isFunctionDeclarationIdentifierName(node: Identifier): boolean {
return node.parent.kind === SyntaxKind.FunctionDeclaration &&
(<FunctionDeclaration>node.parent).name === node;
}
// An alias symbol is created by one of the following declarations:
// import <symbol> = ...
// import <symbol> from ...

View File

@ -1604,6 +1604,15 @@ namespace FourSlash {
assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Type definitions Count"));
}
public verifyImplementationsCount(negative: boolean, expectedCount: number) {
const assertFn = negative ? assert.notEqual : assert.equal;
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
const actualCount = implementations && implementations.length || 0;
assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Implementations Count"));
}
public verifyDefinitionsName(negative: boolean, expectedName: string, expectedContainerName: string) {
const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
const actualDefinitionName = definitions && definitions.length ? definitions[0].name : "";
@ -1618,6 +1627,47 @@ namespace FourSlash {
}
}
public goToImplementation(implIndex: number) {
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (!implementations || !implementations.length) {
this.raiseError("goToImplementation failed - expected to at least one implementation location but got 0");
}
if (implIndex >= implementations.length) {
this.raiseError(`goToImplementation failed - implIndex value (${implIndex}) exceeds implementation list size (${implementations.length})`);
}
const implementation = implementations[implIndex];
this.openFile(implementation.fileName);
this.currentCaretPosition = implementation.textSpan.start;
}
public verifyRangesInImplementationList() {
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (!implementations || !implementations.length) {
this.raiseError("verifyRangesInImplementationList failed - expected to at least one implementation location but got 0");
}
const ranges = this.getRanges();
if (!ranges || !ranges.length) {
this.raiseError("verifyRangesInImplementationList failed - expected to at least one range in test source");
}
for (const range of ranges) {
let rangeIsPresent = false;
const length = range.end - range.start;
for (const impl of implementations) {
if (range.fileName === impl.fileName && range.start === impl.textSpan.start && length === impl.textSpan.length) {
rangeIsPresent = true;
break;
}
}
assert.isTrue(rangeIsPresent, `No implementation found for range ${range.start}, ${range.end} in ${range.fileName}: ${this.rangeText(range)}`);
}
assert.equal(implementations.length, ranges.length, `Different number of implementations (${implementations.length}) and ranges (${ranges.length})`);
}
public getMarkers(): Marker[] {
// Return a copy of the list
return this.testData.markers.slice(0);
@ -2768,6 +2818,10 @@ namespace FourSlashInterface {
this.state.goToTypeDefinition(definitionIndex);
}
public implementation(implementationIndex = 0) {
this.state.goToImplementation(implementationIndex);
}
public position(position: number, fileIndex?: number): void;
public position(position: number, fileName?: string): void;
public position(position: number, fileNameOrIndex?: any): void {
@ -2876,6 +2930,10 @@ namespace FourSlashInterface {
this.state.verifyTypeDefinitionsCount(this.negative, expectedCount);
}
public implementationCountIs(expectedCount: number) {
this.state.verifyImplementationsCount(this.negative, expectedCount);
}
public definitionLocationExists() {
this.state.verifyDefinitionLocationExists(this.negative);
}
@ -3113,6 +3171,10 @@ namespace FourSlashInterface {
public ProjectInfo(expected: string[]) {
this.state.verifyProjectInfo(expected);
}
public allRangesAppearInImplementationList() {
this.state.verifyRangesInImplementationList();
}
}
export class Edit {

View File

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

View File

@ -353,6 +353,28 @@ namespace ts.server {
});
}
getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
};
const request = this.processRequest<protocol.ImplementationRequest>(CommandNames.Implementation, args);
const response = this.processResponse<protocol.ImplementationResponse>(request);
return response.body.map(entry => {
const fileName = entry.file;
const start = this.lineOffsetToPosition(fileName, entry.start);
const end = this.lineOffsetToPosition(fileName, entry.end);
return {
fileName,
textSpan: ts.createTextSpanFromBounds(start, end)
};
});
}
findReferences(fileName: string, position: number): ReferencedSymbol[] {
// Not yet implemented.
return [];

View File

@ -193,6 +193,14 @@ declare namespace ts.server.protocol {
export interface TypeDefinitionRequest extends FileLocationRequest {
}
/**
* Go to implementation request; value of command field is
* "implementation". Return response giving the file locations that
* implement the symbol found in file at location line, col.
*/
export interface ImplementationRequest extends FileLocationRequest {
}
/**
* Location in source code expressed as (one-based) line and character offset.
*/
@ -240,6 +248,13 @@ declare namespace ts.server.protocol {
body?: FileSpan[];
}
/**
* Implementation response message. Gives text range for implementations.
*/
export interface ImplementationResponse extends Response {
body?: FileSpan[];
}
/**
* Get occurrences request; value of command field is
* "occurrences". Return response giving spans that are relevant

View File

@ -111,6 +111,7 @@ namespace ts.server {
export const Formatonkey = "formatonkey";
export const Geterr = "geterr";
export const GeterrForProject = "geterrForProject";
export const Implementation = "implementation";
export const SemanticDiagnosticsSync = "semanticDiagnosticsSync";
export const SyntacticDiagnosticsSync = "syntacticDiagnosticsSync";
export const NavBar = "navbar";
@ -357,6 +358,28 @@ namespace ts.server {
}));
}
private getImplementation(line: number, offset: number, fileName: string): protocol.FileSpan[] {
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (!project || project.languageServiceDiabled) {
throw Errors.NoProject;
}
const compilerService = project.compilerService;
const position = compilerService.host.lineOffsetToPosition(file, line, offset);
const implementations = compilerService.languageService.getImplementationAtPosition(file, position);
if (!implementations) {
return undefined;
}
return implementations.map(impl => ({
file: impl.fileName,
start: compilerService.host.positionToLineOffset(impl.fileName, impl.textSpan.start),
end: compilerService.host.positionToLineOffset(impl.fileName, ts.textSpanEnd(impl.textSpan))
}));
}
private getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[] {
fileName = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(fileName);
@ -1074,6 +1097,10 @@ namespace ts.server {
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
return { response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
},
[CommandNames.Implementation]: (request: protocol.Request) => {
const implArgs = <protocol.FileLocationRequestArgs>request.arguments;
return { response: this.getImplementation(implArgs.line, implArgs.offset, implArgs.file), responseRequired: true };
},
[CommandNames.References]: (request: protocol.Request) => {
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
return { response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };

View File

@ -1213,6 +1213,7 @@ namespace ts {
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[];
getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[];
getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[];
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[];
findReferences(fileName: string, position: number): ReferencedSymbol[];
@ -1301,6 +1302,11 @@ namespace ts {
isDefinition: boolean;
}
export interface ImplementationLocation {
textSpan: TextSpan;
fileName: string;
}
export interface DocumentHighlights {
fileName: string;
highlightSpans: HighlightSpan[];
@ -5288,6 +5294,149 @@ namespace ts {
return getDefinitionFromSymbol(type.symbol, node);
}
/// Goto implementation
function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] {
synchronizeHostData();
const entries: ImplementationLocation[] = [];
const node = getTouchingPropertyName(getValidSourceFile(fileName), position);
const typeChecker = program.getTypeChecker();
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
const entry = getReferenceEntryForShorthandPropertyAssignment(node, typeChecker);
entries.push({
textSpan: entry.textSpan,
fileName: entry.fileName
});
}
else if (definitionIsImplementation(node, typeChecker)) {
const definitions = getDefinitionAtPosition(fileName, position);
forEach(definitions, (definition: DefinitionInfo) => {
entries.push({
textSpan: definition.textSpan,
fileName: definition.fileName
});
});
}
else {
// Do a search for all references and filter them down to implementations only
const result = getReferencedSymbolsForNode(node, program.getSourceFiles(), /*findInStrings*/false, /*findInComments*/false, /*implementations*/true);
forEach(result, referencedSymbol => {
if (referencedSymbol.references) {
forEach(referencedSymbol.references, entry => {
entries.push({
textSpan: entry.textSpan,
fileName: entry.fileName
});
});
}
});
}
return entries;
}
/**
* Returns true if the implementation for this node is the same as its definition
*/
function definitionIsImplementation(node: Node, typeChecker: TypeChecker) {
if (node.kind === SyntaxKind.SuperKeyword || node.kind === SyntaxKind.ThisKeyword) {
return true;
}
if (node.parent.kind === SyntaxKind.PropertyAccessExpression || node.parent.kind === SyntaxKind.ElementAccessExpression) {
const expression = (<ElementAccessExpression | PropertyAccessExpression>node.parent).expression;
// Members of "this" and "super" only have one possible implementation, so no need to find
// all references. Similarly, for the left hand side of the expression it only really
// makes sense to return the definition
if (node === expression || expression.kind === SyntaxKind.SuperKeyword || expression.kind === SyntaxKind.ThisKeyword) {
return true;
}
// Check to see if this is a property that can have multiple implementations by determining
// if the parent is an interface (or class, or union/intersection)
const expressionType = typeChecker.getTypeAtLocation(expression);
if (expressionType && expressionType.getFlags() & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.UnionOrIntersection)) {
return false;
}
// Also check the right hand side to see if this is a type being accessed on a namespace/module
const rightHandType = typeChecker.getTypeAtLocation(node);
return rightHandType && !(rightHandType.getFlags() & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.UnionOrIntersection));
}
const symbol = typeChecker.getSymbolAtLocation(node);
return symbol && !isClassOrInterfaceReference(symbol) && !(symbol.parent && isClassOrInterfaceReference(symbol.parent));
}
function getReferenceEntryForShorthandPropertyAssignment(node: Node, typeChecker: TypeChecker) {
const refSymbol = typeChecker.getSymbolAtLocation(node);
const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration);
if (shorthandSymbol) {
const shorthandDeclarations = shorthandSymbol.getDeclarations();
if (shorthandDeclarations.length === 1) {
return getReferenceEntryFromNode(shorthandDeclarations[0]);
}
else if (shorthandDeclarations.length > 1) {
// This can happen when the property being assigned is a constructor for a
// class that also has interface declarations with the same name. We just want
// the class itself
return forEach(shorthandDeclarations, declaration => {
if (declaration.kind === SyntaxKind.ClassDeclaration) {
return getReferenceEntryFromNode(declaration);
}
});
}
}
}
function isClassOrInterfaceReference(toCheck: Symbol) {
return toCheck.getFlags() & (SymbolFlags.Class | SymbolFlags.Interface);
}
function isIdentifierOfImplementation(node: Identifier): boolean {
const parent = node.parent;
if (isIdentifierName(node) || isFunctionDeclarationIdentifierName(node)) {
// PropertyAccessExpression?
switch (parent.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return !!(<FunctionLikeDeclaration>parent).body;
case SyntaxKind.PropertyDeclaration:
return !!(<PropertyDeclaration>parent).initializer;
case SyntaxKind.PropertyAssignment:
return true;
}
}
else if (isVariableLike(parent) && parent.name === node) {
return !!parent.initializer;
}
return isIdentifierOfClass(node) || isIdentifierOfEnumDeclaration(node);
}
function isIdentifierOfClass(node: Identifier) {
return (node.parent.kind === SyntaxKind.ClassDeclaration || node.parent.kind === SyntaxKind.ClassExpression) &&
(<ClassLikeDeclaration>node.parent).name === node;
}
function isIdentifierOfEnumDeclaration(node: Identifier) {
return node.parent.kind === SyntaxKind.EnumDeclaration && (<EnumDeclaration>node.parent).name === node;
}
function isTypeAssertionExpression(node: Node): node is TypeAssertion {
return node.kind === SyntaxKind.TypeAssertionExpression;
}
function getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
let results = getOccurrencesAtPositionCore(fileName, position);
@ -5334,7 +5483,7 @@ namespace ts {
node.kind === SyntaxKind.StringLiteral ||
isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
const referencedSymbols = getReferencedSymbolsForNode(node, sourceFilesToSearch, /*findInStrings*/ false, /*findInComments*/ false);
const referencedSymbols = getReferencedSymbolsForNode(node, sourceFilesToSearch, /*findInStrings*/ false, /*findInComments*/ false, /*implementations*/false);
return convertReferencedSymbols(referencedSymbols);
}
@ -6006,7 +6155,7 @@ namespace ts {
case SyntaxKind.ThisKeyword:
// case SyntaxKind.SuperKeyword: TODO:GH#9268
case SyntaxKind.StringLiteral:
return getReferencedSymbolsForNode(node, program.getSourceFiles(), findInStrings, findInComments);
return getReferencedSymbolsForNode(node, program.getSourceFiles(), findInStrings, findInComments, /*implementations*/false);
}
return undefined;
}
@ -6024,37 +6173,40 @@ namespace ts {
}
}
function getReferencedSymbolsForNode(node: Node, sourceFiles: SourceFile[], findInStrings: boolean, findInComments: boolean): ReferencedSymbol[] {
function getReferencedSymbolsForNode(node: Node, sourceFiles: SourceFile[], findInStrings: boolean, findInComments: boolean, implementations: boolean): ReferencedSymbol[] {
const typeChecker = program.getTypeChecker();
// Labels
if (isLabelName(node)) {
if (isJumpStatementTarget(node)) {
const labelDefinition = getTargetLabel((<BreakOrContinueStatement>node.parent), (<Identifier>node).text);
// if we have a label definition, look within its statement for references, if not, then
// the label is undefined and we have no results..
return labelDefinition ? getLabelReferencesInNode(labelDefinition.parent, labelDefinition) : undefined;
}
else {
// it is a label definition and not a target, search within the parent labeledStatement
return getLabelReferencesInNode(node.parent, <Identifier>node);
}
}
if (isThis(node)) {
return getReferencesForThisKeyword(node, sourceFiles);
}
if (node.kind === SyntaxKind.SuperKeyword) {
return getReferencesForSuperKeyword(node);
}
const symbol = typeChecker.getSymbolAtLocation(node);
if (!symbol && node.kind === SyntaxKind.StringLiteral) {
return getReferencesForStringLiteral(<StringLiteral>node, sourceFiles);
if (!implementations) {
// Labels
if (isLabelName(node)) {
if (isJumpStatementTarget(node)) {
const labelDefinition = getTargetLabel((<BreakOrContinueStatement>node.parent), (<Identifier>node).text);
// if we have a label definition, look within its statement for references, if not, then
// the label is undefined and we have no results..
return labelDefinition ? getLabelReferencesInNode(labelDefinition.parent, labelDefinition) : undefined;
}
else {
// it is a label definition and not a target, search within the parent labeledStatement
return getLabelReferencesInNode(node.parent, <Identifier>node);
}
}
if (isThis(node)) {
return getReferencesForThisKeyword(node, sourceFiles);
}
if (node.kind === SyntaxKind.SuperKeyword) {
return getReferencesForSuperKeyword(node);
}
if (!symbol && node.kind === SyntaxKind.StringLiteral) {
return getReferencesForStringLiteral(<StringLiteral>node, sourceFiles);
}
}
// Could not find a symbol e.g. unknown identifier
if (!symbol) {
// Can't have references to something that we have no symbol for.
@ -6084,9 +6236,11 @@ namespace ts {
// Maps from a symbol ID to the ReferencedSymbol entry in 'result'.
const symbolToIndex: number[] = [];
const indexToSymbol: {[index: number]: Symbol} = {};
if (scope) {
result = [];
getReferencesInNode(scope, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex);
getReferencesInNode(scope, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex, indexToSymbol);
}
else {
const internedName = getInternedName(symbol, node, declarations);
@ -6097,11 +6251,15 @@ namespace ts {
if (nameTable[internedName] !== undefined) {
result = result || [];
getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex);
getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex, indexToSymbol);
}
}
}
if (implementations) {
return filterToImplementations(node, symbol, result, indexToSymbol);
}
return result;
function getDefinition(symbol: Symbol): DefinitionInfo {
@ -6363,7 +6521,8 @@ namespace ts {
findInStrings: boolean,
findInComments: boolean,
result: ReferencedSymbol[],
symbolToIndex: number[]): void {
symbolToIndex: number[],
indexToSymbol: {[index: number]: Symbol}): void {
const sourceFile = container.getSourceFile();
const tripleSlashDirectivePrefixRegex = /^\/\/\/\s*</;
@ -6430,7 +6589,6 @@ namespace ts {
}
});
}
return;
function getReferencedSymbol(symbol: Symbol): ReferencedSymbol {
@ -6439,6 +6597,7 @@ namespace ts {
if (index === undefined) {
index = result.length;
symbolToIndex[symbolId] = index;
indexToSymbol[index] = symbol;
result.push({
definition: getDefinition(symbol),
@ -6459,6 +6618,268 @@ namespace ts {
}
}
function filterToImplementations(node: Node, searchSymbol: Symbol, refs: ReferencedSymbol[], indexToSymbol: {[index: number]: Symbol}): ReferencedSymbol[] {
if (isPropertyAccessExpression(node.parent) && node.parent.name === node) {
const type = typeChecker.getTypeAtLocation(node.parent.expression);
if (type.getFlags() & TypeFlags.Class) {
// The search results in refs will contain all implementations of the property. This includes
// all implementations in classes that parentSymbol extends from and sibling implementations
// (i.e. implementations in classes with common ancestors that declare the property). We need to
// filter the results to only the implementation used by parentSymbol's class and any implementations
// in any sub-classes
return filterToClassMemberImplementations(node.parent, searchSymbol, refs, indexToSymbol);
}
else if (type.getFlags() & (TypeFlags.UnionOrIntersection | TypeFlags.Interface)) {
// If parentSymbol did not declare the property being accessed, then the search results
// in refs will also contain references to the interfaces that parentSymbol inherits from.
// We need to filter out any implementations of those parent interfaces in addition to filtering out the
// non-implementation references
return filterReferenceEntries(refs, (entry) => {
const impl = getImplementationFromEntry(entry);
if (impl) {
const entryNode = getTokenAtPosition(getValidSourceFile(entry.fileName), entry.textSpan.start);
const element = getContainingObjectLiteralElement(entryNode);
if (element && element.parent && element.parent.kind === SyntaxKind.ObjectLiteralExpression) {
const objType = getDeclaredTypeOfObjectLiteralExpression(element.parent);
if (objType && typeChecker.isTypeAssignableTo(objType, type)) {
return impl;
}
}
else {
const defClass = getContainingClass(entryNode);
if (defClass) {
const classType = typeChecker.getTypeAtLocation(defClass);
if (typeChecker.isTypeAssignableTo(classType, type)) {
return impl;
}
}
}
}
});
}
}
return filterReferenceEntries(refs, getImplementationFromEntry);
}
function getDeclaredTypeOfObjectLiteralExpression(node: Node): Type {
if (node && node.parent) {
if (node.parent.kind === SyntaxKind.ParenthesizedExpression) {
return getDeclaredTypeOfObjectLiteralExpression(node.parent);
}
if (isVariableLike(node.parent) && node.parent.type) {
return typeChecker.getTypeAtLocation(node.parent.type);
}
else if (node.parent.kind === SyntaxKind.ReturnStatement) {
const containerSig = typeChecker.getSignatureFromDeclaration(getContainingFunction(node));
return typeChecker.getReturnTypeOfSignature(containerSig);
}
else if (node.parent.kind === SyntaxKind.TypeAssertionExpression) {
return typeChecker.getTypeAtLocation((<TypeAssertion>node.parent).type);
}
}
return undefined;
}
function filterReferenceEntries(refs: ReferencedSymbol[], filterer: (x: ReferenceEntry) => ReferenceEntry): ReferencedSymbol[] {
const result: ReferencedSymbol[] = [];
forEach(refs, (ref) => {
const filtered: ReferenceEntry[] = [];
forEach(ref.references, (entry) => {
const filteredEntry = filterer(entry);
if (filteredEntry) {
filtered.push(filteredEntry);
}
});
if (filtered.length) {
result.push({ definition: ref.definition, references: filtered });
}
});
return result;
}
function filterToClassMemberImplementations(parent: PropertyAccessExpression, searchSymbol: Symbol, refs: ReferencedSymbol[], indexToSymbol: {[index: number]: Symbol}): ReferencedSymbol[] {
// Need to find out what class this member is being accessed on
const type = typeChecker.getTypeAtLocation(parent.expression);
const classHierarchy: Symbol[] = getClassHierarchy(searchSymbol.parent);
let lowest: ReferencedSymbol[];
let lowestRank: number;
// Filter down the references to those that refer to implementations of the symbol. That is, implementations
// from subclasses of the parent class and the lowest implementation in the parent class' class hierarchy
forEach(refs, (refSymbol, index) => {
const actual = indexToSymbol[index];
// We only care about implementations in classes in the hierarchy
if (actual.parent.getFlags() & SymbolFlags.Interface) {
return;
}
const rank = classHierarchy.indexOf(actual.parent);
// No need to check anything past the lowest we have found so far
if (lowest && rank > lowestRank) {
return;
}
const implementations: ReferenceEntry[] = [];
forEach(refSymbol.references, (entry) => {
const impl = getImplementationFromEntry(entry, type, classHierarchy);
if (impl) {
implementations.push(impl);
}
});
if (implementations.length) {
if (!lowest || rank < lowestRank) {
lowest = [{ definition: refSymbol.definition, references: implementations }];
lowestRank = rank;
}
else if (rank === lowestRank) {
lowest.push({ definition: refSymbol.definition, references: implementations });
}
}
});
return lowest;
}
function getImplementationFromEntry(entry: ReferenceEntry, base?: Type, hierarchy?: Symbol[]): ReferenceEntry {
const sourceFile = getValidSourceFile(entry.fileName);
const refNode = getTouchingPropertyName(sourceFile, entry.textSpan.start);
// Check to make sure this reference is either part of a sub class or a class that we explicitly
// inherit from in the class hierarchy
if (isMemberOfSubOrParentClass(refNode, base, hierarchy) && refNode.kind === SyntaxKind.Identifier) {
// Check if we found a function/propertyAssignment/method with an implementation or initializer
if (isIdentifierOfImplementation(<Identifier>refNode)) {
return getReferenceEntryFromNode(refNode.parent);
}
else if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
// Go ahead and dereference the shorthand assignment by going to its definition
return getReferenceEntryForShorthandPropertyAssignment(refNode, typeChecker);
}
// Check if the node is within an extends or implements clause
const containingHeritageClause = getContainingClassHeritageClause(refNode);
if (containingHeritageClause) {
return getReferenceEntryFromNode(containingHeritageClause.parent);
}
// If we got a type reference, try and see if the reference applies to any expressions that can implement an interface
const containingTypeReference = getContainingTypeReference(refNode);
if (containingTypeReference) {
const parent = containingTypeReference.parent;
if (isVariableLike(parent) && parent.type === containingTypeReference && parent.initializer && isImplementationExpression(parent.initializer)) {
return getReferenceEntryFromNode(parent.initializer);
}
else if (isFunctionLike(parent) && parent.type === containingTypeReference && parent.body && parent.body.kind === SyntaxKind.Block) {
return forEachReturnStatement(<Block>parent.body, (returnStatement) => {
if (returnStatement.expression && isImplementationExpression(returnStatement.expression)) {
return getReferenceEntryFromNode(returnStatement.expression);
}
});
}
else if (isTypeAssertionExpression(parent) && isImplementationExpression(parent.expression)) {
return getReferenceEntryFromNode(parent.expression);
}
}
}
}
function isMemberOfSubOrParentClass(reference: Node, base: Type, hierarchy: Symbol[]) {
if (!base || !hierarchy) {
return true;
}
const referenceSymbol = typeChecker.getSymbolAtLocation(reference);
if (referenceSymbol && referenceSymbol.parent) {
if (hierarchy.indexOf(referenceSymbol.parent) !== -1) {
return true;
}
const referenceParentDeclarations = referenceSymbol.parent.getDeclarations();
if (referenceParentDeclarations.length) {
const referenceParentType = typeChecker.getTypeAtLocation(referenceParentDeclarations[0]);
return typeChecker.isTypeAssignableTo(referenceParentType, base);
}
}
return false;
}
function getContainingTypeReference(node: Node): Node {
if (node) {
if (node.kind === SyntaxKind.TypeReference) {
return node;
}
if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) {
return getContainingTypeReference(node.parent);
}
}
return undefined;
}
function getContainingClassHeritageClause(node: Node): HeritageClause {
if (node) {
if (node.kind === SyntaxKind.ExpressionWithTypeArguments
&& node.parent.kind === SyntaxKind.HeritageClause
&& isClassLike(node.parent.parent)) {
return <HeritageClause>node.parent;
}
else if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression) {
return getContainingClassHeritageClause(node.parent);
}
}
return undefined;
}
/**
* Returns true if this is an expression that could be used to implement an interface.
*/
function isImplementationExpression(node: Expression): boolean {
// Unwrap parentheses
if (node.kind === SyntaxKind.ParenthesizedExpression) {
return isImplementationExpression((<ParenthesizedExpression>node).expression);
}
return node.kind === SyntaxKind.ArrowFunction ||
node.kind === SyntaxKind.FunctionExpression ||
node.kind === SyntaxKind.ObjectLiteralExpression ||
node.kind === SyntaxKind.ClassExpression;
}
function getClassHierarchy(symbol: Symbol) {
const classes: Symbol[] = [];
getClassHierarchyRecursive(symbol, classes, createMap<Symbol>());
return classes;
function getClassHierarchyRecursive(symbol: Symbol, result: Symbol[], previousIterationSymbolsCache: SymbolTable) {
if (hasProperty(previousIterationSymbolsCache, symbol.name)) {
return;
}
previousIterationSymbolsCache[symbol.name] = symbol;
if (result.indexOf(symbol) !== -1) {
return;
}
result.push(symbol);
forEach(symbol.getDeclarations(), (decl) => {
if (isClassLike(decl)) {
const heritage = getClassExtendsHeritageClauseElement(decl);
if (heritage) {
const type = typeChecker.getTypeAtLocation(heritage);
if (type && type.symbol) {
getClassHierarchyRecursive(type.symbol, result, previousIterationSymbolsCache);
}
}
}
});
}
}
function getReferencesForSuperKeyword(superKeyword: Node): ReferencedSymbol[] {
let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false);
if (!searchSpaceNode) {
@ -8310,6 +8731,7 @@ namespace ts {
getQuickInfoAtPosition,
getDefinitionAtPosition,
getTypeDefinitionAtPosition,
getImplementationAtPosition,
getReferencesAtPosition,
findReferences,
getOccurrencesAtPosition,

View File

@ -173,6 +173,12 @@ namespace ts {
*/
getTypeDefinitionAtPosition(fileName: string, position: number): string;
/**
* Returns a JSON-encoded value of the type:
* { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[]
*/
getImplementationAtPosition(fileName: string, position: number): string;
/**
* Returns a JSON-encoded value of the type:
* { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[]
@ -772,6 +778,19 @@ namespace ts {
);
}
/// GOTO Implementation
/**
* Computes the implementation location of the symbol
* at the requested position.
*/
public getImplementationAtPosition(fileName: string, position: number): string {
return this.forwardJSONCall(
`getImplementationAtPosition('${fileName}', ${position})`,
() => this.languageService.getImplementationAtPosition(fileName, position)
);
}
public getRenameInfo(fileName: string, position: number): string {
return this.forwardJSONCall(
`getRenameInfo('${fileName}', ${position})`,

View File

@ -110,6 +110,7 @@ declare namespace FourSlashInterface {
eof(): void;
definition(definitionIndex?: number): void;
type(definitionIndex?: number): void;
implementation(implementationIndex?: number): void;
position(position: number, fileIndex?: number): any;
position(position: number, fileName?: string): any;
file(index: number, content?: string, scriptKindName?: string): any;
@ -134,6 +135,7 @@ declare namespace FourSlashInterface {
quickInfoExists(): void;
definitionCountIs(expectedCount: number): void;
typeDefinitionCountIs(expectedCount: number): void;
implementationCountIs(expectedCount: number): void;
definitionLocationExists(): void;
verifyDefinitionsName(name: string, containerName: string): void;
isValidBraceCompletionAtPosition(openingBrace?: string): void;
@ -226,6 +228,7 @@ declare namespace FourSlashInterface {
getSyntacticDiagnostics(expected: string): void;
getSemanticDiagnostics(expected: string): void;
ProjectInfo(expected: string[]): void;
allRangesAppearInImplementationList(): void;
}
class edit {
backspace(count?: number): void;

View File

@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>
// Should handle calls made on members declared in a class
//// class Bar {
//// [|hello() {}|]
//// }
////
//// new Bar().hel/*reference*/lo;
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,21 @@
/// <reference path='fourslash.ts'/>
// Should handle calls made on member declared in an abstract class
//// abstract class AbstractBar {
//// abstract he/*declaration*/llo(): void;
//// }
////
//// class Bar extends AbstractBar{
//// [|hello() {}|]
//// }
////
//// function whatever(x: AbstractBar) {
//// x.he/*reference*/llo();
//// }
goTo.marker("reference");
verify.allRangesAppearInImplementationList();
goTo.marker("declaration");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,13 @@
/// <reference path='fourslash.ts'/>
// Should handle calls made on members of an enum
//// enum Foo {
//// [|Foo1 = function initializer() { return 5 } ()|],
//// Foo2 = 6
//// }
////
//// Foo.Fo/*reference*/o1;
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,13 @@
/// <reference path='fourslash.ts'/>
// Should handle calls made on enum name
//// [|enum Foo {
//// Foo1 = function initializer() { return 5 } (),
//// Foo2 = 6
//// }|]
////
//// Fo/*reference*/o;
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,27 @@
/// <reference path='fourslash.ts'/>
// Should return method implementations in object literals within variable-like declarations
//// interface Foo {
//// he/*declaration*/llo: () => void
//// }
////
//// var bar: Foo = { [|hello: helloImpl|] };
////
//// function helloImpl () {}
////
//// function whatever(x: Foo = { [|hello() {/**1*/}|] }) {
//// x.he/*function_call*/llo()
//// }
////
//// class Bar {
//// x: Foo = { [|hello() {/*2*/}|] }
////
//// constructor(public f: Foo = { [|hello() {/**3*/}|] } ) {}
//// }
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();
goTo.marker("declaration");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,25 @@
/// <reference path='fourslash.ts'/>
// Should return implementations in a simple class
//// interface Foo {
//// hel/*declaration*/lo(): void;
//// okay?: number;
//// }
////
//// class Bar implements Foo {
//// [|hello() {}|]
//// public sure() {}
//// }
////
//// function whatever(a: Foo) {
//// a.he/*function_call*/llo();
//// }
////
//// whatever(new Bar());
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();
goTo.marker("declaration");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,25 @@
/// <reference path='fourslash.ts'/>
// Should return implementations when left hand side of function call is an abstract class
//// interface Foo {
//// he/*declaration*/llo(): void
//// }
////
//// abstract class AbstractBar implements Foo {
//// abstract hello(): void;
//// }
////
//// class Bar extends AbstractBar {
//// [|hello() {}|]
//// }
////
//// function whatever(a: AbstractBar) {
//// a.he/*function_call*/llo();
//// }
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();
goTo.marker("declaration");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,25 @@
/// <reference path='fourslash.ts'/>
// Should not return super implementations when method is implemented in class
//// interface Foo {
//// hello (): void;
//// }
////
//// class Bar extends SuperBar {
//// [|hello() {}|]
//// }
////
//// class SuperBar implements Foo {
//// hello() {} // should not show up
//// }
////
//// class OtherBar implements Foo {
//// hello() {} // should not show up
//// }
////
//// new Bar().hel/*function_call*/lo();
//// new Bar()["hello"]();
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,26 @@
/// <reference path='fourslash.ts'/>
// Should return implementation in class and all sub-classes of target
//// interface Foo {
//// hello (): void;
//// }
////
//// class Bar extends SuperBar {
//// [|hello() {}|]
//// }
////
//// class SuperBar implements Foo {
//// [|hello() {}|]
//// }
////
//// class OtherBar implements Foo {
//// hello() {} // should not show up
//// }
////
//// function (x: SuperBar) {
//// x.he/*function_call*/llo()
//// }
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,38 @@
/// <reference path='fourslash.ts'/>
// Should not return implementations in classes with a shared parent that implements the interface
//// interface Foo {
//// hello (): void;
//// }
////
//// class SuperBar implements Foo {
//// [|hello() {}|]
//// }
////
//// class Bar extends SuperBar {
//// hello2() {}
//// }
////
//// class OtherBar extends SuperBar {
//// [|hello() {}|] // This could be considered a false positive because it does not extend Bar. Returned because it shares a common ancestor and is structurally equivalent
//// hello2() {}
//// hello3() {}
//// }
////
//// class NotRelatedToBar {
//// hello() {} // Equivalent to last case, but shares no common ancestors with Bar and so is not returned
//// hello2() {}
//// hello3() {}
//// }
////
//// class NotBar extends SuperBar {
//// hello() {} // Should not be returned because it is not structurally equivalent to Bar
//// }
////
//// function whatever(x: Bar) {
//// x.he/*function_call*/llo()
//// }
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,49 @@
/// <reference path='fourslash.ts'/>
// Should not return references to parent interfaces even if the method is declared there
//// interface SuperFoo {
//// hello (): void;
//// }
////
//// interface Foo extends SuperFoo {
//// someOtherFunction(): void;
//// }
////
//// class Bar implements Foo {
//// [|hello() {}|]
//// someOtherFunction() {}
//// }
////
//// function createFoo(): Foo {
//// return {
//// [|hello() {}|],
//// someOtherFunction() {}
//// };
//// }
////
//// var y: Foo = {
//// [|hello() {}|],
//// someOtherFunction() {}
//// };
////
//// class FooLike implements SuperFoo {
//// [|hello() {}|] // This case could be considered a false positive. It does not explicitly implement Foo but does implement it structurally and it shares a common ancestor
//// someOtherFunction() {}
//// }
////
//// class NotRelatedToFoo {
//// hello() {} // This case is equivalent to the last case, but is not returned because it does not share a common ancestor with Foo
//// someOtherFunction() {}
//// }
////
//// class NotFoo implements SuperFoo {
//// hello() {} // We only want implementations of Foo, even though the function is declared in SuperFoo
//// }
////
//// function (x: Foo) {
//// x.he/*function_call*/llo()
//// }
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,23 @@
/// <reference path='fourslash.ts'/>
// Should handle calls made on this
//// interface Foo {
//// hello (): void;
//// }
////
//// class SuperBar implements Foo {
//// [|hello() {}|]
//// }
////
//// class Bar extends SuperBar {
//// whatever() { this.he/*function_call*/llo(); }
//// }
////
//// class SubBar extends Bar {
//// hello() {}
//// }
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,30 @@
/// <reference path='fourslash.ts'/>
// Should handle calls made on super
//// interface Foo {
//// hello (): void;
//// }
////
//// class SubBar extends Bar {
//// hello() {}
//// }
////
//// class Bar extends SuperBar {
//// hello() {}
////
//// whatever() {
//// super.he/*function_call*/llo();
//// }
//// }
////
//// class SuperBar extends MegaBar {
//// [|hello() {}|]
//// }
////
//// class MegaBar implements Foo {
//// hello() {}
//// }
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,36 @@
/// <reference path='fourslash.ts'/>
// Should handle intersection types
//// interface Foo {
//// hello(): void;
//// aloha(): void;
//// }
////
//// interface Bar {
//// hello(): void;
//// goodbye(): void;
//// }
////
//// class FooImpl implements Foo {
//// hello() {/**FooImpl*/}
//// aloha() {}
//// }
////
//// class BarImpl implements Bar {
//// hello() {/**BarImpl*/}
//// goodbye() {}
//// }
////
//// class FooAndBarImpl implements Foo, Bar {
//// [|hello() {/**FooAndBarImpl*/}|]
//// aloha() {}
//// goodbye() {}
//// }
////
//// function someFunction(x: Foo & Bar) {
//// x.he/*function_call*/llo();
//// }
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,36 @@
/// <reference path='fourslash.ts'/>
// Should handle union types
//// interface Foo {
//// hello(): void;
//// aloha(): void;
//// }
////
//// interface Bar {
//// hello(): void;
//// goodbye(): void;
//// }
////
//// class FooImpl implements Foo {
//// [|hello() {/**FooImpl*/}|]
//// aloha() {}
//// }
////
//// class BarImpl implements Bar {
//// [|hello() {/**BarImpl*/}|]
//// goodbye() {}
//// }
////
//// class FooAndBarImpl implements Foo, Bar {
//// [|hello() {/**FooAndBarImpl*/}|]
//// aloha() {}
//// goodbye() {}
//// }
////
//// function someFunction(x: Foo | Bar) {
//// x.he/*function_call*/llo();
//// }
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>
// Should handle members of object literals in type assertion expressions
//// interface Foo {
//// hel/*reference*/lo(): void;
//// }
////
//// var x = <Foo> { [|hello: () => {}|] };
//// var y = <Foo> (((({ [|hello: () => {}|] }))));
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,23 @@
/// <reference path='fourslash.ts'/>
// Should handle property assignments in object literals within variable like declarations
//// interface Foo {
//// hello: number
//// }
////
//// var bar: Foo = { [|hello: 5|] };
////
////
//// function whatever(x: Foo = { [|hello: 5 * 9|] }) {
//// x.he/*reference*/llo
//// }
////
//// class Bar {
//// x: Foo = { [|hello: 6|] }
////
//// constructor(public f: Foo = { [|hello: 7|] } ) {}
//// }
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,16 @@
/// <reference path='fourslash.ts'/>
// Should handle property assignments within class declarations
//// interface Foo { hello: number }
////
//// class Bar implements Foo {
//// [|hello = 5 * 9;|]
//// }
////
//// function whatever(foo: Foo) {
//// foo.he/*reference*/llo;
//// }
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,26 @@
/// <reference path='fourslash.ts'/>
// Should go to definitions in object literals in variable like declarations when invoked on interface
//// interface Fo/*interface_definition*/o {
//// hello: () => void
//// }
////
//// interface Baz extends Foo {}
////
//// var bar: Foo = [|{ hello: helloImpl /**0*/ }|];
////
//// function helloImpl () {}
////
//// function whatever(x: Foo = [|{ hello() {/**1*/} }|] ) {
//// }
////
//// class Bar {
//// x: Foo = [|{ hello() {/*2*/} }|]
////
//// constructor(public f: Foo = [|{ hello() {/**3*/} }|] ) {}
//// }
goTo.marker("interface_definition");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,25 @@
/// <reference path='fourslash.ts'/>
//// interface Fo/*interface_definition*/o { hello(): void }
////
//// [|class SuperBar implements Foo {
//// hello () {}
//// }|]
////
//// [|abstract class AbstractBar implements Foo {
//// abstract hello (): void;
//// }|]
////
//// class Bar extends SuperBar {
//// }
////
//// class NotAbstractBar extends AbstractBar {
//// hello () {}
//// }
////
//// var x = new SuperBar();
//// var y: SuperBar = new SuperBar();
//// var z: AbstractBar = new NotAbstractBar();
goTo.marker("interface_definition");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,20 @@
/// <reference path='fourslash.ts'/>
// Should go to definitions in object literals in return statements of functions with the type of the interface
//// interface Fo/*interface_definition*/o { hello: () => void }
////
//// function createFoo(): Foo {
//// return [|{
//// hello() {}
//// }|];
//// }
////
//// function createFooLike() {
//// return {
//// hello() {}
//// };
//// }
goTo.marker("interface_definition");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,10 @@
/// <reference path='fourslash.ts'/>
// Should go to object literals within cast expressions when invoked on interface
//// interface Fo/*interface_definition*/o { hello: () => void }
////
//// var x = <Foo> [|{ hello: () => {} }|];
goTo.marker("interface_definition");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,22 @@
/// <reference path='fourslash.ts'/>
// Should go to function literals that implement the interface within variable like declarations when invoked on an interface
//// interface Fo/*interface_definition*/o {
//// (a: number): void
//// }
////
//// var bar: Foo = [|(a) => {/**0*/}|];
////
//// function whatever(x: Foo = [|(a) => {/**1*/}|] ) {
//// }
////
//// class Bar {
//// x: Foo = [|(a) => {/**2*/}|]
////
//// constructor(public f: Foo = [|function(a) {}|] ) {}
//// }
goTo.marker("interface_definition");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,14 @@
/// <reference path='fourslash.ts'/>
// Should go to function literals that implement the interface within type assertions when invoked on an interface
//// interface Fo/*interface_definition*/o {
//// (a: number): void
//// }
////
//// let bar2 = <Foo> [|function(a) {}|];
////
goTo.marker("interface_definition");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,16 @@
/// <reference path='fourslash.ts'/>
// Should go to class expressions that implement a constructor type
//// interface Fo/*interface_definition*/o {
//// new (a: number): SomeOtherType;
//// }
////
//// interface SomeOtherType {}
////
//// let x: Foo = [|class { constructor (a: number) {} }|];
//// let y = <Foo> [|class { constructor (a: number) {} }|];
goTo.marker("interface_definition");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>
// Should not crash when invoked on an invalid location
//// var x1 = 50/*0*/0;
//// var x2 = "hel/*1*/lo";
//// /*2*/
for(var i = 0; i < 3; i++) {
goTo.marker("" + i);
verify.implementationCountIs(0);
}

View File

@ -0,0 +1,9 @@
/// <reference path='fourslash.ts'/>
// Should return definition of locally declared functions
//// he/*function_call*/llo();
//// [|function hello() {}|]
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,9 @@
/// <reference path='fourslash.ts'/>
// Should return the defintion of locally defined variables
//// const [|hello = function() {}|];
//// he/*function_call*/llo();
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,8 @@
/// <reference path='fourslash.ts'/>
//// const x = { [|hello: () => {}|] };
////
//// x.he/*function_call*/llo();
////
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>
// Should return the definition when invoked on variable assignment
//// let [|he/*local_var*/llo = {}|];
////
//// x.hello();
////
//// hello = {};
////
goTo.marker("local_var");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,10 @@
/// <reference path='fourslash.ts'/>
// Should return definition of function when invoked on the declaration
//// [|function he/*local_var*/llo() {}|]
////
//// hello();
////
goTo.marker("local_var");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>
// Should handle calls made the left hand side of a property access expression
//// class Bar {
//// public hello() {}
//// }
////
//// var [|someVar = new Bar()|];
//// someVa/*reference*/r.hello();
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>
// Should handle calls on namespaces
//// [|namespace Foo {
//// export function hello() {}
//// }|]
////
//// let x = Fo/*reference*/o;
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>
// Should handle calls on modules
//// [|module Foo {
//// export function hello() {}
//// }|]
////
//// let x = Fo/*reference*/o;
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>
// Should handle property access expressions on namespaces
//// namespace Foo {
//// [|export function hello() {}|]
//// }
////
//// Foo.hell/*reference*/o();
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>
// Should handle property access expressions on namespaces
//// module Foo {
//// [|export function hello() {}|]
//// }
////
//// Foo.hell/*reference*/o();
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,26 @@
/// <reference path='fourslash.ts'/>
// Should handle sub-namespaces
//// /*parentNamespace*/namespace Foo {
//// export function hello() {}
//// }
////
//// /*parentNamespace2*/namespace Foo./*childNamespace*/Bar {
//// export function okay() {}
//// }
////
//// Fo/*parentReference*/o.hello();
//// Foo.Ba/*childReference*/r.okay();
goTo.marker("parentReference");
goTo.implementation(0);
verify.caretAtMarker("parentNamespace");
goTo.marker("parentReference");
goTo.implementation(1);
verify.caretAtMarker("parentNamespace2");
goTo.marker("childReference");
goTo.implementation();
verify.caretAtMarker("childNamespace")

View File

@ -0,0 +1,26 @@
/// <reference path='fourslash.ts'/>
// Should handle sub-modules
//// /*parentModule*/module Foo {
//// export function hello() {}
//// }
////
//// /*parentModule2*/module Foo./*childModule*/Bar {
//// export function okay() {}
//// }
////
//// Fo/*parentReference*/o.hello();
//// Foo.Ba/*childReference*/r.okay();
goTo.marker("parentReference");
goTo.implementation(0);
verify.caretAtMarker("parentModule");
goTo.marker("parentReference");
goTo.implementation(1);
verify.caretAtMarker("parentModule2");
goTo.marker("childReference");
goTo.implementation();
verify.caretAtMarker("childModule")

View File

@ -0,0 +1,28 @@
/// <reference path='fourslash.ts'/>
// Should handle types that are members of a namespace in type references and heritage clauses
//// namespace Foo {
//// export interface Bar {
//// hello(): void;
//// }
////
//// [|class BarImpl implements Bar {
//// hello() {}
//// }|]
//// }
////
//// [|class Baz implements Foo.Bar {
//// hello() {}
//// }|]
////
//// var someVar1 : Foo.Bar = [|{ hello: () => {/**1*/} }|];
////
//// var someVar2 = <Foo.Bar> [|{ hello: () => {/**2*/} }|];
////
//// function whatever(x: Foo.Ba/*reference*/r) {
////
//// }
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,28 @@
/// <reference path='fourslash.ts'/>
// Should handle types that are members of a module in type references and heritage clauses
//// module Foo {
//// export interface Bar {
//// hello(): void;
//// }
////
//// [|class BarImpl implements Bar {
//// hello() {}
//// }|]
//// }
////
//// [|class Baz implements Foo.Bar {
//// hello() {}
//// }|]
////
//// var someVar1 : Foo.Bar = [|{ hello: () => {/**1*/} }|];
////
//// var someVar2 = <Foo.Bar> [|{ hello: () => {/**2*/} }|];
////
//// function whatever(x: Foo.Ba/*reference*/r) {
////
//// }
goTo.marker("reference");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,43 @@
/// <reference path='fourslash.ts'/>
// Should handle shorthand property assignments of class constructors
//// interface Foo {
//// someFunction(): void;
//// }
////
//// interface FooConstructor {
//// new (): Foo
//// }
////
//// interface Bar {
//// Foo: FooConstructor;
//// }
////
//// var x = /*classExpression*/class Foo {
//// createBarInClassExpression(): Bar {
//// return {
//// Fo/*classExpressionRef*/o
//// };
//// }
////
//// someFunction() {}
//// }
////
//// /*declaredClass*/class Foo {
////
//// }
////
//// function createBarUsingClassDeclaration(): Bar {
//// return {
//// Fo/*declaredClassRef*/o
//// };
//// }
goTo.marker("classExpressionRef");
goTo.implementation();
verify.caretAtMarker("classExpression");
goTo.marker("declaredClassRef");
goTo.implementation();
verify.caretAtMarker("declaredClass");

View File

@ -0,0 +1,48 @@
/// <reference path='fourslash.ts'/>
// Should handle shorthand property assignments of class constructors when invoked on member of interface
//// interface Foo {
//// someFunction(): void;
//// }
////
//// interface FooConstructor {
//// new (): Foo
//// }
////
//// interface Bar {
//// Foo: FooConstructor;
//// }
////
//// // Class expression that gets used in a bar implementation
//// var x = [|class Foo {
//// createBarInClassExpression(): Bar {
//// return {
//// Foo
//// };
//// }
////
//// someFunction() {}
//// }|];
////
//// // Class declaration that gets used in a bar implementation. This class has multiple definitions
//// // (the class declaration and the interface above), but we only want the class returned
//// [|class Foo {
////
//// }|]
////
//// function createBarUsingClassDeclaration(): Bar {
//// return {
//// Foo
//// };
//// }
////
//// // Class expression that does not get used in a bar implementation
//// var y = class Foo {
//// someFunction() {}
//// };
////
//// createBarUsingClassDeclaration().Fo/*reference*/o;
goTo.marker("reference");
verify.allRangesAppearInImplementationList();;

View File

@ -0,0 +1,22 @@
/// <reference path='fourslash.ts'/>
// Should go to implementation of properties that are assigned to implementations of an interface using shorthand notation
//// interface Foo {
//// hello(): void;
//// }
////
//// function createFoo(): Foo {
//// return {
//// hello
//// };
////
//// [|function hello() {}|]
//// }
////
//// function whatever(x: Foo) {
//// x.h/*function_call*/ello();
//// }
goTo.marker("function_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,16 @@
/// <reference path='fourslash.ts'/>
// Should go to super class declaration when invoked on a super call expression
//// [|class Foo {
//// constructor() {}
//// }|]
////
//// class Bar extends Foo {
//// constructor() {
//// su/*super_call*/per();
//// }
//// }
goTo.marker("super_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,16 @@
/// <reference path='fourslash.ts'/>
// Should go to the super class declaration when invoked on the super keyword in a property access expression
//// [|class Foo {
//// hello() {}
//// }|]
////
//// class Bar extends Foo {
//// hello() {
//// sup/*super_call*/er.hello();
//// }
//// }
goTo.marker("super_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,14 @@
/// <reference path='fourslash.ts'/>
// Should go to class declaration when invoked on this keyword in property access expression
//// [|class Bar extends Foo {
//// hello() {
//// thi/*this_call*/s.whatever();
//// }
////
//// whatever() {}
//// }|]
goTo.marker("this_call");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>
// Should go to class declaration when invoked on a this type reference
//// [|class Bar extends Foo {
//// hello(): th/*this_type*/is {
//// return this;
//// }
//// }|]
goTo.marker("this_type");
verify.allRangesAppearInImplementationList();

View File

@ -0,0 +1,9 @@
/// <reference path="../fourslash.ts"/>
// @Filename: a.ts
//// interface Fo/*1*/o {}
//// /*2*/class Bar implements Foo {}
goTo.marker('1');
goTo.implementation();
verify.caretAtMarker('2');

View File

@ -0,0 +1,35 @@
/// <reference path='fourslash.ts' />
// @Filename: goToImplementationDifferentFile_Implementation.ts
//// /*fooClassImplementation*/class FooImpl implements Foo {}
////
//// /*barClassImplementation*/class Bar {
//// /*barHelloFunctionImplementation*/hello() {}
//// }
////
// @Filename: goToImplementationDifferentFile_Consumption.ts
//// interface Fo/*fooClassReference*/o {}
////
//// var x = new B/*barClassReference*/ar();
////
//// x.hel/*barHelloFunctionReference*/lo();
////
//// /*thisImplementation*/class SomeClass {
//// someMethod() {
//// thi/*thisReference*/s.someMethod();
//// }
//// }
var markerList = [
"fooClass",
"barClass",
"barHelloFunction",
"this"
];
markerList.forEach((marker) => {
goTo.marker(marker + 'Reference');
goTo.implementation();
verify.caretAtMarker(marker + 'Implementation');
});

View File

@ -0,0 +1,35 @@
/// <reference path='fourslash.ts' />
// @Filename: goToImplementationDifferentFile_Implementation.ts
//// /*fooClassImplementation*/class FooImpl implements Foo {}
////
//// /*barClassImplementation*/class Bar {
//// /*barHelloFunctionImplementation*/hello() {}
//// }
////
// @Filename: goToImplementationDifferentFile_Consumption.ts
//// interface Fo/*fooClassReference*/o {}
////
//// var x = new B/*barClassReference*/ar();
////
//// x.hel/*barHelloFunctionReference*/lo();
////
//// /*thisImplementation*/class SomeClass {
//// someMethod() {
//// thi/*thisReference*/s.someMethod();
//// }
//// }
var markerList = [
"fooClass",
"barClass",
"barHelloFunction",
"this"
];
markerList.forEach((marker) => {
goTo.marker(marker + 'Reference');
goTo.implementation();
verify.caretAtMarker(marker + 'Implementation');
});