diff --git a/Jakefile.js b/Jakefile.js index 16f0645323c24..c62a4ffd6ce0e 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -129,6 +129,7 @@ var servicesSources = [ "documentRegistry.ts", "findAllReferences.ts", "goToDefinition.ts", + "goToImplementation.ts", "jsDoc.ts", "jsTyping.ts", "navigateTo.ts", @@ -789,7 +790,7 @@ function cleanTestDirs() { // used to pass data from jake command line directly to run.js function writeTestConfigFile(tests, light, taskConfigsFolder, workerCount, stackTraceLimit) { - var testConfigContents = JSON.stringify({ + var testConfigContents = JSON.stringify({ test: tests ? [tests] : undefined, light: light, workerCount: workerCount, diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 5fd8194071a38..a076812462e1e 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -88,6 +88,10 @@ namespace FourSlash { marker?: Marker; } + interface ImplementationLocationInformation extends ts.ImplementationLocation { + matched?: boolean; + } + export interface TextSpan { start: number; end: number; @@ -1699,6 +1703,17 @@ namespace FourSlash { assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Type definitions Count")); } + public verifyImplementationListIsEmpty(negative: boolean) { + const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition); + + if (negative) { + assert.isTrue(implementations && implementations.length > 0, "Expected at least one implementation but got 0"); + } + else { + assert.isUndefined(implementations, "Expected implementation list to be empty but implementations returned"); + } + } + public verifyGoToDefinitionName(expectedName: string, expectedContainerName: string) { const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition); const actualDefinitionName = definitions && definitions.length ? definitions[0].name : ""; @@ -1707,6 +1722,82 @@ namespace FourSlash { assert.equal(actualDefinitionContainerName, expectedContainerName, this.messageAtLastKnownMarker("Definition Info Container Name")); } + public goToImplementation() { + const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition); + if (!implementations || !implementations.length) { + this.raiseError("goToImplementation failed - expected to find at least one implementation location but got 0"); + } + if (implementations.length > 1) { + this.raiseError(`goToImplementation failed - more than 1 implementation returned (${implementations.length})`); + } + + const implementation = implementations[0]; + this.openFile(implementation.fileName); + this.currentCaretPosition = implementation.textSpan.start; + } + + public verifyRangesInImplementationList(markerName: string) { + this.goToMarker(markerName); + const implementations: ImplementationLocationInformation[] = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition); + if (!implementations || !implementations.length) { + this.raiseError("verifyRangesInImplementationList failed - expected to find at least one implementation location but got 0"); + } + + for (let i = 0; i < implementations.length; i++) { + for (let j = 0; j < implementations.length; j++) { + if (i !== j && implementationsAreEqual(implementations[i], implementations[j])) { + const { textSpan, fileName } = implementations[i]; + const end = textSpan.start + textSpan.length; + this.raiseError(`Duplicate implementations returned for range (${textSpan.start}, ${end}) in ${fileName}`); + } + } + } + + const ranges = this.getRanges(); + + if (!ranges || !ranges.length) { + this.raiseError("verifyRangesInImplementationList failed - expected to find at least one range in test source"); + } + + const unsatisfiedRanges: Range[] = []; + + for (const range of ranges) { + const length = range.end - range.start; + const matchingImpl = ts.find(implementations, impl => + range.fileName === impl.fileName && range.start === impl.textSpan.start && length === impl.textSpan.length); + if (matchingImpl) { + matchingImpl.matched = true; + } + else { + unsatisfiedRanges.push(range); + } + } + + const unmatchedImplementations = implementations.filter(impl => !impl.matched); + if (unmatchedImplementations.length || unsatisfiedRanges.length) { + let error = "Not all ranges or implementations are satisfied"; + if (unsatisfiedRanges.length) { + error += "\nUnsatisfied ranges:"; + for (const range of unsatisfiedRanges) { + error += `\n (${range.start}, ${range.end}) in ${range.fileName}: ${this.rangeText(range)}`; + } + } + + if (unmatchedImplementations.length) { + error += "\nUnmatched implementations:"; + for (const impl of unmatchedImplementations) { + const end = impl.textSpan.start + impl.textSpan.length; + error += `\n (${impl.textSpan.start}, ${end}) in ${impl.fileName}: ${this.getFileContent(impl.fileName).slice(impl.textSpan.start, end)}`; + } + } + this.raiseError(error); + } + + function implementationsAreEqual(a: ImplementationLocationInformation, b: ImplementationLocationInformation) { + return a.fileName === b.fileName && TestState.textSpansEqual(a.textSpan, b.textSpan); + } + } + public getMarkers(): Marker[] { // Return a copy of the list return this.testData.markers.slice(0); @@ -2885,6 +2976,10 @@ namespace FourSlashInterface { this.state.goToTypeDefinition(definitionIndex); } + public implementation() { + this.state.goToImplementation(); + } + public position(position: number, fileIndex?: number): void; public position(position: number, fileName?: string): void; public position(position: number, fileNameOrIndex?: any): void { @@ -2985,6 +3080,10 @@ namespace FourSlashInterface { this.state.verifyTypeDefinitionsCount(this.negative, expectedCount); } + public implementationListIsEmpty() { + this.state.verifyImplementationListIsEmpty(this.negative); + } + public isValidBraceCompletionAtPosition(openingBrace: string) { this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace); } @@ -3253,6 +3352,10 @@ namespace FourSlashInterface { public ProjectInfo(expected: string[]) { this.state.verifyProjectInfo(expected); } + + public allRangesAppearInImplementationList(markerName: string) { + this.state.verifyRangesInImplementationList(markerName); + } } export class Edit { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 3518174cc2d8c..f96676747e4c8 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -435,6 +435,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)); } diff --git a/src/server/client.ts b/src/server/client.ts index 8391300d468ac..7268c3c9573c7 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -368,6 +368,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(CommandNames.Implementation, args); + const response = this.processResponse(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 []; diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index f053906428087..ebf43cdbb9e6f 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -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 diff --git a/src/server/session.ts b/src/server/session.ts index ff4e9e2d29f99..1cf971cf5cf8a 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -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"; @@ -363,6 +364,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 implementations = compilerService.languageService.getImplementationAtPosition(file, + compilerService.host.lineOffsetToPosition(file, line, offset)); + + 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); @@ -1090,6 +1113,10 @@ namespace ts.server { const defArgs = request.arguments; return { response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true }; }, + [CommandNames.Implementation]: (request: protocol.Request) => { + const implArgs = request.arguments; + return { response: this.getImplementation(implArgs.line, implArgs.offset, implArgs.file), responseRequired: true }; + }, [CommandNames.References]: (request: protocol.Request) => { const defArgs = request.arguments; return { response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true }; diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index 50e257370d385..0f18427e56885 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -27,7 +27,7 @@ namespace ts.DocumentHighlights { node.kind === SyntaxKind.StringLiteral || isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) { - const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFilesToSearch, /*findInStrings*/ false, /*findInComments*/ false); + const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFilesToSearch, /*findInStrings*/ false, /*findInComments*/ false, /*implementations*/false); return convertReferencedSymbols(referencedSymbols); } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index d8bb2929268b9..dcda3e40b1a67 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -17,42 +17,45 @@ namespace ts.FindAllReferences { // case SyntaxKind.SuperKeyword: TODO:GH#9268 case SyntaxKind.ConstructorKeyword: case SyntaxKind.StringLiteral: - return getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFiles, findInStrings, findInComments); + return getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFiles, findInStrings, findInComments, /*implementations*/false); } return undefined; } - export function getReferencedSymbolsForNode(typeChecker: TypeChecker, cancellationToken: CancellationToken, node: Node, sourceFiles: SourceFile[], findInStrings: boolean, findInComments: boolean): ReferencedSymbol[] { - // Labels - if (isLabelName(node)) { - if (isJumpStatementTarget(node)) { - const labelDefinition = getTargetLabel((node.parent), (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, node); + export function getReferencedSymbolsForNode(typeChecker: TypeChecker, cancellationToken: CancellationToken, node: Node, sourceFiles: SourceFile[], findInStrings: boolean, findInComments: boolean, implementations: boolean): ReferencedSymbol[] { + if (!implementations) { + // Labels + if (isLabelName(node)) { + if (isJumpStatementTarget(node)) { + const labelDefinition = getTargetLabel((node.parent), (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, node); + } } - } - if (isThis(node)) { - return getReferencesForThisKeyword(node, sourceFiles); - } + if (isThis(node)) { + return getReferencesForThisKeyword(node, sourceFiles); + } - if (node.kind === SyntaxKind.SuperKeyword) { - return getReferencesForSuperKeyword(node); + if (node.kind === SyntaxKind.SuperKeyword) { + return getReferencesForSuperKeyword(node); + } } // `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword, // so we have to specify that we want the constructor symbol. const symbol = typeChecker.getSymbolAtLocation(node); - if (!symbol && node.kind === SyntaxKind.StringLiteral) { + if (!implementations && !symbol && node.kind === SyntaxKind.StringLiteral) { return getReferencesForStringLiteral(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. @@ -356,9 +359,9 @@ namespace ts.FindAllReferences { } /** Search within node "container" for references for a search value, where the search value is defined as a - * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). - * searchLocation: a node where the search value - */ + * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). + * searchLocation: a node where the search value + */ function getReferencesInNode(container: Node, searchSymbol: Symbol, searchText: string, @@ -374,6 +377,9 @@ namespace ts.FindAllReferences { const start = findInComments ? container.getFullStart() : container.getStart(); const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, start, container.getEnd()); + const parents = getParentSymbolsOfPropertyAccess(); + const inheritsFromCache: Map = createMap(); + if (possiblePositions.length) { // Build the set of symbols to search for, initially it has only the current symbol const searchSymbols = populateSearchSymbolSet(searchSymbol, searchLocation); @@ -386,8 +392,8 @@ namespace ts.FindAllReferences { // This wasn't the start of a token. Check to see if it might be a // match in a comment or string if that's what the caller is asking // for. - if ((findInStrings && isInString(sourceFile, position)) || - (findInComments && isInNonReferenceComment(sourceFile, position))) { + if (!implementations && ((findInStrings && isInString(sourceFile, position)) || + (findInComments && isInNonReferenceComment(sourceFile, position)))) { // In the case where we're looking inside comments/strings, we don't have // an actual definition. So just use 'undefined' here. Features like @@ -415,11 +421,10 @@ namespace ts.FindAllReferences { const referenceSymbolDeclaration = referenceSymbol.valueDeclaration; const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration); const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, - /*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword); + /*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword, parents, inheritsFromCache); if (relatedSymbol) { - const referencedSymbol = getReferencedSymbol(relatedSymbol); - referencedSymbol.references.push(getReferenceEntryFromNode(referenceLocation)); + addReferenceToRelatedSymbol(referenceLocation, relatedSymbol); } /* Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment * has two meaning : property name and property value. Therefore when we do findAllReference at the position where @@ -428,8 +433,7 @@ namespace ts.FindAllReferences { * position of property accessing, the referenceEntry of such position will be handled in the first case. */ else if (!(referenceSymbol.flags & SymbolFlags.Transient) && searchSymbols.indexOf(shorthandValueSymbol) >= 0) { - const referencedSymbol = getReferencedSymbol(shorthandValueSymbol); - referencedSymbol.references.push(getReferenceEntryFromNode(referenceSymbolDeclaration.name)); + addReferenceToRelatedSymbol(referenceSymbolDeclaration.name, shorthandValueSymbol); } else if (searchLocation.kind === SyntaxKind.ConstructorKeyword) { findAdditionalConstructorReferences(referenceSymbol, referenceLocation); @@ -437,9 +441,34 @@ namespace ts.FindAllReferences { } }); } - return; + /* If we are just looking for implementations and this is a property access expression, we need to get the + * symbol of the local type of the symbol the property is being accessed on. This is because our search + * symbol may have a different parent symbol if the local type's symbol does not declare the property + * being accessed (i.e. it is declared in some parent class or interface) + */ + function getParentSymbolsOfPropertyAccess(): Symbol[] | undefined { + if (implementations) { + const propertyAccessExpression = getPropertyAccessExpressionFromRightHandSide(searchLocation); + if (propertyAccessExpression) { + const localParentType = typeChecker.getTypeAtLocation(propertyAccessExpression.expression); + if (localParentType) { + if (localParentType.symbol && localParentType.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) && localParentType.symbol !== searchSymbol.parent) { + return [localParentType.symbol]; + } + else if (localParentType.flags & TypeFlags.UnionOrIntersection) { + return getSymbolsForClassAndInterfaceComponents(localParentType); + } + } + } + } + } + + function getPropertyAccessExpressionFromRightHandSide(node: Node): PropertyAccessExpression { + return isRightSideOfPropertyAccess(node) && node.parent; + } + /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ function findAdditionalConstructorReferences(referenceSymbol: Symbol, referenceLocation: Node): void { Debug.assert(isClassLike(searchSymbol.valueDeclaration)); @@ -534,6 +563,200 @@ namespace ts.FindAllReferences { return result[index]; } + + function addReferenceToRelatedSymbol(node: Node, relatedSymbol: Symbol) { + const references = getReferencedSymbol(relatedSymbol).references; + if (implementations) { + getImplementationReferenceEntryForNode(node, references); + } + else { + references.push(getReferenceEntryFromNode(node)); + } + } + } + + function getImplementationReferenceEntryForNode(refNode: Node, result: ReferenceEntry[]): void { + // Check if we found a function/propertyAssignment/method with an implementation or initializer + if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { + result.push(getReferenceEntryFromNode(refNode.parent)); + } + else if (refNode.kind === SyntaxKind.Identifier) { + if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + // Go ahead and dereference the shorthand assignment by going to its definition + getReferenceEntriesForShorthandPropertyAssignment(refNode, typeChecker, result); + } + + // Check if the node is within an extends or implements clause + const containingClass = getContainingClassIfInHeritageClause(refNode); + if (containingClass) { + result.push(getReferenceEntryFromNode(containingClass)); + return; + } + + // 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)) { + maybeAdd(getReferenceEntryFromNode(parent.initializer)); + } + else if (isFunctionLike(parent) && parent.type === containingTypeReference && parent.body) { + if (parent.body.kind === SyntaxKind.Block) { + forEachReturnStatement(parent.body, returnStatement => { + if (returnStatement.expression && isImplementationExpression(returnStatement.expression)) { + maybeAdd(getReferenceEntryFromNode(returnStatement.expression)); + } + }); + } + else if (isImplementationExpression(parent.body)) { + maybeAdd(getReferenceEntryFromNode(parent.body)); + } + } + else if (isAssertionExpression(parent) && isImplementationExpression(parent.expression)) { + maybeAdd(getReferenceEntryFromNode(parent.expression)); + } + } + } + + // Type nodes can contain multiple references to the same type. For example: + // let x: Foo & (Foo & Bar) = ... + // Because we are returning the implementation locations and not the identifier locations, + // duplicate entries would be returned here as each of the type references is part of + // the same implementation. For that reason, check before we add a new entry + function maybeAdd(a: ReferenceEntry) { + if (!forEach(result, b => a.fileName === b.fileName && a.textSpan.start === b.textSpan.start && a.textSpan.length === b.textSpan.length)) { + result.push(a); + } + } + } + + function getSymbolsForClassAndInterfaceComponents(type: UnionOrIntersectionType, result: Symbol[] = []): Symbol[] { + for (const componentType of type.types) { + if (componentType.symbol && componentType.symbol.getFlags() & (SymbolFlags.Class | SymbolFlags.Interface)) { + result.push(componentType.symbol); + } + if (componentType.getFlags() & TypeFlags.UnionOrIntersection) { + getSymbolsForClassAndInterfaceComponents(componentType, result); + } + } + return result; + } + + function getContainingTypeReference(node: Node): Node { + let topLevelTypeReference: Node = undefined; + + while (node) { + if (isTypeNode(node)) { + topLevelTypeReference = node; + } + node = node.parent; + } + + return topLevelTypeReference; + } + + function getContainingClassIfInHeritageClause(node: Node): ClassLikeDeclaration { + if (node && node.parent) { + if (node.kind === SyntaxKind.ExpressionWithTypeArguments + && node.parent.kind === SyntaxKind.HeritageClause + && isClassLike(node.parent.parent)) { + return node.parent.parent; + } + + else if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression) { + return getContainingClassIfInHeritageClause(node.parent); + } + } + return undefined; + } + + /** + * Returns true if this is an expression that can be considered an implementation + */ + function isImplementationExpression(node: Expression): boolean { + // Unwrap parentheses + if (node.kind === SyntaxKind.ParenthesizedExpression) { + return isImplementationExpression((node).expression); + } + + return node.kind === SyntaxKind.ArrowFunction || + node.kind === SyntaxKind.FunctionExpression || + node.kind === SyntaxKind.ObjectLiteralExpression || + node.kind === SyntaxKind.ClassExpression || + node.kind === SyntaxKind.ArrayLiteralExpression; + } + + /** + * Determines if the parent symbol occurs somewhere in the child's ancestry. If the parent symbol + * is an interface, determines if some ancestor of the child symbol extends or inherits from it. + * Also takes in a cache of previous results which makes this slightly more efficient and is + * necessary to avoid potential loops like so: + * class A extends B { } + * class B extends A { } + * + * We traverse the AST rather than using the type checker because users are typically only interested + * in explicit implementations of an interface/class when calling "Go to Implementation". Sibling + * implementations of types that share a common ancestor with the type whose implementation we are + * searching for need to be filtered out of the results. The type checker doesn't let us make the + * distinction between structurally compatible implementations and explicit implementations, so we + * must use the AST. + * + * @param child A class or interface Symbol + * @param parent Another class or interface Symbol + * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results + */ + function explicitlyInheritsFrom(child: Symbol, parent: Symbol, cachedResults: Map): boolean { + const parentIsInterface = parent.getFlags() & SymbolFlags.Interface; + return searchHierarchy(child); + + function searchHierarchy(symbol: Symbol): boolean { + if (symbol === parent) { + return true; + } + + const key = getSymbolId(symbol) + "," + getSymbolId(parent); + if (key in cachedResults) { + return cachedResults[key]; + } + + // Set the key so that we don't infinitely recurse + cachedResults[key] = false; + + const inherits = forEach(symbol.getDeclarations(), declaration => { + if (isClassLike(declaration)) { + if (parentIsInterface) { + const interfaceReferences = getClassImplementsHeritageClauseElements(declaration); + if (interfaceReferences) { + for (const typeReference of interfaceReferences) { + if (searchTypeReference(typeReference)) { + return true; + } + } + } + } + return searchTypeReference(getClassExtendsHeritageClauseElement(declaration)); + } + else if (declaration.kind === SyntaxKind.InterfaceDeclaration) { + if (parentIsInterface) { + return forEach(getInterfaceBaseTypeNodes(declaration), searchTypeReference); + } + } + return false; + }); + + cachedResults[key] = inherits; + return inherits; + } + + function searchTypeReference(typeReference: ExpressionWithTypeArguments): boolean { + if (typeReference) { + const type = typeChecker.getTypeAtLocation(typeReference); + if (type && type.symbol) { + return searchHierarchy(type.symbol); + } + } + return false; + } } function getReferencesForSuperKeyword(superKeyword: Node): ReferencedSymbol[] { @@ -696,6 +919,7 @@ namespace ts.FindAllReferences { } } + function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[]): ReferencedSymbol[] { const type = getStringLiteralTypeForNode(node, typeChecker); @@ -821,7 +1045,7 @@ namespace ts.FindAllReferences { } // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions - if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + if (!implementations && rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap()); } }); @@ -888,7 +1112,7 @@ namespace ts.FindAllReferences { } } - function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean): Symbol | undefined { + function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean, parents: Symbol[] | undefined, cache: Map): Symbol { if (contains(searchSymbols, referenceSymbol)) { // If we are searching for constructor uses, they must be 'new' expressions. return (!searchLocationIsConstructor || isNewExpressionTarget(referenceLocation)) && referenceSymbol; @@ -898,7 +1122,7 @@ namespace ts.FindAllReferences { // symbols but by looking up for related symbol of this alias so it can handle multiple level of indirectness. const aliasSymbol = getAliasSymbolForPropertyNameSymbol(referenceSymbol, referenceLocation); if (aliasSymbol) { - return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, searchLocationIsConstructor); + return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, searchLocationIsConstructor, parents, cache); } // If the reference location is in an object literal, try to get the contextual type for the @@ -941,8 +1165,16 @@ namespace ts.FindAllReferences { } // Finally, try all properties with the same name in any type the containing type extended or implemented, and - // see if any is in the list + // see if any is in the list. If we were passed a parent symbol, only include types that are subtypes of the + // parent symbol if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + // Parents will only be defined if implementations is true + if (parents) { + if (!forEach(parents, parent => explicitlyInheritsFrom(rootSymbol.parent, parent, cache))) { + return undefined; + } + } + const result: Symbol[] = []; getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap()); return forEach(result, s => searchSymbols.indexOf(s) >= 0 ? s : undefined); @@ -1035,7 +1267,55 @@ namespace ts.FindAllReferences { return referenceEntries; } - function getReferenceEntryFromNode(node: Node): ReferenceEntry { + function isImplementation(node: Node): boolean { + if (!node) { + return false; + } + else if (isVariableLike(node)) { + if (node.initializer) { + return true; + } + else if (node.kind === SyntaxKind.VariableDeclaration) { + const parentStatement = getParentStatementOfVariableDeclaration(node); + return parentStatement && hasModifier(parentStatement, ModifierFlags.Ambient); + } + } + else if (isFunctionLike(node)) { + return !!node.body || hasModifier(node, ModifierFlags.Ambient); + } + else { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ModuleDeclaration: + return true; + } + } + return false; + } + + function getParentStatementOfVariableDeclaration(node: VariableDeclaration): VariableStatement { + if (node.parent && node.parent.parent && node.parent.parent.kind === SyntaxKind.VariableStatement) { + Debug.assert(node.parent.kind === SyntaxKind.VariableDeclarationList); + return node.parent.parent; + } + } + + export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, typeChecker: TypeChecker, result: ReferenceEntry[]): void { + const refSymbol = typeChecker.getSymbolAtLocation(node); + const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); + + if (shorthandSymbol) { + for (const declaration of shorthandSymbol.getDeclarations()) { + if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { + result.push(getReferenceEntryFromNode(declaration)); + } + } + } + } + + export function getReferenceEntryFromNode(node: Node): ReferenceEntry { let start = node.getStart(); let end = node.getEnd(); diff --git a/src/services/goToImplementation.ts b/src/services/goToImplementation.ts new file mode 100644 index 0000000000000..123d29630ad81 --- /dev/null +++ b/src/services/goToImplementation.ts @@ -0,0 +1,27 @@ +/* @internal */ +namespace ts.GoToImplementation { + export function getImplementationAtPosition(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): ImplementationLocation[] { + // If invoked directly on a shorthand property assignment, then return + // the declaration of the symbol being assigned (not the symbol being assigned to). + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + const result: ReferenceEntry[] = []; + FindAllReferences.getReferenceEntriesForShorthandPropertyAssignment(node, typeChecker, result); + return result.length > 0 ? result : undefined; + } + else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) { + // References to and accesses on the super keyword only have one possible implementation, so no + // need to "Find all References" + const symbol = typeChecker.getSymbolAtLocation(node); + return symbol.valueDeclaration && [FindAllReferences.getReferenceEntryFromNode(symbol.valueDeclaration)]; + } + else { + // Perform "Find all References" and retrieve only those that are implementations + const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, + node, sourceFiles, /*findInStrings*/false, /*findInComments*/false, /*implementations*/true); + const result = flatMap(referencedSymbols, symbol => + map(symbol.references, ({ textSpan, fileName }) => ({ textSpan, fileName }))); + + return result && result.length > 0 ? result : undefined; + } + } +} diff --git a/src/services/services.ts b/src/services/services.ts index f3e95e9486853..001bbcfaec29f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -10,6 +10,7 @@ /// /// /// +/// /// /// /// @@ -1271,6 +1272,13 @@ namespace ts { return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position); } + /// Goto implementation + function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { + synchronizeHostData(); + return GoToImplementation.getImplementationAtPosition(program.getTypeChecker(), cancellationToken, + program.getSourceFiles(), getTouchingPropertyName(getValidSourceFile(fileName), position)); + } + function getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { synchronizeHostData(); return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position); @@ -1781,6 +1789,7 @@ namespace ts { getSignatureHelpItems, getQuickInfoAtPosition, getDefinitionAtPosition, + getImplementationAtPosition, getTypeDefinitionAtPosition, getReferencesAtPosition, findReferences, diff --git a/src/services/shims.ts b/src/services/shims.ts index b7de34f53d6e8..260c80127b5bb 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -177,6 +177,12 @@ namespace ts { */ getTypeDefinitionAtPosition(fileName: string, position: number): string; + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; }[] + */ + 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 }[] @@ -798,6 +804,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})`, diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 59fd0048c82ba..58312c6f38ff3 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -49,6 +49,7 @@ "documentRegistry.ts", "findAllReferences.ts", "goToDefinition.ts", + "goToImplementation.ts", "jsDoc.ts", "jsTyping.ts", "navigateTo.ts", diff --git a/src/services/types.ts b/src/services/types.ts index 08501dc872549..7bd325482e6ed 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -209,6 +209,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[]; @@ -297,6 +298,11 @@ namespace ts { isDefinition: boolean; } + export interface ImplementationLocation { + textSpan: TextSpan; + fileName: string; + } + export interface DocumentHighlights { fileName: string; highlightSpans: HighlightSpan[]; diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index b8c8402705376..5f86cbbafa488 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -111,6 +111,7 @@ declare namespace FourSlashInterface { bof(): void; eof(): void; type(definitionIndex?: number): void; + implementation(): void; position(position: number, fileIndex?: number): any; position(position: number, fileName?: string): any; file(index: number, content?: string, scriptKindName?: string): any; @@ -133,6 +134,7 @@ declare namespace FourSlashInterface { errorExistsBeforeMarker(markerName?: string): void; quickInfoExists(): void; typeDefinitionCountIs(expectedCount: number): void; + implementationListIsEmpty(): void; isValidBraceCompletionAtPosition(openingBrace?: string): void; } class verify extends verifyNegatable { @@ -250,6 +252,7 @@ declare namespace FourSlashInterface { getSyntacticDiagnostics(expected: string): void; getSemanticDiagnostics(expected: string): void; ProjectInfo(expected: string[]): void; + allRangesAppearInImplementationList(markerName: string): void; } class edit { backspace(count?: number): void; diff --git a/tests/cases/fourslash/goToImplementationClassMethod_00.ts b/tests/cases/fourslash/goToImplementationClassMethod_00.ts new file mode 100644 index 0000000000000..6fc8d9bf7ccdb --- /dev/null +++ b/tests/cases/fourslash/goToImplementationClassMethod_00.ts @@ -0,0 +1,11 @@ +/// + +// Should handle calls made on members declared in a class + +//// class Bar { +//// [|hello() {}|] +//// } +//// +//// new Bar().hel/*reference*/lo; + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationClassMethod_01.ts b/tests/cases/fourslash/goToImplementationClassMethod_01.ts new file mode 100644 index 0000000000000..7f59376a6ae34 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationClassMethod_01.ts @@ -0,0 +1,18 @@ +/// + +// 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(); +//// } + +verify.allRangesAppearInImplementationList("reference"); +verify.allRangesAppearInImplementationList("declaration"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationEnum_00.ts b/tests/cases/fourslash/goToImplementationEnum_00.ts new file mode 100644 index 0000000000000..81e72b871a274 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationEnum_00.ts @@ -0,0 +1,12 @@ +/// + +// Should handle calls made on members of an enum + +//// enum Foo { +//// [|Foo1 = function initializer() { return 5 } ()|], +//// Foo2 = 6 +//// } +//// +//// Foo.Fo/*reference*/o1; + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationEnum_01.ts b/tests/cases/fourslash/goToImplementationEnum_01.ts new file mode 100644 index 0000000000000..43273b6b03a5c --- /dev/null +++ b/tests/cases/fourslash/goToImplementationEnum_01.ts @@ -0,0 +1,12 @@ +/// + +// Should handle calls made on enum name + +//// [|enum Foo { +//// Foo1 = function initializer() { return 5 } (), +//// Foo2 = 6 +//// }|] +//// +//// Fo/*reference*/o; + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts new file mode 100644 index 0000000000000..38afd0cfb3dd0 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts @@ -0,0 +1,25 @@ +/// + +// Should return method implementations in object literals within variable-like declarations + +//// interface Foo { +//// he/*declaration*/llo: () => void +//// } +//// +//// var bar: Foo = { [|hello: helloImpl|] }; +//// var baz: 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*/}|] } ) {} +//// } + +verify.allRangesAppearInImplementationList("function_call"); +verify.allRangesAppearInImplementationList("declaration"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_01.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_01.ts new file mode 100644 index 0000000000000..8bd328bcb54eb --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_01.ts @@ -0,0 +1,22 @@ +/// + +// 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()); + +verify.allRangesAppearInImplementationList("function_call"); +verify.allRangesAppearInImplementationList("declaration"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_02.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_02.ts new file mode 100644 index 0000000000000..82dceaad91c1d --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_02.ts @@ -0,0 +1,22 @@ +/// + +// 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(); +//// } + +verify.allRangesAppearInImplementationList("function_call"); +verify.allRangesAppearInImplementationList("declaration"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_03.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_03.ts new file mode 100644 index 0000000000000..8826cc1eab4f1 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_03.ts @@ -0,0 +1,24 @@ +/// + +// 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"](); + +verify.allRangesAppearInImplementationList("function_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_04.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_04.ts new file mode 100644 index 0000000000000..a9b825c717f5a --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_04.ts @@ -0,0 +1,25 @@ +/// + +// 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() +//// } + +verify.allRangesAppearInImplementationList("function_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_05.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_05.ts new file mode 100644 index 0000000000000..b051e16a92998 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_05.ts @@ -0,0 +1,37 @@ +/// + +// 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() {} +//// 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() +//// } + +verify.allRangesAppearInImplementationList("function_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_06.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_06.ts new file mode 100644 index 0000000000000..b340270096da9 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_06.ts @@ -0,0 +1,48 @@ +/// + +// 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() {} +//// 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() +//// } + +verify.allRangesAppearInImplementationList("function_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_08.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_08.ts new file mode 100644 index 0000000000000..d2038f99efd8b --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_08.ts @@ -0,0 +1,22 @@ +/// + +// 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() {}|] +//// } + + +verify.allRangesAppearInImplementationList("function_call"); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts new file mode 100644 index 0000000000000..84c57ec2bce37 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts @@ -0,0 +1,31 @@ +/// + +// 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(); +//// super["hel/*element_access*/lo"](); +//// } +//// } +//// +//// class SuperBar extends MegaBar { +//// [|hello() {}|] +//// } +//// +//// class MegaBar implements Foo { +//// hello() {} +//// } + +verify.allRangesAppearInImplementationList("function_call"); +verify.allRangesAppearInImplementationList("element_access"); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts new file mode 100644 index 0000000000000..0604d2511f28a --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts @@ -0,0 +1,48 @@ +/// + +// Should handle union and intersection types + +//// interface BaseFoo { +//// hello(): void; +//// } +//// +//// interface Foo extends BaseFoo { +//// aloha(): void; +//// } +//// +//// interface Bar { +//// hello(): void; +//// goodbye(): void; +//// } +//// +//// class FooImpl implements Foo { +//// [|hello() {/**FooImpl*/}|] +//// aloha() {} +//// } +//// +//// class BaseFooImpl implements BaseFoo { +//// hello() {/**BaseFooImpl*/} // Should not show up +//// } +//// +//// class BarImpl implements Bar { +//// [|hello() {/**BarImpl*/}|] +//// goodbye() {} +//// } +//// +//// class FooAndBarImpl implements Foo, Bar { +//// [|hello() {/**FooAndBarImpl*/}|] +//// aloha() {} +//// goodbye() {} +//// } +//// +//// function someFunction(x: Foo | Bar) { +//// x.he/*function_call0*/llo(); +//// } +//// +//// function anotherFunction(x: Foo & Bar) { +//// x.he/*function_call1*/llo(); +//// } + +for (var i = 0; i < 2; i++) { + verify.allRangesAppearInImplementationList("function_call" + i); +} diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts new file mode 100644 index 0000000000000..9528aa409b496 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts @@ -0,0 +1,12 @@ +/// + +// Should handle members of object literals in type assertion expressions + +//// interface Foo { +//// hel/*reference*/lo(): void; +//// } +//// +//// var x = { [|hello: () => {}|] }; +//// var y = (((({ [|hello: () => {}|] })))); + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceProperty_00.ts b/tests/cases/fourslash/goToImplementationInterfaceProperty_00.ts new file mode 100644 index 0000000000000..70e49c10b45af --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceProperty_00.ts @@ -0,0 +1,22 @@ +/// + +// 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|] } ) {} +//// } + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceProperty_01.ts b/tests/cases/fourslash/goToImplementationInterfaceProperty_01.ts new file mode 100644 index 0000000000000..14b80362917dd --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceProperty_01.ts @@ -0,0 +1,15 @@ +/// + +// 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; +//// } + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterface_00.ts b/tests/cases/fourslash/goToImplementationInterface_00.ts new file mode 100644 index 0000000000000..8fc36b4bab547 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_00.ts @@ -0,0 +1,25 @@ +/// + +// 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*/ }|]; +//// var baz: Foo[] = [|[{ hello: helloImpl /**4*/ }]|]; +//// +//// function helloImpl () {} +//// +//// function whatever(x: Foo = [|{ hello() {/**1*/} }|] ) { +//// } +//// +//// class Bar { +//// x: Foo = [|{ hello() {/*2*/} }|] +//// +//// constructor(public f: Foo = [|{ hello() {/**3*/} }|] ) {} +//// } + +verify.allRangesAppearInImplementationList("interface_definition"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterface_01.ts b/tests/cases/fourslash/goToImplementationInterface_01.ts new file mode 100644 index 0000000000000..62d01da6ca8c9 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_01.ts @@ -0,0 +1,24 @@ +/// + +//// 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(); + +verify.allRangesAppearInImplementationList("interface_definition"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterface_02.ts b/tests/cases/fourslash/goToImplementationInterface_02.ts new file mode 100644 index 0000000000000..27f29465a9a2c --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_02.ts @@ -0,0 +1,28 @@ +/// + +// 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 } +//// +//// let x: number = 9; +//// +//// function createFoo(): Foo { +//// if (x === 2) { +//// return [|{ +//// hello() {} +//// }|]; +//// } +//// return [|{ +//// hello() {} +//// }|]; +//// } +//// +//// let createFoo2 = (): Foo => [|({hello() {}})|]; +//// +//// function createFooLike() { +//// return { +//// hello() {} +//// }; +//// } + +verify.allRangesAppearInImplementationList("interface_definition"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterface_03.ts b/tests/cases/fourslash/goToImplementationInterface_03.ts new file mode 100644 index 0000000000000..1c63852dafcad --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_03.ts @@ -0,0 +1,9 @@ +/// + +// Should go to object literals within cast expressions when invoked on interface + +//// interface Fo/*interface_definition*/o { hello: () => void } +//// +//// var x = [|{ hello: () => {} }|]; + +verify.allRangesAppearInImplementationList("interface_definition"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterface_04.ts b/tests/cases/fourslash/goToImplementationInterface_04.ts new file mode 100644 index 0000000000000..febc511388d44 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_04.ts @@ -0,0 +1,20 @@ +/// + +// 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) {}|] ) {} +//// } + +verify.allRangesAppearInImplementationList("interface_definition"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterface_05.ts b/tests/cases/fourslash/goToImplementationInterface_05.ts new file mode 100644 index 0000000000000..3b31b89b52852 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_05.ts @@ -0,0 +1,12 @@ +/// + +// 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 = [|function(a) {}|]; +//// + +verify.allRangesAppearInImplementationList("interface_definition"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterface_06.ts b/tests/cases/fourslash/goToImplementationInterface_06.ts new file mode 100644 index 0000000000000..15f521328685d --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_06.ts @@ -0,0 +1,14 @@ +/// + +// 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 = [|class { constructor (a: number) {} }|]; + +verify.allRangesAppearInImplementationList("interface_definition"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterface_07.ts b/tests/cases/fourslash/goToImplementationInterface_07.ts new file mode 100644 index 0000000000000..6d874b3dc7630 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_07.ts @@ -0,0 +1,28 @@ +/// + +// Should handle all the various type references + +//// interface Fo/*interface_definition*/o { +//// hello (): void; +//// } +//// +//// interface Bar { +//// hello (): void; +//// } +//// +//// let x1: Foo = [|{ hello () { /**typeReference*/ } }|]; +//// let x2: () => Foo = [|(() => { hello () { /**functionType*/} })|]; +//// let x3: Foo | Bar = [|{ hello () { /**unionType*/} }|]; +//// let x4: Foo & (Foo & Bar) = [|{ hello () { /**intersectionType*/} }|]; +//// let x5: [Foo] = [|[{ hello () { /**tupleType*/} }]|]; +//// let x6: (Foo) = [|{ hello () { /**parenthesizedType*/} }|]; +//// let x7: (new() => Foo) = [|class { hello () { /**constructorType*/} }|]; +//// let x8: Foo[] = [|[{ hello () { /**arrayType*/} }]|]; +//// let x9: { y: Foo } = [|{ y: { hello () { /**typeLiteral*/} } }|]; +//// +//// // Should not do anything for type predicates +//// function isFoo(a: any): a is Foo { +//// return true; +//// } + +verify.allRangesAppearInImplementationList("interface_definition"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterface_08.ts b/tests/cases/fourslash/goToImplementationInterface_08.ts new file mode 100644 index 0000000000000..652cfe46246aa --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_08.ts @@ -0,0 +1,21 @@ +/// + +// Should not hang on inheritance loops + +//// interface Base { +//// hello (): void; +//// } +//// +//// interface A extends Base {} +//// interface B extends C, A {} +//// interface C extends B, A {} +//// +//// class X implements B { +//// [|hello() {}|] +//// } +//// +//// function someFunction(d : A) { +//// d.he/*function_call*/llo(); +//// } + +verify.allRangesAppearInImplementationList("function_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInvalid.ts b/tests/cases/fourslash/goToImplementationInvalid.ts new file mode 100644 index 0000000000000..52b2773342e1f --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInvalid.ts @@ -0,0 +1,12 @@ +/// + +// 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.implementationListIsEmpty(); +} \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationLocal_00.ts b/tests/cases/fourslash/goToImplementationLocal_00.ts new file mode 100644 index 0000000000000..b97fadfcb02e7 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_00.ts @@ -0,0 +1,8 @@ +/// + +// Should return definition of locally declared functions + +//// he/*function_call*/llo(); +//// [|function hello() {}|] + +verify.allRangesAppearInImplementationList("function_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationLocal_01.ts b/tests/cases/fourslash/goToImplementationLocal_01.ts new file mode 100644 index 0000000000000..0bda3418354a5 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_01.ts @@ -0,0 +1,8 @@ +/// + +// Should return the defintion of locally defined variables + +//// const [|hello = function() {}|]; +//// he/*function_call*/llo(); + +verify.allRangesAppearInImplementationList("function_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationLocal_02.ts b/tests/cases/fourslash/goToImplementationLocal_02.ts new file mode 100644 index 0000000000000..62bb8a71a1086 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_02.ts @@ -0,0 +1,8 @@ +/// + +//// const x = { [|hello: () => {}|] }; +//// +//// x.he/*function_call*/llo(); +//// + +verify.allRangesAppearInImplementationList("function_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationLocal_03.ts b/tests/cases/fourslash/goToImplementationLocal_03.ts new file mode 100644 index 0000000000000..d3f25ea29dc96 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_03.ts @@ -0,0 +1,12 @@ +/// + +// Should return the definition when invoked on variable assignment + +//// let [|he/*local_var*/llo = {}|]; +//// +//// x.hello(); +//// +//// hello = {}; +//// + +verify.allRangesAppearInImplementationList("local_var"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationLocal_04.ts b/tests/cases/fourslash/goToImplementationLocal_04.ts new file mode 100644 index 0000000000000..9de9dcd0a9f5f --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_04.ts @@ -0,0 +1,10 @@ +/// + +// Should return definition of function when invoked on the declaration + +//// [|function he/*local_var*/llo() {}|] +//// +//// hello(); +//// + +verify.allRangesAppearInImplementationList("local_var"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationLocal_05.ts b/tests/cases/fourslash/goToImplementationLocal_05.ts new file mode 100644 index 0000000000000..969f4c25fc737 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_05.ts @@ -0,0 +1,12 @@ +/// + +// 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(); + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationLocal_06.ts b/tests/cases/fourslash/goToImplementationLocal_06.ts new file mode 100644 index 0000000000000..acdbaffc36f46 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_06.ts @@ -0,0 +1,8 @@ +/// + +// Should be able to go to ambient variable declarations + +//// declare var [|someVar: string|]; +//// someVa/*reference*/r + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationLocal_07.ts b/tests/cases/fourslash/goToImplementationLocal_07.ts new file mode 100644 index 0000000000000..3556c25abc0b7 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_07.ts @@ -0,0 +1,8 @@ +/// + +// Should be able to go to ambient function declarations + +//// [|declare function someFunction(): () => void;|] +//// someFun/*reference*/ction(); + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationLocal_08.ts b/tests/cases/fourslash/goToImplementationLocal_08.ts new file mode 100644 index 0000000000000..3556c25abc0b7 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_08.ts @@ -0,0 +1,8 @@ +/// + +// Should be able to go to ambient function declarations + +//// [|declare function someFunction(): () => void;|] +//// someFun/*reference*/ction(); + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_00.ts b/tests/cases/fourslash/goToImplementationNamespace_00.ts new file mode 100644 index 0000000000000..5b71b7bbf9fa1 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_00.ts @@ -0,0 +1,20 @@ +/// + +// Should handle namespace and module implementations + +//// /*implementation0*/namespace Foo { +//// export function hello() {} +//// } +//// +//// /*implementation1*/module Bar { +//// export function sure() {} +//// } +//// +//// let x = Fo/*reference0*/o; +//// let y = Ba/*reference1*/r; + +for (let i = 0; i < 2; i ++) { + goTo.marker("reference" + i); + goTo.implementation(); + verify.caretAtMarker("implementation" + i); +} \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_01.ts b/tests/cases/fourslash/goToImplementationNamespace_01.ts new file mode 100644 index 0000000000000..b2e267d537871 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_01.ts @@ -0,0 +1,11 @@ +/// + +// Should handle property access expressions on namespaces + +//// namespace Foo { +//// [|export function hello() {}|] +//// } +//// +//// Foo.hell/*reference*/o(); + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_02.ts b/tests/cases/fourslash/goToImplementationNamespace_02.ts new file mode 100644 index 0000000000000..2d2ae960a99fc --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_02.ts @@ -0,0 +1,11 @@ +/// + +// Should handle property access expressions on namespaces + +//// module Foo { +//// [|export function hello() {}|] +//// } +//// +//// Foo.hell/*reference*/o(); + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_03.ts b/tests/cases/fourslash/goToImplementationNamespace_03.ts new file mode 100644 index 0000000000000..751d182ec00ab --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_03.ts @@ -0,0 +1,27 @@ +/// + +// 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 = [|{ hello: () => {/**2*/} }|]; +//// +//// function whatever(x: Foo.Ba/*reference*/r) { +//// +//// } + +verify.allRangesAppearInImplementationList("reference"); diff --git a/tests/cases/fourslash/goToImplementationNamespace_04.ts b/tests/cases/fourslash/goToImplementationNamespace_04.ts new file mode 100644 index 0000000000000..ac0b85e43fe33 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_04.ts @@ -0,0 +1,27 @@ +/// + +// 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 = [|{ hello: () => {/**2*/} }|]; +//// +//// function whatever(x: Foo.Ba/*reference*/r) { +//// +//// } + +verify.allRangesAppearInImplementationList("reference"); diff --git a/tests/cases/fourslash/goToImplementationNamespace_05.ts b/tests/cases/fourslash/goToImplementationNamespace_05.ts new file mode 100644 index 0000000000000..6936b79bd9b09 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_05.ts @@ -0,0 +1,22 @@ +/// + +// Should handle namespace and module implementations with qualified names + +//// /*implementation0*/namespace Foo./*implementation2*/Baz { +//// export function hello() {} +//// } +//// +//// /*implementation1*/module Bar./*implementation3*/Baz { +//// export function sure() {} +//// } +//// +//// let x = Fo/*reference0*/o; +//// let y = Ba/*reference1*/r; +//// let x1 = Foo.B/*reference2*/az; +//// let y1 = Bar.B/*reference3*/az; + +for (let i = 0; i < 4; i ++) { + goTo.marker("reference" + i); + goTo.implementation(); + verify.caretAtMarker("implementation" + i); +} \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_06.ts b/tests/cases/fourslash/goToImplementationNamespace_06.ts new file mode 100644 index 0000000000000..f5e52d90b1c22 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_06.ts @@ -0,0 +1,12 @@ +/// + +// Should handle type queries + +//// [|namespace F/*declaration*/oo { +//// declare function hello(): void; +//// }|] +//// +//// +//// let x: typeof Foo = [|{ hello() {} }|]; + +verify.allRangesAppearInImplementationList("declaration"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_00.ts b/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_00.ts new file mode 100644 index 0000000000000..ad4a0cd6b5135 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_00.ts @@ -0,0 +1,43 @@ +/// + +// 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"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_01.ts b/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_01.ts new file mode 100644 index 0000000000000..fb4763619a4ba --- /dev/null +++ b/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_01.ts @@ -0,0 +1,47 @@ +/// + +// 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; + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_02.ts b/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_02.ts new file mode 100644 index 0000000000000..a27480c82728a --- /dev/null +++ b/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_02.ts @@ -0,0 +1,21 @@ +/// + +// 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(); +//// } + +verify.allRangesAppearInImplementationList("function_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationSuper_00.ts b/tests/cases/fourslash/goToImplementationSuper_00.ts new file mode 100644 index 0000000000000..53377bdcdb004 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationSuper_00.ts @@ -0,0 +1,15 @@ +/// + +// 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(); +//// } +//// } + +verify.allRangesAppearInImplementationList("super_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationSuper_01.ts b/tests/cases/fourslash/goToImplementationSuper_01.ts new file mode 100644 index 0000000000000..fffa7caf71690 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationSuper_01.ts @@ -0,0 +1,15 @@ +/// + +// 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(); +//// } +//// } + +verify.allRangesAppearInImplementationList("super_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationThis_00.ts b/tests/cases/fourslash/goToImplementationThis_00.ts new file mode 100644 index 0000000000000..19212c9a77ac5 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationThis_00.ts @@ -0,0 +1,13 @@ +/// + +// 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() {} +//// }|] + +verify.allRangesAppearInImplementationList("this_call"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationThis_01.ts b/tests/cases/fourslash/goToImplementationThis_01.ts new file mode 100644 index 0000000000000..6761c6ec6c908 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationThis_01.ts @@ -0,0 +1,11 @@ +/// + +// Should go to class declaration when invoked on a this type reference + +//// [|class Bar extends Foo { +//// hello(): th/*this_type*/is { +//// return this; +//// } +//// }|] + +verify.allRangesAppearInImplementationList("this_type"); \ No newline at end of file diff --git a/tests/cases/fourslash/server/implementation01.ts b/tests/cases/fourslash/server/implementation01.ts new file mode 100644 index 0000000000000..24d5f935bdace --- /dev/null +++ b/tests/cases/fourslash/server/implementation01.ts @@ -0,0 +1,9 @@ +/// + +// @Filename: a.ts +//// interface Fo/*1*/o {} +//// /*2*/class Bar implements Foo {} + +goTo.marker('1'); +goTo.implementation(); +verify.caretAtMarker('2'); \ No newline at end of file diff --git a/tests/cases/fourslash/shims-pp/getImplementationAtPosition.ts b/tests/cases/fourslash/shims-pp/getImplementationAtPosition.ts new file mode 100644 index 0000000000000..a509922f91943 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/getImplementationAtPosition.ts @@ -0,0 +1,35 @@ +/// + +// @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'); +}); diff --git a/tests/cases/fourslash/shims/getImplementationAtPosition.ts b/tests/cases/fourslash/shims/getImplementationAtPosition.ts new file mode 100644 index 0000000000000..a509922f91943 --- /dev/null +++ b/tests/cases/fourslash/shims/getImplementationAtPosition.ts @@ -0,0 +1,35 @@ +/// + +// @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'); +});