From 111e50921c20c120bb123945f84949365e020eb3 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 22 Aug 2016 13:57:40 -0700 Subject: [PATCH 01/13] Go to Implementation --- src/compiler/checker.ts | 3 +- src/compiler/types.ts | 1 + src/compiler/utilities.ts | 5 + src/harness/fourslash.ts | 62 +++ src/harness/harnessLanguageService.ts | 3 + src/server/client.ts | 22 + src/server/protocol.d.ts | 15 + src/server/session.ts | 27 + src/services/services.ts | 478 +++++++++++++++++- src/services/shims.ts | 19 + tests/cases/fourslash/fourslash.ts | 3 + .../goToImplementationClassMethod_00.ts | 12 + .../goToImplementationClassMethod_01.ts | 21 + .../fourslash/goToImplementationEnum_00.ts | 13 + .../fourslash/goToImplementationEnum_01.ts | 13 + .../goToImplementationInterfaceMethod_00.ts | 27 + .../goToImplementationInterfaceMethod_01.ts | 25 + .../goToImplementationInterfaceMethod_02.ts | 25 + .../goToImplementationInterfaceMethod_03.ts | 25 + .../goToImplementationInterfaceMethod_04.ts | 26 + .../goToImplementationInterfaceMethod_05.ts | 38 ++ .../goToImplementationInterfaceMethod_06.ts | 49 ++ .../goToImplementationInterfaceMethod_08.ts | 23 + .../goToImplementationInterfaceMethod_09.ts | 30 ++ .../goToImplementationInterfaceMethod_10.ts | 36 ++ .../goToImplementationInterfaceMethod_11.ts | 36 ++ .../goToImplementationInterfaceMethod_12.ts | 12 + .../goToImplementationInterfaceProperty_00.ts | 23 + .../goToImplementationInterfaceProperty_01.ts | 16 + .../goToImplementationInterface_00.ts | 26 + .../goToImplementationInterface_01.ts | 25 + .../goToImplementationInterface_02.ts | 20 + .../goToImplementationInterface_03.ts | 10 + .../goToImplementationInterface_04.ts | 22 + .../goToImplementationInterface_05.ts | 14 + .../goToImplementationInterface_06.ts | 16 + .../fourslash/goToImplementationInvalid.ts | 12 + .../fourslash/goToImplementationLocal_00.ts | 9 + .../fourslash/goToImplementationLocal_01.ts | 9 + .../fourslash/goToImplementationLocal_02.ts | 8 + .../fourslash/goToImplementationLocal_03.ts | 12 + .../fourslash/goToImplementationLocal_04.ts | 10 + .../fourslash/goToImplementationLocal_05.ts | 12 + .../goToImplementationNamespace_00.ts | 12 + .../goToImplementationNamespace_01.ts | 12 + .../goToImplementationNamespace_02.ts | 12 + .../goToImplementationNamespace_03.ts | 12 + .../goToImplementationNamespace_04.ts | 26 + .../goToImplementationNamespace_05.ts | 26 + .../goToImplementationNamespace_06.ts | 28 + .../goToImplementationNamespace_07.ts | 28 + ...mentationShorthandPropertyAssignment_00.ts | 43 ++ ...mentationShorthandPropertyAssignment_01.ts | 48 ++ ...mentationShorthandPropertyAssignment_02.ts | 22 + .../fourslash/goToImplementationSuper_00.ts | 16 + .../fourslash/goToImplementationSuper_01.ts | 16 + .../fourslash/goToImplementationThis_00.ts | 14 + .../fourslash/goToImplementationThis_01.ts | 12 + .../fourslash/server/implementation01.ts | 9 + .../shims-pp/getImplementationAtPosition.ts | 35 ++ .../shims/getImplementationAtPosition.ts | 35 ++ 61 files changed, 1670 insertions(+), 29 deletions(-) create mode 100644 tests/cases/fourslash/goToImplementationClassMethod_00.ts create mode 100644 tests/cases/fourslash/goToImplementationClassMethod_01.ts create mode 100644 tests/cases/fourslash/goToImplementationEnum_00.ts create mode 100644 tests/cases/fourslash/goToImplementationEnum_01.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_01.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_02.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_03.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_04.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_05.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_06.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_08.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_12.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceProperty_00.ts create mode 100644 tests/cases/fourslash/goToImplementationInterfaceProperty_01.ts create mode 100644 tests/cases/fourslash/goToImplementationInterface_00.ts create mode 100644 tests/cases/fourslash/goToImplementationInterface_01.ts create mode 100644 tests/cases/fourslash/goToImplementationInterface_02.ts create mode 100644 tests/cases/fourslash/goToImplementationInterface_03.ts create mode 100644 tests/cases/fourslash/goToImplementationInterface_04.ts create mode 100644 tests/cases/fourslash/goToImplementationInterface_05.ts create mode 100644 tests/cases/fourslash/goToImplementationInterface_06.ts create mode 100644 tests/cases/fourslash/goToImplementationInvalid.ts create mode 100644 tests/cases/fourslash/goToImplementationLocal_00.ts create mode 100644 tests/cases/fourslash/goToImplementationLocal_01.ts create mode 100644 tests/cases/fourslash/goToImplementationLocal_02.ts create mode 100644 tests/cases/fourslash/goToImplementationLocal_03.ts create mode 100644 tests/cases/fourslash/goToImplementationLocal_04.ts create mode 100644 tests/cases/fourslash/goToImplementationLocal_05.ts create mode 100644 tests/cases/fourslash/goToImplementationNamespace_00.ts create mode 100644 tests/cases/fourslash/goToImplementationNamespace_01.ts create mode 100644 tests/cases/fourslash/goToImplementationNamespace_02.ts create mode 100644 tests/cases/fourslash/goToImplementationNamespace_03.ts create mode 100644 tests/cases/fourslash/goToImplementationNamespace_04.ts create mode 100644 tests/cases/fourslash/goToImplementationNamespace_05.ts create mode 100644 tests/cases/fourslash/goToImplementationNamespace_06.ts create mode 100644 tests/cases/fourslash/goToImplementationNamespace_07.ts create mode 100644 tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_00.ts create mode 100644 tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_01.ts create mode 100644 tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_02.ts create mode 100644 tests/cases/fourslash/goToImplementationSuper_00.ts create mode 100644 tests/cases/fourslash/goToImplementationSuper_01.ts create mode 100644 tests/cases/fourslash/goToImplementationThis_00.ts create mode 100644 tests/cases/fourslash/goToImplementationThis_01.ts create mode 100644 tests/cases/fourslash/server/implementation01.ts create mode 100644 tests/cases/fourslash/shims-pp/getImplementationAtPosition.ts create mode 100644 tests/cases/fourslash/shims/getImplementationAtPosition.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 193deded4c5fa..f7ce30fe4f340 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -103,7 +103,8 @@ namespace ts { getJsxElementAttributesType, getJsxIntrinsicTagNames, - isOptionalParameter + isOptionalParameter, + isTypeAssignableTo }; const tupleTypes = createMap(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a61d4a360b30c..adfde2d060576 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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[]; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 870df4a54af56..35dea56cf0bf4 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1669,6 +1669,11 @@ namespace ts { return false; } + export function isFunctionDeclarationIdentifierName(node: Identifier): boolean { + return node.parent.kind === SyntaxKind.FunctionDeclaration && + (node.parent).name === node; + } + // An alias symbol is created by one of the following declarations: // import = ... // import from ... diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 63731417f480a..27baabf652a66 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -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 { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 9412443278002..5209c6d739849 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -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)); } diff --git a/src/server/client.ts b/src/server/client.ts index 88177e91fad7b..a9938ca85ec31 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -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(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 6442848abbe4b..5263b86e2a346 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 9383a54bd48f7..783451e02c6b9 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"; @@ -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 = 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/services.ts b/src/services/services.ts index d200e081c5aa0..bb63f30c603bf 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -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 = (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 !!(parent).body; + case SyntaxKind.PropertyDeclaration: + return !!(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) && + (node.parent).name === node; + } + + function isIdentifierOfEnumDeclaration(node: Identifier) { + return node.parent.kind === SyntaxKind.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(); + const symbol = typeChecker.getSymbolAtLocation(node); - // 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 (!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); + } - const symbol = typeChecker.getSymbolAtLocation(node); - if (!symbol && node.kind === SyntaxKind.StringLiteral) { - return getReferencesForStringLiteral(node, sourceFiles); + if (!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. @@ -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* { + 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((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(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(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 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((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()); + 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, diff --git a/src/services/shims.ts b/src/services/shims.ts index 9a581ed6be18d..612ed955ed4dc 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -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})`, diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 9ce3197a46f73..f35e4fc3e0ee0 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -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; diff --git a/tests/cases/fourslash/goToImplementationClassMethod_00.ts b/tests/cases/fourslash/goToImplementationClassMethod_00.ts new file mode 100644 index 0000000000000..465db091d3203 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationClassMethod_00.ts @@ -0,0 +1,12 @@ +/// + +// Should handle calls made on members declared in a class + +//// class Bar { +//// [|hello() {}|] +//// } +//// +//// new Bar().hel/*reference*/lo; + +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ 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..8b1cb3e762d6b --- /dev/null +++ b/tests/cases/fourslash/goToImplementationClassMethod_01.ts @@ -0,0 +1,21 @@ +/// + +// 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(); \ 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..3a93542f80692 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationEnum_00.ts @@ -0,0 +1,13 @@ +/// + +// 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(); \ 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..2e7203f3c8f68 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationEnum_01.ts @@ -0,0 +1,13 @@ +/// + +// Should handle calls made on enum name + +//// [|enum Foo { +//// Foo1 = function initializer() { return 5 } (), +//// Foo2 = 6 +//// }|] +//// +//// Fo/*reference*/o; + +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ 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..71e6266aa201f --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts @@ -0,0 +1,27 @@ +/// + +// 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(); \ 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..5f5ba64338b7b --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_01.ts @@ -0,0 +1,25 @@ +/// + +// 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(); \ 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..f5b1f1eea9318 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_02.ts @@ -0,0 +1,25 @@ +/// + +// 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(); \ 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..cc22956bb0553 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_03.ts @@ -0,0 +1,25 @@ +/// + +// 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(); \ 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..32f5ff414843b --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_04.ts @@ -0,0 +1,26 @@ +/// + +// 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(); \ 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..43079b20dcc34 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_05.ts @@ -0,0 +1,38 @@ +/// + +// 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(); \ 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..175b5bd50fc14 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_06.ts @@ -0,0 +1,49 @@ +/// + +// 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(); \ 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..7d0be4a38a6d7 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_08.ts @@ -0,0 +1,23 @@ +/// + +// 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(); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts new file mode 100644 index 0000000000000..cb32f0203d094 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts @@ -0,0 +1,30 @@ +/// + +// 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(); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts new file mode 100644 index 0000000000000..eed8c4180434e --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts @@ -0,0 +1,36 @@ +/// + +// 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(); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts new file mode 100644 index 0000000000000..7cf0533de5fcd --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts @@ -0,0 +1,36 @@ +/// + +// 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(); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_12.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_12.ts new file mode 100644 index 0000000000000..7d948006fb363 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_12.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: () => {}|] })))); +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ 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..43b59d021d3cb --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceProperty_00.ts @@ -0,0 +1,23 @@ +/// + +// 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(); \ 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..2725a5ab9d41c --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterfaceProperty_01.ts @@ -0,0 +1,16 @@ +/// + +// 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(); \ 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..6e8ca5f317905 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_00.ts @@ -0,0 +1,26 @@ +/// + +// 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(); \ 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..6d9d5c9750767 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_01.ts @@ -0,0 +1,25 @@ +/// + +//// 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(); \ 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..b22de0e266ef3 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_02.ts @@ -0,0 +1,20 @@ +/// + +// 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(); \ 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..b198269d498b7 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_03.ts @@ -0,0 +1,10 @@ +/// + +// Should go to object literals within cast expressions when invoked on interface + +//// interface Fo/*interface_definition*/o { hello: () => void } +//// +//// var x = [|{ hello: () => {} }|]; + +goTo.marker("interface_definition"); +verify.allRangesAppearInImplementationList(); \ 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..3989c19436378 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_04.ts @@ -0,0 +1,22 @@ +/// + +// 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(); \ 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..d0f87c6d6d4eb --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_05.ts @@ -0,0 +1,14 @@ +/// + +// 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) {}|]; +//// + + +goTo.marker("interface_definition"); +verify.allRangesAppearInImplementationList(); \ 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..7ba2ebfc023eb --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_06.ts @@ -0,0 +1,16 @@ +/// + +// 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) {} }|]; + + +goTo.marker("interface_definition"); +verify.allRangesAppearInImplementationList(); \ 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..a9739c9fe1232 --- /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.implementationCountIs(0); +} \ 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..0d0ba9001b733 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_00.ts @@ -0,0 +1,9 @@ +/// + +// Should return definition of locally declared functions + +//// he/*function_call*/llo(); +//// [|function hello() {}|] + +goTo.marker("function_call"); +verify.allRangesAppearInImplementationList(); \ 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..d2ccb976e956b --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_01.ts @@ -0,0 +1,9 @@ +/// + +// Should return the defintion of locally defined variables + +//// const [|hello = function() {}|]; +//// he/*function_call*/llo(); + +goTo.marker("function_call"); +verify.allRangesAppearInImplementationList(); \ 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..47097652d811f --- /dev/null +++ b/tests/cases/fourslash/goToImplementationLocal_02.ts @@ -0,0 +1,8 @@ +/// + +//// const x = { [|hello: () => {}|] }; +//// +//// x.he/*function_call*/llo(); +//// +goTo.marker("function_call"); +verify.allRangesAppearInImplementationList(); \ 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..46ad28a3e1de2 --- /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 = {}; +//// +goTo.marker("local_var"); +verify.allRangesAppearInImplementationList(); \ 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..3452749bdcfb0 --- /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(); +//// +goTo.marker("local_var"); +verify.allRangesAppearInImplementationList(); \ 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..e58343d25053e --- /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(); +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ 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..edae50425bd4f --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_00.ts @@ -0,0 +1,12 @@ +/// + +// Should handle calls on namespaces + +//// [|namespace Foo { +//// export function hello() {} +//// }|] +//// +//// let x = Fo/*reference*/o; + +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ 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..4d5b30efa0cb2 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_01.ts @@ -0,0 +1,12 @@ +/// + +// Should handle calls on modules + +//// [|module Foo { +//// export function hello() {} +//// }|] +//// +//// let x = Fo/*reference*/o; + +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ 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..ea24336be7eab --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_02.ts @@ -0,0 +1,12 @@ +/// + +// Should handle property access expressions on namespaces + +//// namespace Foo { +//// [|export function hello() {}|] +//// } +//// +//// Foo.hell/*reference*/o(); + +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ 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..c4388df7e8a92 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_03.ts @@ -0,0 +1,12 @@ +/// + +// Should handle property access expressions on namespaces + +//// module Foo { +//// [|export function hello() {}|] +//// } +//// +//// Foo.hell/*reference*/o(); + +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_04.ts b/tests/cases/fourslash/goToImplementationNamespace_04.ts new file mode 100644 index 0000000000000..5f4267da530f1 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_04.ts @@ -0,0 +1,26 @@ +/// + +// 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") \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_05.ts b/tests/cases/fourslash/goToImplementationNamespace_05.ts new file mode 100644 index 0000000000000..3dfb1fcca49ea --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_05.ts @@ -0,0 +1,26 @@ +/// + +// 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") \ 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..38aeb47feaee0 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_06.ts @@ -0,0 +1,28 @@ +/// + +// 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) { +//// +//// } + +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); diff --git a/tests/cases/fourslash/goToImplementationNamespace_07.ts b/tests/cases/fourslash/goToImplementationNamespace_07.ts new file mode 100644 index 0000000000000..fc64275d906d3 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_07.ts @@ -0,0 +1,28 @@ +/// + +// 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) { +//// +//// } + +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); 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..abb0aad0602c4 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_01.ts @@ -0,0 +1,48 @@ +/// + +// 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();; \ 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..8db6cbce5deb4 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_02.ts @@ -0,0 +1,22 @@ +/// + +// 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(); diff --git a/tests/cases/fourslash/goToImplementationSuper_00.ts b/tests/cases/fourslash/goToImplementationSuper_00.ts new file mode 100644 index 0000000000000..b7def58ca591d --- /dev/null +++ b/tests/cases/fourslash/goToImplementationSuper_00.ts @@ -0,0 +1,16 @@ +/// + +// 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(); \ 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..11ba0719e5d24 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationSuper_01.ts @@ -0,0 +1,16 @@ +/// + +// 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(); \ 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..0e4a0ff9849a0 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationThis_00.ts @@ -0,0 +1,14 @@ +/// + +// 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(); \ 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..001ebc08b9fa9 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationThis_01.ts @@ -0,0 +1,12 @@ +/// + +// 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(); \ 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'); +}); From 457b67f67a141097c56807db165f1510d94db47f Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 23 Aug 2016 11:12:01 -0700 Subject: [PATCH 02/13] PR Feedback --- src/compiler/utilities.ts | 5 ----- src/server/session.ts | 4 ++-- src/services/services.ts | 20 ++++++++++++++++++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 35dea56cf0bf4..870df4a54af56 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1669,11 +1669,6 @@ namespace ts { return false; } - export function isFunctionDeclarationIdentifierName(node: Identifier): boolean { - return node.parent.kind === SyntaxKind.FunctionDeclaration && - (node.parent).name === node; - } - // An alias symbol is created by one of the following declarations: // import = ... // import from ... diff --git a/src/server/session.ts b/src/server/session.ts index 783451e02c6b9..2eb994519b113 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -366,9 +366,9 @@ namespace ts.server { } const compilerService = project.compilerService; - const position = compilerService.host.lineOffsetToPosition(file, line, offset); + const implementations = compilerService.languageService.getImplementationAtPosition(file, + compilerService.host.lineOffsetToPosition(file, line, offset)); - const implementations = compilerService.languageService.getImplementationAtPosition(file, position); if (!implementations) { return undefined; } diff --git a/src/services/services.ts b/src/services/services.ts index bb63f30c603bf..6f682822bba3f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -5302,6 +5302,8 @@ namespace ts { const node = getTouchingPropertyName(getValidSourceFile(fileName), position); const typeChecker = program.getTypeChecker(); + // 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 entry = getReferenceEntryForShorthandPropertyAssignment(node, typeChecker); entries.push({ @@ -5309,6 +5311,10 @@ namespace ts { fileName: entry.fileName }); } + + // For most symbols, the definition is the same as the implementation so we can just + // call "Go to Definition". This case should handle anything that is not a type + // reference to or member of an interface, class, or union/intersection type. else if (definitionIsImplementation(node, typeChecker)) { const definitions = getDefinitionAtPosition(fileName, position); forEach(definitions, (definition: DefinitionInfo) => { @@ -5318,8 +5324,12 @@ namespace ts { }); }); } + + // Interfaces, classes, and unions/intersection types separate the implementation and + // definition so "Go to Definition" is not sufficient. This case handles invocations + // on type references and members of those types. else { - // Do a search for all references and filter them down to implementations only + // Perform "Find all References" and filter them down to implementations only const result = getReferencedSymbolsForNode(node, program.getSourceFiles(), /*findInStrings*/false, /*findInComments*/false, /*implementations*/true); forEach(result, referencedSymbol => { @@ -5362,7 +5372,8 @@ namespace ts { return false; } - // Also check the right hand side to see if this is a type being accessed on a namespace/module + // Also check the right hand side to see if this is a type being accessed on a namespace/module. + // For example, SomeModule.SomeType const rightHandType = typeChecker.getTypeAtLocation(node); return rightHandType && !(rightHandType.getFlags() & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.UnionOrIntersection)); } @@ -5424,6 +5435,11 @@ namespace ts { return isIdentifierOfClass(node) || isIdentifierOfEnumDeclaration(node); } + function isFunctionDeclarationIdentifierName(node: Identifier): boolean { + return node.parent.kind === SyntaxKind.FunctionDeclaration && + (node.parent).name === node; + } + function isIdentifierOfClass(node: Identifier) { return (node.parent.kind === SyntaxKind.ClassDeclaration || node.parent.kind === SyntaxKind.ClassExpression) && (node.parent).name === node; From 051c7b0217e85228342d6c1b974735e2978b0689 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 29 Aug 2016 15:46:26 -0700 Subject: [PATCH 03/13] Refine the search set instead of filtering to implementations --- src/compiler/checker.ts | 3 +- src/compiler/types.ts | 1 - src/services/services.ts | 274 ++++++------------ .../goToImplementationInterfaceMethod_05.ts | 2 +- .../goToImplementationInterfaceMethod_06.ts | 2 +- 5 files changed, 91 insertions(+), 191 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f7ce30fe4f340..193deded4c5fa 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -103,8 +103,7 @@ namespace ts { getJsxElementAttributesType, getJsxIntrinsicTagNames, - isOptionalParameter, - isTypeAssignableTo + isOptionalParameter }; const tupleTypes = createMap(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index adfde2d060576..a61d4a360b30c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1889,7 +1889,6 @@ 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[]; diff --git a/src/services/services.ts b/src/services/services.ts index 6f682822bba3f..914ae98746a75 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -6272,10 +6272,6 @@ namespace ts { } } - if (implementations) { - return filterToImplementations(node, symbol, result, indexToSymbol); - } - return result; function getDefinition(symbol: Symbol): DefinitionInfo { @@ -6546,6 +6542,20 @@ namespace ts { const start = findInComments ? container.getFullStart() : container.getStart(); const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, start, container.getEnd()); + // 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) + let parentSymbol: Symbol = undefined; + let inheritanceCache: Map = undefined; + if (implementations && searchLocation.parent && searchLocation.parent.kind === SyntaxKind.PropertyAccessExpression && searchLocation === (searchLocation.parent).name) { + const localParentType = typeChecker.getTypeAtLocation((searchLocation.parent).expression); + if (localParentType && localParentType.symbol && localParentType.symbol.getFlags() & (SymbolFlags.Interface | SymbolFlags.Class) && localParentType.symbol.parent !== searchSymbol.parent) { + parentSymbol = localParentType.symbol; + inheritanceCache = createMap(); + } + } + if (possiblePositions.length) { // Build the set of symbols to search for, initially it has only the current symbol const searchSymbols = populateSearchSymbolSet(searchSymbol, searchLocation); @@ -6554,7 +6564,7 @@ namespace ts { cancellationToken.throwIfCancellationRequested(); const referenceLocation = getTouchingPropertyName(sourceFile, position); - if (!isValidReferencePosition(referenceLocation, searchText)) { + if (!implementations && !isValidReferencePosition(referenceLocation, searchText)) { // 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. @@ -6586,11 +6596,15 @@ namespace ts { if (referenceSymbol) { const referenceSymbolDeclaration = referenceSymbol.valueDeclaration; const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration); - const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation); + const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, parentSymbol, inheritanceCache); if (relatedSymbol) { - const referencedSymbol = getReferencedSymbol(relatedSymbol); - referencedSymbol.references.push(getReferenceEntryFromNode(referenceLocation)); + const referenceEntry = implementations ? getImplementationReferenceEntryForNode(referenceLocation) : getReferenceEntryFromNode(referenceLocation); + + if (referenceEntry) { + const referencedSymbol = getReferencedSymbol(relatedSymbol); + referencedSymbol.references.push(referenceEntry); + } } /* 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 @@ -6599,8 +6613,11 @@ namespace ts { * 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)); + const referenceEntry = implementations ? getImplementationReferenceEntryForNode(referenceSymbolDeclaration.name) : getReferenceEntryFromNode(referenceSymbolDeclaration.name); + if (referenceEntry) { + const referencedSymbol = getReferencedSymbol(shorthandValueSymbol); + referencedSymbol.references.push(referenceEntry); + } } } }); @@ -6634,141 +6651,10 @@ 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((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); - + function getImplementationReferenceEntryForNode(refNode: Node): ReferenceEntry { // 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) { + if (refNode.kind === SyntaxKind.Identifier) { // Check if we found a function/propertyAssignment/method with an implementation or initializer if (isIdentifierOfImplementation(refNode)) { return getReferenceEntryFromNode(refNode.parent); @@ -6805,25 +6691,6 @@ namespace ts { } } - 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) { @@ -6867,32 +6734,66 @@ namespace ts { node.kind === SyntaxKind.ClassExpression; } - function getClassHierarchy(symbol: Symbol) { - const classes: Symbol[] = []; - getClassHierarchyRecursive(symbol, classes, createMap()); - return classes; + /** + * 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. + * This 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 { } + * + * @param child A class or interface Symbol + * @param parent Another class or interface Symbol + * @param cachedResults A map of symbol names to booleans indicating previous results + */ + function inheritsFrom(child: Symbol, parent: Symbol, cachedResults: Map = createMap()): boolean { + const parentIsInterface = parent.getFlags() & SymbolFlags.Interface; + return searchHierarchy(child, cachedResults); - function getClassHierarchyRecursive(symbol: Symbol, result: Symbol[], previousIterationSymbolsCache: SymbolTable) { - if (hasProperty(previousIterationSymbolsCache, symbol.name)) { - return; + function searchHierarchy(symbol: Symbol, cachedResults: Map): boolean { + if (symbol === parent) { + return true; } - previousIterationSymbolsCache[symbol.name] = symbol; - - if (result.indexOf(symbol) !== -1) { - return; + else if (symbol.name in cachedResults) { + return cachedResults[symbol.name]; } - 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); + + cachedResults[symbol.name] = 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, cachedResults)) { + return true; + } + } } } + return searchTypeReference(getClassExtendsHeritageClauseElement(declaration), cachedResults); } + else if (declaration.kind === SyntaxKind.InterfaceDeclaration) { + if (parentIsInterface) { + return forEach(getInterfaceBaseTypeNodes(declaration), base => searchTypeReference(base, cachedResults)); + } + } + return false; }); + + cachedResults[symbol.name] = inherits; + return inherits; + } + + function searchTypeReference(typeReference: ExpressionWithTypeArguments, cachedResults: Map) { + if (typeReference) { + const type = typeChecker.getTypeAtLocation(typeReference); + if (type && type.symbol) { + return searchHierarchy(type.symbol, cachedResults); + } + } + return false; } } @@ -7176,7 +7077,7 @@ namespace ts { } // 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()); } }); @@ -7243,7 +7144,7 @@ namespace ts { } } - function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol { + function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, parentSymbol: Symbol, inheritanceCache: Map): Symbol { if (searchSymbols.indexOf(referenceSymbol) >= 0) { return referenceSymbol; } @@ -7252,7 +7153,7 @@ namespace ts { // 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); + return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, parentSymbol, inheritanceCache); } // If the reference location is in an object literal, try to get the contextual type for the @@ -7295,8 +7196,9 @@ namespace ts { } // 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 - if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + // 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) && (!parentSymbol || inheritsFrom(rootSymbol.parent, parentSymbol, inheritanceCache))) { const result: Symbol[] = []; getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap()); return forEach(result, s => searchSymbols.indexOf(s) >= 0 ? s : undefined); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_05.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_05.ts index 43079b20dcc34..5e8bd76a07247 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_05.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_05.ts @@ -15,7 +15,7 @@ //// } //// //// 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 +//// hello() {} //// hello2() {} //// hello3() {} //// } diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_06.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_06.ts index 175b5bd50fc14..b7f275e6c07a7 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_06.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_06.ts @@ -28,7 +28,7 @@ //// }; //// //// 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 +//// hello() {} //// someOtherFunction() {} //// } //// From 115141cb50c878f4a2d43939252c4e15bd3d85d0 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 29 Aug 2016 18:09:16 -0700 Subject: [PATCH 04/13] Also check inheritance for union and intersection types --- src/services/services.ts | 58 +++++++++++++++---- .../goToImplementationInterfaceMethod_10.ts | 29 +++++++--- .../goToImplementationInterfaceMethod_11.ts | 48 ++++----------- .../goToImplementationInterfaceMethod_12.ts | 12 ---- 4 files changed, 80 insertions(+), 67 deletions(-) delete mode 100644 tests/cases/fourslash/goToImplementationInterfaceMethod_12.ts diff --git a/src/services/services.ts b/src/services/services.ts index 914ae98746a75..511d76d30f7db 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1844,6 +1844,13 @@ namespace ts { owners: string[]; } + // Internal interface used for tracking state in find all references when checking + // the inheritance hierarchy of property access expressions + interface SymbolInheritanceState { + symbol: Symbol; + cachedInheritanceResults: Map; + } + export interface DisplayPartsSymbolWriter extends SymbolWriter { displayParts(): SymbolDisplayPart[]; } @@ -6546,13 +6553,17 @@ namespace ts { // 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) - let parentSymbol: Symbol = undefined; - let inheritanceCache: Map = undefined; - if (implementations && searchLocation.parent && searchLocation.parent.kind === SyntaxKind.PropertyAccessExpression && searchLocation === (searchLocation.parent).name) { + let parentSymbols: SymbolInheritanceState[] = undefined; + + if (implementations && isRightSideOfPropertyAccess(searchLocation)) { const localParentType = typeChecker.getTypeAtLocation((searchLocation.parent).expression); - if (localParentType && localParentType.symbol && localParentType.symbol.getFlags() & (SymbolFlags.Interface | SymbolFlags.Class) && localParentType.symbol.parent !== searchSymbol.parent) { - parentSymbol = localParentType.symbol; - inheritanceCache = createMap(); + if (localParentType) { + if (localParentType.symbol && isClassOrInterfaceReference(localParentType.symbol) && localParentType.symbol.parent !== searchSymbol.parent) { + parentSymbols = [createSymbolInheritanceState(localParentType.symbol)]; + } + else if (localParentType.getFlags() & TypeFlags.UnionOrIntersection) { + parentSymbols = map(getSymbolsForComponentTypes(localParentType), createSymbolInheritanceState); + } } } @@ -6596,7 +6607,7 @@ namespace ts { if (referenceSymbol) { const referenceSymbolDeclaration = referenceSymbol.valueDeclaration; const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration); - const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, parentSymbol, inheritanceCache); + const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, parentSymbols); if (relatedSymbol) { const referenceEntry = implementations ? getImplementationReferenceEntryForNode(referenceLocation) : getReferenceEntryFromNode(referenceLocation); @@ -6691,6 +6702,18 @@ namespace ts { } } + function getSymbolsForComponentTypes(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) { + getSymbolsForComponentTypes(componentType, result); + } + } + return result; + } + function getContainingTypeReference(node: Node): Node { if (node) { if (node.kind === SyntaxKind.TypeReference) { @@ -6786,7 +6809,7 @@ namespace ts { return inherits; } - function searchTypeReference(typeReference: ExpressionWithTypeArguments, cachedResults: Map) { + function searchTypeReference(typeReference: ExpressionWithTypeArguments, cachedResults: Map): boolean { if (typeReference) { const type = typeChecker.getTypeAtLocation(typeReference); if (type && type.symbol) { @@ -6797,6 +6820,13 @@ namespace ts { } } + function createSymbolInheritanceState(symbol: Symbol): SymbolInheritanceState { + return { + symbol, + cachedInheritanceResults: createMap() + }; + } + function getReferencesForSuperKeyword(superKeyword: Node): ReferencedSymbol[] { let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); if (!searchSpaceNode) { @@ -7144,7 +7174,7 @@ namespace ts { } } - function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, parentSymbol: Symbol, inheritanceCache: Map): Symbol { + function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, parentSymbols: SymbolInheritanceState[]): Symbol { if (searchSymbols.indexOf(referenceSymbol) >= 0) { return referenceSymbol; } @@ -7153,7 +7183,7 @@ namespace ts { // 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, parentSymbol, inheritanceCache); + return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, parentSymbols); } // If the reference location is in an object literal, try to get the contextual type for the @@ -7198,7 +7228,13 @@ namespace ts { // 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. 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) && (!parentSymbol || inheritsFrom(rootSymbol.parent, parentSymbol, inheritanceCache))) { + if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + if (parentSymbols) { + if (!forEach(parentSymbols, ({symbol, cachedInheritanceResults}) => inheritsFrom(rootSymbol.parent, symbol, cachedInheritanceResults))) { + return undefined; + } + } + const result: Symbol[] = []; getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap()); return forEach(result, s => searchSymbols.indexOf(s) >= 0 ? s : undefined); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts index eed8c4180434e..76d4e750ad953 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts @@ -1,9 +1,12 @@ /// -// Should handle intersection types +// Should handle union and intersection types -//// interface Foo { +//// interface BaseFoo { //// hello(): void; +//// } +//// +//// interface Foo extends BaseFoo { //// aloha(): void; //// } //// @@ -13,12 +16,16 @@ //// } //// //// class FooImpl implements Foo { -//// hello() {/**FooImpl*/} +//// [|hello() {/**FooImpl*/}|] //// aloha() {} //// } //// +//// class BaseFooImpl implements BaseFoo { +//// hello() {/**BaseFooImpl*/} // Should not show up +//// } +//// //// class BarImpl implements Bar { -//// hello() {/**BarImpl*/} +//// [|hello() {/**BarImpl*/}|] //// goodbye() {} //// } //// @@ -28,9 +35,15 @@ //// goodbye() {} //// } //// -//// function someFunction(x: Foo & Bar) { -//// x.he/*function_call*/llo(); +//// function someFunction(x: Foo | Bar) { +//// x.he/*function_call0*/llo(); +//// } +//// +//// function anotherFunction(x: Foo & Bar) { +//// x.he/*function_call1*/llo(); //// } -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); +for (var i = 0; i < 2; i++) { + goTo.marker("function_call" + i); + verify.allRangesAppearInImplementationList(); +} diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts index 7cf0533de5fcd..7d948006fb363 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts @@ -1,36 +1,12 @@ -/// - -// 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(); +/// + +// Should handle members of object literals in type assertion expressions + +//// interface Foo { +//// hel/*reference*/lo(): void; +//// } +//// +//// var x = { [|hello: () => {}|] }; +//// var y = (((({ [|hello: () => {}|] })))); +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_12.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_12.ts deleted file mode 100644 index 7d948006fb363..0000000000000 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_12.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -// Should handle members of object literals in type assertion expressions - -//// interface Foo { -//// hel/*reference*/lo(): void; -//// } -//// -//// var x = { [|hello: () => {}|] }; -//// var y = (((({ [|hello: () => {}|] })))); -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file From 66f30c9841bebaf8e0c62da4ed36a64ee7b35ca0 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 6 Sep 2016 17:02:23 -0700 Subject: [PATCH 05/13] PR feedback --- src/harness/fourslash.ts | 2 +- src/services/services.ts | 188 ++++++------------ .../goToImplementationInterfaceMethod_00.ts | 1 + .../goToImplementationInterfaceMethod_08.ts | 2 +- .../goToImplementationInterfaceMethod_09.ts | 4 + .../goToImplementationNamespace_00.ts | 33 ++- .../goToImplementationNamespace_01.ts | 10 +- .../goToImplementationNamespace_02.ts | 2 +- .../goToImplementationNamespace_03.ts | 30 ++- .../goToImplementationNamespace_04.ts | 40 ++-- .../goToImplementationNamespace_05.ts | 26 --- .../goToImplementationNamespace_06.ts | 28 --- .../goToImplementationNamespace_07.ts | 28 --- 13 files changed, 146 insertions(+), 248 deletions(-) delete mode 100644 tests/cases/fourslash/goToImplementationNamespace_05.ts delete mode 100644 tests/cases/fourslash/goToImplementationNamespace_06.ts delete mode 100644 tests/cases/fourslash/goToImplementationNamespace_07.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index c577d2dd4bbb8..80baca88a705f 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -3017,7 +3017,7 @@ namespace FourSlashInterface { public typeDefinitionCountIs(expectedCount: number) { this.state.verifyTypeDefinitionsCount(this.negative, expectedCount); } - + public implementationCountIs(expectedCount: number) { this.state.verifyImplementationsCount(this.negative, expectedCount); } diff --git a/src/services/services.ts b/src/services/services.ts index 79172287e6e26..013e093336e8f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -5010,38 +5010,25 @@ namespace ts { function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { synchronizeHostData(); - const entries: ImplementationLocation[] = []; const node = getTouchingPropertyName(getValidSourceFile(fileName), position); const typeChecker = program.getTypeChecker(); // 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 entry = getReferenceEntryForShorthandPropertyAssignment(node, typeChecker); - entries.push({ - textSpan: entry.textSpan, - fileName: entry.fileName - }); + return getReferenceEntryForShorthandPropertyAssignment(node, typeChecker); } - - // For most symbols, the definition is the same as the implementation so we can just - // call "Go to Definition". This case should handle anything that is not a type - // reference to or member of an interface, class, or union/intersection type. - else if (definitionIsImplementation(node, typeChecker)) { - const definitions = getDefinitionAtPosition(fileName, position); - forEach(definitions, (definition: DefinitionInfo) => { - entries.push({ - textSpan: definition.textSpan, - fileName: definition.fileName - }); - }); + else if (node.kind === SyntaxKind.SuperKeyword || isSuperPropertyOrElementAccess(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); + if (symbol.valueDeclaration) { + return [getReferenceEntryFromNode(symbol.valueDeclaration)]; + } } - - // Interfaces, classes, and unions/intersection types separate the implementation and - // definition so "Go to Definition" is not sufficient. This case handles invocations - // on type references and members of those types. else { - // Perform "Find all References" and filter them down to implementations only + const entries: ImplementationLocation[] = []; + // Perform "Find all References" and retrieve only those that are implementations const result = getReferencedSymbolsForNode(node, program.getSourceFiles(), /*findInStrings*/false, /*findInComments*/false, /*implementations*/true); forEach(result, referencedSymbol => { @@ -5054,44 +5041,8 @@ namespace ts { }); } }); + return entries; } - - 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 = (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. - // For example, SomeModule.SomeType - 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) { @@ -5099,21 +5050,15 @@ namespace ts { 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 + const result: ReferenceEntry[] = []; - return forEach(shorthandDeclarations, declaration => { - if (declaration.kind === SyntaxKind.ClassDeclaration) { - return getReferenceEntryFromNode(declaration); - } - }); + for (const declaration of shorthandSymbol.getDeclarations()) { + if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { + result.push(getReferenceEntryFromNode(declaration)); + } } + + return result; } } @@ -5121,11 +5066,14 @@ namespace ts { return toCheck.getFlags() & (SymbolFlags.Class | SymbolFlags.Interface); } - function isIdentifierOfImplementation(node: Identifier): boolean { + function isNameOfImplementation(node: Node): boolean { const parent = node.parent; - if (isIdentifierName(node) || isFunctionDeclarationIdentifierName(node)) { - // PropertyAccessExpression? + if (isDeclarationName(node)) { + if (isVariableLike(parent)) { + return !!parent.initializer; + } + switch (parent.kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: @@ -5134,31 +5082,15 @@ namespace ts { case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return !!(parent).body; - case SyntaxKind.PropertyDeclaration: - return !!(parent).initializer; case SyntaxKind.PropertyAssignment: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.EnumDeclaration: return true; } } - else if (isVariableLike(parent) && parent.name === node) { - return !!parent.initializer; - } - return isIdentifierOfClass(node) || isIdentifierOfEnumDeclaration(node); - } - - function isFunctionDeclarationIdentifierName(node: Identifier): boolean { - return node.parent.kind === SyntaxKind.FunctionDeclaration && - (node.parent).name === node; - } - - function isIdentifierOfClass(node: Identifier) { - return (node.parent.kind === SyntaxKind.ClassDeclaration || node.parent.kind === SyntaxKind.ClassExpression) && - (node.parent).name === node; - } - - function isIdentifierOfEnumDeclaration(node: Identifier) { - return node.parent.kind === SyntaxKind.EnumDeclaration && (node.parent).name === node; + return false; } function isTypeAssertionExpression(node: Node): node is TypeAssertion { @@ -5927,12 +5859,11 @@ namespace ts { 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); + // so we have to specify that we want the constructor symbol. + const symbol = typeChecker.getSymbolAtLocation(node); if (!implementations && !symbol && node.kind === SyntaxKind.StringLiteral) { return getReferencesForStringLiteral(node, sourceFiles); @@ -6325,12 +6256,7 @@ namespace ts { /*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword, parentSymbols); if (relatedSymbol) { - const referenceEntry = implementations ? getImplementationReferenceEntryForNode(referenceLocation) : getReferenceEntryFromNode(referenceLocation); - - if (referenceEntry) { - const referencedSymbol = getReferencedSymbol(relatedSymbol); - referencedSymbol.references.push(referenceEntry); - } + 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 @@ -6339,11 +6265,7 @@ namespace ts { * 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 referenceEntry = implementations ? getImplementationReferenceEntryForNode(referenceSymbolDeclaration.name) : getReferenceEntryFromNode(referenceSymbolDeclaration.name); - if (referenceEntry) { - const referencedSymbol = getReferencedSymbol(shorthandValueSymbol); - referencedSymbol.references.push(referenceEntry); - } + addReferenceToRelatedSymbol(referenceSymbolDeclaration.name, shorthandValueSymbol); } else if (searchLocation.kind === SyntaxKind.ConstructorKeyword) { findAdditionalConstructorReferences(referenceSymbol, referenceLocation); @@ -6448,25 +6370,43 @@ namespace ts { return result[index]; } + + function addReferenceToRelatedSymbol(node: Node, relatedSymbol: Symbol) { + if (implementations) { + const referenceEntries = getImplementationReferenceEntryForNode(node); + if (referenceEntries && referenceEntries.length) { + const referencedSymbol = getReferencedSymbol(relatedSymbol); + for (const referenceEntry of referenceEntries) { + referencedSymbol.references.push(referenceEntry); + } + } + } + else { + const referenceEntry = getReferenceEntryFromNode(node); + if (referenceEntry) { + getReferencedSymbol(relatedSymbol).references.push(referenceEntry); + } + } + } } - function getImplementationReferenceEntryForNode(refNode: Node): ReferenceEntry { - // 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 + function getImplementationReferenceEntryForNode(refNode: Node): ReferenceEntry[] { + // Check if we found a function/propertyAssignment/method with an implementation or initializer + if (isNameOfImplementation(refNode)) { + return [getReferenceEntryFromNode(refNode.parent)]; + } + if (refNode.kind === SyntaxKind.Identifier) { - // Check if we found a function/propertyAssignment/method with an implementation or initializer - if (isIdentifierOfImplementation(refNode)) { - return getReferenceEntryFromNode(refNode.parent); - } - else if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { // Go ahead and dereference the shorthand assignment by going to its definition - return getReferenceEntryForShorthandPropertyAssignment(refNode, typeChecker); + const referenceEntries = getReferenceEntryForShorthandPropertyAssignment(refNode, typeChecker); + return referenceEntries && referenceEntries.length ? referenceEntries : undefined; } // Check if the node is within an extends or implements clause const containingHeritageClause = getContainingClassHeritageClause(refNode); if (containingHeritageClause) { - return getReferenceEntryFromNode(containingHeritageClause.parent); + 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 @@ -6474,17 +6414,17 @@ namespace ts { if (containingTypeReference) { const parent = containingTypeReference.parent; if (isVariableLike(parent) && parent.type === containingTypeReference && parent.initializer && isImplementationExpression(parent.initializer)) { - return getReferenceEntryFromNode(parent.initializer); + return [getReferenceEntryFromNode(parent.initializer)]; } else if (isFunctionLike(parent) && parent.type === containingTypeReference && parent.body && parent.body.kind === SyntaxKind.Block) { - return forEachReturnStatement(parent.body, (returnStatement) => { + return [forEachReturnStatement(parent.body, (returnStatement) => { if (returnStatement.expression && isImplementationExpression(returnStatement.expression)) { return getReferenceEntryFromNode(returnStatement.expression); } - }); + })]; } else if (isTypeAssertionExpression(parent) && isImplementationExpression(parent.expression)) { - return getReferenceEntryFromNode(parent.expression); + return [getReferenceEntryFromNode(parent.expression)]; } } } diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts index 71e6266aa201f..ca5498c3492eb 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts @@ -7,6 +7,7 @@ //// } //// //// var bar: Foo = { [|hello: helloImpl|] }; +//// var baz: Foo = { [|"hello": helloImpl|] }; //// //// function helloImpl () {} //// diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_08.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_08.ts index 7d0be4a38a6d7..e3db4b5157d7e 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_08.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_08.ts @@ -15,7 +15,7 @@ //// } //// //// class SubBar extends Bar { -//// hello() {} +//// [|hello() {}|] //// } diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts index cb32f0203d094..922ea026888d7 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts @@ -15,6 +15,7 @@ //// //// whatever() { //// super.he/*function_call*/llo(); +//// super["hel/*element_access*/lo"](); //// } //// } //// @@ -28,3 +29,6 @@ goTo.marker("function_call"); verify.allRangesAppearInImplementationList(); + +goTo.marker("element_access"); +verify.allRangesAppearInImplementationList(); diff --git a/tests/cases/fourslash/goToImplementationNamespace_00.ts b/tests/cases/fourslash/goToImplementationNamespace_00.ts index edae50425bd4f..1f2be487e3f6b 100644 --- a/tests/cases/fourslash/goToImplementationNamespace_00.ts +++ b/tests/cases/fourslash/goToImplementationNamespace_00.ts @@ -1,12 +1,29 @@ /// -// Should handle calls on namespaces +// Should not return results on namespaces and modules -//// [|namespace Foo { -//// export function hello() {} -//// }|] -//// -//// let x = Fo/*reference*/o; +//// namespace Foo { +//// export function hello() {} +//// } +//// +//// namespace Foo.Bar { +//// export function okay() {} +//// } +//// +//// namespace Baz { +//// export function sure() {} +//// } +//// +//// namespace Baz.Bar { +//// export function alright() {} +//// } +//// +//// let w = Fo/*reference0*/o; +//// let x = Foo.B/*reference1*/ar; +//// let w = Ba/*reference2*/z; +//// let x = Baz.B/*reference3*/ar; -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +for (let i = 0; i < 4; i++) { + goTo.marker("reference" + i); + verify.implementationCountIs(0); +} \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_01.ts b/tests/cases/fourslash/goToImplementationNamespace_01.ts index 4d5b30efa0cb2..ea24336be7eab 100644 --- a/tests/cases/fourslash/goToImplementationNamespace_01.ts +++ b/tests/cases/fourslash/goToImplementationNamespace_01.ts @@ -1,12 +1,12 @@ /// -// Should handle calls on modules +// Should handle property access expressions on namespaces -//// [|module Foo { -//// export function hello() {} -//// }|] +//// namespace Foo { +//// [|export function hello() {}|] +//// } //// -//// let x = Fo/*reference*/o; +//// Foo.hell/*reference*/o(); goTo.marker("reference"); verify.allRangesAppearInImplementationList(); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_02.ts b/tests/cases/fourslash/goToImplementationNamespace_02.ts index ea24336be7eab..c4388df7e8a92 100644 --- a/tests/cases/fourslash/goToImplementationNamespace_02.ts +++ b/tests/cases/fourslash/goToImplementationNamespace_02.ts @@ -2,7 +2,7 @@ // Should handle property access expressions on namespaces -//// namespace Foo { +//// module Foo { //// [|export function hello() {}|] //// } //// diff --git a/tests/cases/fourslash/goToImplementationNamespace_03.ts b/tests/cases/fourslash/goToImplementationNamespace_03.ts index c4388df7e8a92..38aeb47feaee0 100644 --- a/tests/cases/fourslash/goToImplementationNamespace_03.ts +++ b/tests/cases/fourslash/goToImplementationNamespace_03.ts @@ -1,12 +1,28 @@ /// -// Should handle property access expressions on namespaces +// Should handle types that are members of a namespace in type references and heritage clauses -//// module Foo { -//// [|export function hello() {}|] -//// } -//// -//// Foo.hell/*reference*/o(); +//// 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) { +//// +//// } goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +verify.allRangesAppearInImplementationList(); diff --git a/tests/cases/fourslash/goToImplementationNamespace_04.ts b/tests/cases/fourslash/goToImplementationNamespace_04.ts index 5f4267da530f1..fc64275d906d3 100644 --- a/tests/cases/fourslash/goToImplementationNamespace_04.ts +++ b/tests/cases/fourslash/goToImplementationNamespace_04.ts @@ -1,26 +1,28 @@ /// -// Should handle sub-namespaces +// Should handle types that are members of a module in type references and heritage clauses -//// /*parentNamespace*/namespace Foo { -//// export function hello() {} -//// } +//// module Foo { +//// export interface Bar { +//// hello(): void; +//// } //// -//// /*parentNamespace2*/namespace Foo./*childNamespace*/Bar { -//// export function okay() {} +//// [|class BarImpl implements Bar { +//// hello() {} +//// }|] //// } //// -//// 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"); +//// [|class Baz implements Foo.Bar { +//// hello() {} +//// }|] +//// +//// var someVar1 : Foo.Bar = [|{ hello: () => {/**1*/} }|]; +//// +//// var someVar2 = [|{ hello: () => {/**2*/} }|]; +//// +//// function whatever(x: Foo.Ba/*reference*/r) { +//// +//// } -goTo.marker("childReference"); -goTo.implementation(); -verify.caretAtMarker("childNamespace") \ No newline at end of file +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); diff --git a/tests/cases/fourslash/goToImplementationNamespace_05.ts b/tests/cases/fourslash/goToImplementationNamespace_05.ts deleted file mode 100644 index 3dfb1fcca49ea..0000000000000 --- a/tests/cases/fourslash/goToImplementationNamespace_05.ts +++ /dev/null @@ -1,26 +0,0 @@ -/// - -// 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") \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_06.ts b/tests/cases/fourslash/goToImplementationNamespace_06.ts deleted file mode 100644 index 38aeb47feaee0..0000000000000 --- a/tests/cases/fourslash/goToImplementationNamespace_06.ts +++ /dev/null @@ -1,28 +0,0 @@ -/// - -// 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) { -//// -//// } - -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); diff --git a/tests/cases/fourslash/goToImplementationNamespace_07.ts b/tests/cases/fourslash/goToImplementationNamespace_07.ts deleted file mode 100644 index fc64275d906d3..0000000000000 --- a/tests/cases/fourslash/goToImplementationNamespace_07.ts +++ /dev/null @@ -1,28 +0,0 @@ -/// - -// 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) { -//// -//// } - -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); From 9c562f867dc18aa279de0c479b3e9dcca350ed85 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 6 Sep 2016 17:14:21 -0700 Subject: [PATCH 06/13] Handle multiple return statements that implement interface --- src/services/services.ts | 10 +++++++--- .../cases/fourslash/goToImplementationInterface_02.ts | 7 +++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 013e093336e8f..b287bd735cd06 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -6417,11 +6417,15 @@ namespace ts { return [getReferenceEntryFromNode(parent.initializer)]; } else if (isFunctionLike(parent) && parent.type === containingTypeReference && parent.body && parent.body.kind === SyntaxKind.Block) { - return [forEachReturnStatement(parent.body, (returnStatement) => { + let result: ReferenceEntry[]; + + forEachReturnStatement(parent.body, (returnStatement) => { if (returnStatement.expression && isImplementationExpression(returnStatement.expression)) { - return getReferenceEntryFromNode(returnStatement.expression); + (result || (result = [])).push(getReferenceEntryFromNode(returnStatement.expression)); } - })]; + }); + + return result; } else if (isTypeAssertionExpression(parent) && isImplementationExpression(parent.expression)) { return [getReferenceEntryFromNode(parent.expression)]; diff --git a/tests/cases/fourslash/goToImplementationInterface_02.ts b/tests/cases/fourslash/goToImplementationInterface_02.ts index b22de0e266ef3..af7f188ffa40a 100644 --- a/tests/cases/fourslash/goToImplementationInterface_02.ts +++ b/tests/cases/fourslash/goToImplementationInterface_02.ts @@ -4,7 +4,14 @@ //// interface Fo/*interface_definition*/o { hello: () => void } //// +//// let x: number = 9; +//// //// function createFoo(): Foo { +//// if (x === 2) { +//// return [|{ +//// hello() {} +//// }|]; +//// } //// return [|{ //// hello() {} //// }|]; From 5913a350d7c8ae85569bdd17c15aa8b2759178e8 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 6 Sep 2016 17:34:41 -0700 Subject: [PATCH 07/13] Updating method name that changed in master --- src/services/services.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index a3b36dccc4656..d1177c9741411 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -192,8 +192,8 @@ namespace ts { } } // For syntactic classifications, all trivia are classcified together, including jsdoc comments. - // For that to work, the jsdoc comments should still be the leading trivia of the first child. - // Restoring the scanner position ensures that. + // For that to work, the jsdoc comments should still be the leading trivia of the first child. + // Restoring the scanner position ensures that. pos = this.pos; forEachChild(this, processNode, processNodes); if (pos < this.end) { @@ -5027,7 +5027,7 @@ namespace ts { if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { return getReferenceEntryForShorthandPropertyAssignment(node, typeChecker); } - else if (node.kind === SyntaxKind.SuperKeyword || isSuperPropertyOrElementAccess(node.parent)) { + 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); From 1cdd1d35a378d837a579fb497c34b38721fee11e Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Fri, 9 Sep 2016 15:13:51 -0700 Subject: [PATCH 08/13] Code cleanup and a few edge cases --- src/harness/fourslash.ts | 58 ++++++- src/services/services.ts | 159 ++++++++---------- .../goToImplementationInterface_00.ts | 1 + .../goToImplementationInterface_07.ts | 29 ++++ .../fourslash/goToImplementationLocal_06.ts | 8 + .../fourslash/goToImplementationLocal_07.ts | 8 + .../fourslash/goToImplementationLocal_08.ts | 8 + .../goToImplementationNamespace_00.ts | 25 +-- .../goToImplementationNamespace_05.ts | 22 +++ .../goToImplementationNamespace_06.ts | 13 ++ 10 files changed, 217 insertions(+), 114 deletions(-) create mode 100644 tests/cases/fourslash/goToImplementationInterface_07.ts create mode 100644 tests/cases/fourslash/goToImplementationLocal_06.ts create mode 100644 tests/cases/fourslash/goToImplementationLocal_07.ts create mode 100644 tests/cases/fourslash/goToImplementationLocal_08.ts create mode 100644 tests/cases/fourslash/goToImplementationNamespace_05.ts create mode 100644 tests/cases/fourslash/goToImplementationNamespace_06.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index f45d27c59f3c0..94e67cf981c00 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; @@ -1693,45 +1697,87 @@ namespace FourSlash { assert.equal(actualDefinitionContainerName, expectedContainerName, this.messageAtLastKnownMarker("Definition Info Container Name")); } - public goToImplementation(implIndex: number) { + 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 === undefined && implementations.length > 1) { + this.raiseError(`goToImplementation failed - no index given but more than 1 implementation returned (${implementations.length})`); + } + if (implIndex >= implementations.length) { this.raiseError(`goToImplementation failed - implIndex value (${implIndex}) exceeds implementation list size (${implementations.length})`); } - const implementation = implementations[implIndex]; + const implementation = implementations[implIndex || 0]; this.openFile(implementation.fileName); this.currentCaretPosition = implementation.textSpan.start; } public verifyRangesInImplementationList() { - const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition); + const implementations: ImplementationLocationInformation[] = 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"); } + 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 at least one range in test source"); } + const unsatisfiedRanges: Range[] = []; + 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) { + impl.matched = true; rangeIsPresent = true; break; } } - assert.isTrue(rangeIsPresent, `No implementation found for range ${range.start}, ${range.end} in ${range.fileName}: ${this.rangeText(range)}`); + if (!rangeIsPresent) { + 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 (unsatisfiedRanges.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); } - assert.equal(implementations.length, ranges.length, `Different number of implementations (${implementations.length}) and ranges (${ranges.length})`); } public getMarkers(): Marker[] { @@ -2911,7 +2957,7 @@ namespace FourSlashInterface { this.state.goToTypeDefinition(definitionIndex); } - public implementation(implementationIndex = 0) { + public implementation(implementationIndex?: number) { this.state.goToImplementation(implementationIndex); } diff --git a/src/services/services.ts b/src/services/services.ts index d1177c9741411..568b92f5c0658 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -5025,7 +5025,9 @@ namespace ts { // 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) { - return getReferenceEntryForShorthandPropertyAssignment(node, typeChecker); + const result: ReferenceEntry[] = []; + getReferenceEntryForShorthandPropertyAssignment(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 @@ -5054,47 +5056,42 @@ namespace ts { } } - function getReferenceEntryForShorthandPropertyAssignment(node: Node, typeChecker: TypeChecker) { + function getReferenceEntryForShorthandPropertyAssignment(node: Node, typeChecker: TypeChecker, result: ReferenceEntry[]): void { const refSymbol = typeChecker.getSymbolAtLocation(node); const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); if (shorthandSymbol) { - const result: ReferenceEntry[] = []; - for (const declaration of shorthandSymbol.getDeclarations()) { if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { result.push(getReferenceEntryFromNode(declaration)); } } - - return result; } } - function isClassOrInterfaceReference(toCheck: Symbol) { - return toCheck.getFlags() & (SymbolFlags.Class | SymbolFlags.Interface); - } - function isNameOfImplementation(node: Node): boolean { const parent = node.parent; if (isDeclarationName(node)) { if (isVariableLike(parent)) { + if (parent.kind === SyntaxKind.VariableDeclaration) { + const parentStatement = parent.parent && parent.parent.parent; + if (parentStatement && hasModifier(parentStatement, ModifierFlags.Ambient)) { + return true; + } + } return !!parent.initializer; } + else if (isFunctionLike(parent)) { + return !!parent.body || hasModifier(parent, ModifierFlags.Ambient); + } switch (parent.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return !!(parent).body; case SyntaxKind.PropertyAssignment: case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.EnumDeclaration: + case SyntaxKind.ModuleDeclaration: return true; } } @@ -5870,9 +5867,9 @@ namespace ts { } } - // `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); + // `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 (!implementations && !symbol && node.kind === SyntaxKind.StringLiteral) { return getReferencesForStringLiteral(node, sourceFiles); @@ -5908,11 +5905,9 @@ 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, indexToSymbol); + getReferencesInNode(scope, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex); } else { const internedName = getInternedName(symbol, node, declarations); @@ -5923,7 +5918,7 @@ namespace ts { if (nameTable[internedName] !== undefined) { result = result || []; - getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex, indexToSymbol); + getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex); } } } @@ -6195,8 +6190,7 @@ namespace ts { findInStrings: boolean, findInComments: boolean, result: ReferencedSymbol[], - symbolToIndex: number[], - indexToSymbol: {[index: number]: Symbol}): void { + symbolToIndex: number[]): void { const sourceFile = container.getSourceFile(); @@ -6207,16 +6201,17 @@ namespace ts { // 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) - let parentSymbols: SymbolInheritanceState[] = undefined; + let parents: Symbol[]; + const cache: Map = createMap(); if (implementations && isRightSideOfPropertyAccess(searchLocation)) { const localParentType = typeChecker.getTypeAtLocation((searchLocation.parent).expression); if (localParentType) { - if (localParentType.symbol && isClassOrInterfaceReference(localParentType.symbol) && localParentType.symbol.parent !== searchSymbol.parent) { - parentSymbols = [createSymbolInheritanceState(localParentType.symbol)]; + if (localParentType.symbol && localParentType.symbol.getFlags() & (SymbolFlags.Class | SymbolFlags.Interface) && localParentType.symbol !== searchSymbol.parent) { + parents = [localParentType.symbol]; } else if (localParentType.getFlags() & TypeFlags.UnionOrIntersection) { - parentSymbols = map(getSymbolsForComponentTypes(localParentType), createSymbolInheritanceState); + parents = getSymbolsForComponentTypes(localParentType); } } } @@ -6229,12 +6224,12 @@ namespace ts { cancellationToken.throwIfCancellationRequested(); const referenceLocation = getTouchingPropertyName(sourceFile, position); - if (!implementations && !isValidReferencePosition(referenceLocation, searchText)) { + if (!isValidReferencePosition(referenceLocation, searchText)) { // 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 @@ -6262,7 +6257,7 @@ namespace ts { const referenceSymbolDeclaration = referenceSymbol.valueDeclaration; const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration); const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, - /*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword, parentSymbols); + /*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword, parents, cache); if (relatedSymbol) { addReferenceToRelatedSymbol(referenceLocation, relatedSymbol); @@ -6369,7 +6364,6 @@ namespace ts { if (index === undefined) { index = result.length; symbolToIndex[symbolId] = index; - indexToSymbol[index] = symbol; result.push({ definition: getDefinition(symbol), @@ -6381,41 +6375,32 @@ namespace ts { } function addReferenceToRelatedSymbol(node: Node, relatedSymbol: Symbol) { + const references = getReferencedSymbol(relatedSymbol).references; if (implementations) { - const referenceEntries = getImplementationReferenceEntryForNode(node); - if (referenceEntries && referenceEntries.length) { - const referencedSymbol = getReferencedSymbol(relatedSymbol); - for (const referenceEntry of referenceEntries) { - referencedSymbol.references.push(referenceEntry); - } - } + getImplementationReferenceEntryForNode(node, references); } else { - const referenceEntry = getReferenceEntryFromNode(node); - if (referenceEntry) { - getReferencedSymbol(relatedSymbol).references.push(referenceEntry); - } + references.push(getReferenceEntryFromNode(node)); } } } - function getImplementationReferenceEntryForNode(refNode: Node): ReferenceEntry[] { + function getImplementationReferenceEntryForNode(refNode: Node, result: ReferenceEntry[]): void { // Check if we found a function/propertyAssignment/method with an implementation or initializer if (isNameOfImplementation(refNode)) { - return [getReferenceEntryFromNode(refNode.parent)]; + result.push(getReferenceEntryFromNode(refNode.parent)); } - - if (refNode.kind === SyntaxKind.Identifier) { + else if (refNode.kind === SyntaxKind.Identifier) { if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { // Go ahead and dereference the shorthand assignment by going to its definition - const referenceEntries = getReferenceEntryForShorthandPropertyAssignment(refNode, typeChecker); - return referenceEntries && referenceEntries.length ? referenceEntries : undefined; + getReferenceEntryForShorthandPropertyAssignment(refNode, typeChecker, result); } // Check if the node is within an extends or implements clause const containingHeritageClause = getContainingClassHeritageClause(refNode); if (containingHeritageClause) { - return [getReferenceEntryFromNode(containingHeritageClause.parent)]; + result.push(getReferenceEntryFromNode(containingHeritageClause.parent)); + return; } // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface @@ -6423,21 +6408,17 @@ namespace ts { if (containingTypeReference) { const parent = containingTypeReference.parent; if (isVariableLike(parent) && parent.type === containingTypeReference && parent.initializer && isImplementationExpression(parent.initializer)) { - return [getReferenceEntryFromNode(parent.initializer)]; + result.push(getReferenceEntryFromNode(parent.initializer)); } else if (isFunctionLike(parent) && parent.type === containingTypeReference && parent.body && parent.body.kind === SyntaxKind.Block) { - let result: ReferenceEntry[]; - forEachReturnStatement(parent.body, (returnStatement) => { if (returnStatement.expression && isImplementationExpression(returnStatement.expression)) { - (result || (result = [])).push(getReferenceEntryFromNode(returnStatement.expression)); + result.push(getReferenceEntryFromNode(returnStatement.expression)); } }); - - return result; } else if (isTypeAssertionExpression(parent) && isImplementationExpression(parent.expression)) { - return [getReferenceEntryFromNode(parent.expression)]; + result.push(getReferenceEntryFromNode(parent.expression)); } } } @@ -6456,16 +6437,16 @@ namespace ts { } function getContainingTypeReference(node: Node): Node { - if (node) { - if (node.kind === SyntaxKind.TypeReference) { - return node; - } + let topLevelTypeReference: Node = undefined; - if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) { - return getContainingTypeReference(node.parent); + while (node) { + if (isTypeNode(node)) { + topLevelTypeReference = node; } + node = node.parent; } - return undefined; + + return topLevelTypeReference; } function getContainingClassHeritageClause(node: Node): HeritageClause { @@ -6495,7 +6476,8 @@ namespace ts { return node.kind === SyntaxKind.ArrowFunction || node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ObjectLiteralExpression || - node.kind === SyntaxKind.ClassExpression; + node.kind === SyntaxKind.ClassExpression || + node.kind === SyntaxKind.ArrayLiteralExpression; } /** @@ -6510,19 +6492,21 @@ namespace ts { * @param parent Another class or interface Symbol * @param cachedResults A map of symbol names to booleans indicating previous results */ - function inheritsFrom(child: Symbol, parent: Symbol, cachedResults: Map = createMap()): boolean { + function inheritsFrom(child: Symbol, parent: Symbol, cachedResults: Map): boolean { const parentIsInterface = parent.getFlags() & SymbolFlags.Interface; - return searchHierarchy(child, cachedResults); + return searchHierarchy(child); - function searchHierarchy(symbol: Symbol, cachedResults: Map): boolean { + function searchHierarchy(symbol: Symbol): boolean { if (symbol === parent) { return true; } - else if (symbol.name in cachedResults) { - return cachedResults[symbol.name]; + + const key = getSymbolId(symbol) + "," + getSymbolId(parent); + if (key in cachedResults) { + return cachedResults[key]; } - cachedResults[symbol.name] = false; + cachedResults[key] = false; const inherits = forEach(symbol.getDeclarations(), (declaration) => { if (isClassLike(declaration)) { @@ -6530,44 +6514,37 @@ namespace ts { const interfaceReferences = getClassImplementsHeritageClauseElements(declaration); if (interfaceReferences) { for (const typeReference of interfaceReferences) { - if (searchTypeReference(typeReference, cachedResults)) { + if (searchTypeReference(typeReference)) { return true; } } } } - return searchTypeReference(getClassExtendsHeritageClauseElement(declaration), cachedResults); + return searchTypeReference(getClassExtendsHeritageClauseElement(declaration)); } else if (declaration.kind === SyntaxKind.InterfaceDeclaration) { if (parentIsInterface) { - return forEach(getInterfaceBaseTypeNodes(declaration), base => searchTypeReference(base, cachedResults)); + return forEach(getInterfaceBaseTypeNodes(declaration), searchTypeReference); } } return false; }); - cachedResults[symbol.name] = inherits; + cachedResults[key] = inherits; return inherits; } - function searchTypeReference(typeReference: ExpressionWithTypeArguments, cachedResults: Map): boolean { + function searchTypeReference(typeReference: ExpressionWithTypeArguments): boolean { if (typeReference) { const type = typeChecker.getTypeAtLocation(typeReference); if (type && type.symbol) { - return searchHierarchy(type.symbol, cachedResults); + return searchHierarchy(type.symbol); } } return false; } } - function createSymbolInheritanceState(symbol: Symbol): SymbolInheritanceState { - return { - symbol, - cachedInheritanceResults: createMap() - }; - } - function getReferencesForSuperKeyword(superKeyword: Node): ReferencedSymbol[] { let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); if (!searchSpaceNode) { @@ -6922,7 +6899,7 @@ namespace ts { } } - function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean, parentSymbols: SymbolInheritanceState[]): Symbol { + function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean, parents: Symbol[], cache: Map): Symbol { if (contains(searchSymbols, referenceSymbol)) { // If we are searching for constructor uses, they must be 'new' expressions. return (!searchLocationIsConstructor || isNewExpressionTarget(referenceLocation)) && referenceSymbol; @@ -6932,7 +6909,7 @@ namespace ts { // 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, parentSymbols); + 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 @@ -6978,8 +6955,8 @@ namespace ts { // 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)) { - if (parentSymbols) { - if (!forEach(parentSymbols, ({symbol, cachedInheritanceResults}) => inheritsFrom(rootSymbol.parent, symbol, cachedInheritanceResults))) { + if (parents) { + if (!forEach(parents, parent => inheritsFrom(rootSymbol.parent, parent, cache))) { return undefined; } } diff --git a/tests/cases/fourslash/goToImplementationInterface_00.ts b/tests/cases/fourslash/goToImplementationInterface_00.ts index 6e8ca5f317905..fc9f6aace072a 100644 --- a/tests/cases/fourslash/goToImplementationInterface_00.ts +++ b/tests/cases/fourslash/goToImplementationInterface_00.ts @@ -9,6 +9,7 @@ //// interface Baz extends Foo {} //// //// var bar: Foo = [|{ hello: helloImpl /**0*/ }|]; +//// var baz: Foo[] = [|[{ hello: helloImpl /**4*/ }]|]; //// //// function helloImpl () {} //// diff --git a/tests/cases/fourslash/goToImplementationInterface_07.ts b/tests/cases/fourslash/goToImplementationInterface_07.ts new file mode 100644 index 0000000000000..c14a98f918517 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationInterface_07.ts @@ -0,0 +1,29 @@ +/// + +// 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 & 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; +//// } + +goTo.marker("interface_definition"); +verify.allRangesAppearInImplementationList(); \ 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..74ef1d778663e --- /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 +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ 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..53159a458108a --- /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(); +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ 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..53159a458108a --- /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(); +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_00.ts b/tests/cases/fourslash/goToImplementationNamespace_00.ts index 1f2be487e3f6b..5b71b7bbf9fa1 100644 --- a/tests/cases/fourslash/goToImplementationNamespace_00.ts +++ b/tests/cases/fourslash/goToImplementationNamespace_00.ts @@ -1,29 +1,20 @@ /// -// Should not return results on namespaces and modules +// Should handle namespace and module implementations -//// namespace Foo { +//// /*implementation0*/namespace Foo { //// export function hello() {} //// } //// -//// namespace Foo.Bar { -//// export function okay() {} -//// } -//// -//// namespace Baz { +//// /*implementation1*/module Bar { //// export function sure() {} //// } //// -//// namespace Baz.Bar { -//// export function alright() {} -//// } -//// -//// let w = Fo/*reference0*/o; -//// let x = Foo.B/*reference1*/ar; -//// let w = Ba/*reference2*/z; -//// let x = Baz.B/*reference3*/ar; +//// let x = Fo/*reference0*/o; +//// let y = Ba/*reference1*/r; -for (let i = 0; i < 4; i++) { +for (let i = 0; i < 2; i ++) { goTo.marker("reference" + i); - verify.implementationCountIs(0); + goTo.implementation(); + verify.caretAtMarker("implementation" + i); } \ No newline at end of file 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..68b4898c6f732 --- /dev/null +++ b/tests/cases/fourslash/goToImplementationNamespace_06.ts @@ -0,0 +1,13 @@ +/// + +// Should handle type queries + +//// [|namespace F/*declaration*/oo { +//// declare function hello(): void; +//// }|] +//// +//// +//// let x: typeof Foo = [|{ hello() {} }|]; + +goTo.marker("declaration"); +verify.allRangesAppearInImplementationList(); \ No newline at end of file From 2069e1cb0cb9f1054fceacab5a2def1e23603a9c Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Fri, 9 Sep 2016 16:24:09 -0700 Subject: [PATCH 09/13] Prevent duplicate entries from type references --- src/services/services.ts | 17 ++++++++++++++--- .../fourslash/goToImplementationInterface_07.ts | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 568b92f5c0658..80cb181a643f5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -6408,20 +6408,31 @@ namespace ts { if (containingTypeReference) { const parent = containingTypeReference.parent; if (isVariableLike(parent) && parent.type === containingTypeReference && parent.initializer && isImplementationExpression(parent.initializer)) { - result.push(getReferenceEntryFromNode(parent.initializer)); + maybeAdd(getReferenceEntryFromNode(parent.initializer)); } else if (isFunctionLike(parent) && parent.type === containingTypeReference && parent.body && parent.body.kind === SyntaxKind.Block) { forEachReturnStatement(parent.body, (returnStatement) => { if (returnStatement.expression && isImplementationExpression(returnStatement.expression)) { - result.push(getReferenceEntryFromNode(returnStatement.expression)); + maybeAdd(getReferenceEntryFromNode(returnStatement.expression)); } }); } else if (isTypeAssertionExpression(parent) && isImplementationExpression(parent.expression)) { - result.push(getReferenceEntryFromNode(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 getSymbolsForComponentTypes(type: UnionOrIntersectionType, result: Symbol[] = []): Symbol[] { diff --git a/tests/cases/fourslash/goToImplementationInterface_07.ts b/tests/cases/fourslash/goToImplementationInterface_07.ts index c14a98f918517..6a8665ec213d7 100644 --- a/tests/cases/fourslash/goToImplementationInterface_07.ts +++ b/tests/cases/fourslash/goToImplementationInterface_07.ts @@ -13,7 +13,7 @@ //// let x1: Foo = [|{ hello () { /**typeReference*/ } }|]; //// let x2: () => Foo = [|(() => { hello () { /**functionType*/} })|]; //// let x3: Foo | Bar = [|{ hello () { /**unionType*/} }|]; -//// let x4: Foo & Bar = [|{ hello () { /**intersectionType*/} }|]; +//// let x4: Foo & (Foo & Bar) = [|{ hello () { /**intersectionType*/} }|]; //// let x5: [Foo] = [|[{ hello () { /**tupleType*/} }]|]; //// let x6: (Foo) = [|{ hello () { /**parenthesizedType*/} }|]; //// let x7: (new() => Foo) = [|class { hello () { /**constructorType*/} }|]; From f91a123d2318928e696356e2b9693c2f93ebe0d5 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 12 Sep 2016 11:10:44 -0700 Subject: [PATCH 10/13] PR feedback --- src/harness/fourslash.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 94e67cf981c00..cb9d95868ae3c 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1700,7 +1700,7 @@ 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"); + this.raiseError("goToImplementation failed - expected to find at least one implementation location but got 0"); } if (implIndex === undefined && implementations.length > 1) { @@ -1719,7 +1719,7 @@ namespace FourSlash { public verifyRangesInImplementationList() { const implementations: ImplementationLocationInformation[] = 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"); + this.raiseError("verifyRangesInImplementationList failed - expected to find at least one implementation location but got 0"); } for (let i = 0; i < implementations.length; i++) { @@ -1735,22 +1735,19 @@ namespace FourSlash { const ranges = this.getRanges(); if (!ranges || !ranges.length) { - this.raiseError("verifyRangesInImplementationList failed - expected to at least one range in test source"); + this.raiseError("verifyRangesInImplementationList failed - expected to find at least one range in test source"); } const unsatisfiedRanges: Range[] = []; 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) { - impl.matched = true; - rangeIsPresent = true; - break; - } + const matchingImpl = ts.find(implementations, impl => + range.fileName === impl.fileName && range.start === impl.textSpan.start && length === impl.textSpan.length); + if (matchingImpl) { + matchingImpl.matched = true; } - if (!rangeIsPresent) { + else { unsatisfiedRanges.push(range); } } @@ -1765,7 +1762,7 @@ namespace FourSlash { } } - if (unsatisfiedRanges.length) { + if (unmatchedImplementations.length) { error += "\nUnmatched implementations:"; for (const impl of unmatchedImplementations) { const end = impl.textSpan.start + impl.textSpan.length; From 4a37fd7bcfa3a10020337dd032fef81dda625bac Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Tue, 13 Sep 2016 17:33:49 -0700 Subject: [PATCH 11/13] More PR feedback --- src/harness/fourslash.ts | 32 ++-- src/services/services.ts | 169 +++++++++--------- src/services/shims.ts | 2 +- tests/cases/fourslash/fourslash.ts | 6 +- .../goToImplementationClassMethod_00.ts | 3 +- .../goToImplementationClassMethod_01.ts | 7 +- .../fourslash/goToImplementationEnum_00.ts | 3 +- .../fourslash/goToImplementationEnum_01.ts | 3 +- .../goToImplementationInterfaceMethod_00.ts | 7 +- .../goToImplementationInterfaceMethod_01.ts | 7 +- .../goToImplementationInterfaceMethod_02.ts | 7 +- .../goToImplementationInterfaceMethod_03.ts | 3 +- .../goToImplementationInterfaceMethod_04.ts | 3 +- .../goToImplementationInterfaceMethod_05.ts | 3 +- .../goToImplementationInterfaceMethod_06.ts | 3 +- .../goToImplementationInterfaceMethod_08.ts | 3 +- .../goToImplementationInterfaceMethod_09.ts | 7 +- .../goToImplementationInterfaceMethod_10.ts | 3 +- .../goToImplementationInterfaceMethod_11.ts | 4 +- .../goToImplementationInterfaceProperty_00.ts | 3 +- .../goToImplementationInterfaceProperty_01.ts | 3 +- .../goToImplementationInterface_00.ts | 4 +- .../goToImplementationInterface_01.ts | 3 +- .../goToImplementationInterface_02.ts | 5 +- .../goToImplementationInterface_03.ts | 3 +- .../goToImplementationInterface_04.ts | 4 +- .../goToImplementationInterface_05.ts | 4 +- .../goToImplementationInterface_06.ts | 4 +- .../goToImplementationInterface_07.ts | 3 +- .../goToImplementationInterface_08.ts | 21 +++ .../fourslash/goToImplementationInvalid.ts | 2 +- .../fourslash/goToImplementationLocal_00.ts | 3 +- .../fourslash/goToImplementationLocal_01.ts | 3 +- .../fourslash/goToImplementationLocal_02.ts | 4 +- .../fourslash/goToImplementationLocal_03.ts | 4 +- .../fourslash/goToImplementationLocal_04.ts | 4 +- .../fourslash/goToImplementationLocal_05.ts | 4 +- .../fourslash/goToImplementationLocal_06.ts | 4 +- .../fourslash/goToImplementationLocal_07.ts | 4 +- .../fourslash/goToImplementationLocal_08.ts | 4 +- .../goToImplementationNamespace_01.ts | 11 +- .../goToImplementationNamespace_02.ts | 11 +- .../goToImplementationNamespace_03.ts | 3 +- .../goToImplementationNamespace_04.ts | 3 +- .../goToImplementationNamespace_06.ts | 3 +- ...mentationShorthandPropertyAssignment_01.ts | 3 +- ...mentationShorthandPropertyAssignment_02.ts | 3 +- .../fourslash/goToImplementationSuper_00.ts | 3 +- .../fourslash/goToImplementationSuper_01.ts | 3 +- .../fourslash/goToImplementationThis_00.ts | 3 +- .../fourslash/goToImplementationThis_01.ts | 3 +- 51 files changed, 192 insertions(+), 225 deletions(-) create mode 100644 tests/cases/fourslash/goToImplementationInterface_08.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index cb9d95868ae3c..92be81ba3f2e0 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1680,13 +1680,13 @@ namespace FourSlash { assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Type definitions Count")); } - public verifyImplementationsCount(negative: boolean, expectedCount: number) { + public verifyImplementationListIsEmpty(negative: boolean) { 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")); + assertFn(actualCount, 0, this.messageAtLastKnownMarker("Implementations Count")); } public verifyGoToDefinitionName(expectedName: string, expectedContainerName: string) { @@ -1697,26 +1697,22 @@ namespace FourSlash { assert.equal(actualDefinitionContainerName, expectedContainerName, this.messageAtLastKnownMarker("Definition Info Container Name")); } - public goToImplementation(implIndex?: number) { + 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 (implIndex === undefined && implementations.length > 1) { - this.raiseError(`goToImplementation failed - no index given but more than 1 implementation returned (${implementations.length})`); - } - - if (implIndex >= implementations.length) { - this.raiseError(`goToImplementation failed - implIndex value (${implIndex}) exceeds implementation list size (${implementations.length})`); + if (implementations.length > 1) { + this.raiseError(`goToImplementation failed - more than 1 implementation returned (${implementations.length})`); } - const implementation = implementations[implIndex || 0]; + const implementation = implementations[0]; this.openFile(implementation.fileName); this.currentCaretPosition = implementation.textSpan.start; } - public verifyRangesInImplementationList() { + 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"); @@ -2954,8 +2950,8 @@ namespace FourSlashInterface { this.state.goToTypeDefinition(definitionIndex); } - public implementation(implementationIndex?: number) { - this.state.goToImplementation(implementationIndex); + public implementation() { + this.state.goToImplementation(); } public position(position: number, fileIndex?: number): void; @@ -3062,8 +3058,8 @@ namespace FourSlashInterface { this.state.verifyTypeDefinitionsCount(this.negative, expectedCount); } - public implementationCountIs(expectedCount: number) { - this.state.verifyImplementationsCount(this.negative, expectedCount); + public implementationListIsEmpty() { + this.state.verifyImplementationListIsEmpty(this.negative); } public isValidBraceCompletionAtPosition(openingBrace: string) { @@ -3319,8 +3315,8 @@ namespace FourSlashInterface { this.state.verifyProjectInfo(expected); } - public allRangesAppearInImplementationList() { - this.state.verifyRangesInImplementationList(); + public allRangesAppearInImplementationList(markerName: string) { + this.state.verifyRangesInImplementationList(markerName); } } diff --git a/src/services/services.ts b/src/services/services.ts index 80cb181a643f5..53eddecee3fe3 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1054,13 +1054,6 @@ namespace ts { moduleDir: string; } - // Internal interface used for tracking state in find all references when checking - // the inheritance hierarchy of property access expressions - interface SymbolInheritanceState { - symbol: Symbol; - cachedInheritanceResults: Map; - } - export interface DisplayPartsSymbolWriter extends SymbolWriter { displayParts(): SymbolDisplayPart[]; } @@ -5026,37 +5019,25 @@ namespace ts { // the declaration of the symbol being assigned (not the symbol being assigned to). if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { const result: ReferenceEntry[] = []; - getReferenceEntryForShorthandPropertyAssignment(node, typeChecker, result); + 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); - if (symbol.valueDeclaration) { - return [getReferenceEntryFromNode(symbol.valueDeclaration)]; - } + return symbol.valueDeclaration && [getReferenceEntryFromNode(symbol.valueDeclaration)]; } else { - const entries: ImplementationLocation[] = []; // Perform "Find all References" and retrieve only those that are implementations - 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; + const referencedSymbols = getReferencedSymbolsForNode(node, program.getSourceFiles(), /*findInStrings*/false, /*findInComments*/false, /*implementations*/true); + + return flatMap(referencedSymbols, symbol => + map(symbol.references, ({ textSpan, fileName }) => ({ textSpan, fileName }))); } } - function getReferenceEntryForShorthandPropertyAssignment(node: Node, typeChecker: TypeChecker, result: ReferenceEntry[]): void { + function getReferenceEntriesForShorthandPropertyAssignment(node: Node, typeChecker: TypeChecker, result: ReferenceEntry[]): void { const refSymbol = typeChecker.getSymbolAtLocation(node); const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); @@ -5069,25 +5050,26 @@ namespace ts { } } - function isNameOfImplementation(node: Node): boolean { - const parent = node.parent; - - if (isDeclarationName(node)) { - if (isVariableLike(parent)) { - if (parent.kind === SyntaxKind.VariableDeclaration) { - const parentStatement = parent.parent && parent.parent.parent; - if (parentStatement && hasModifier(parentStatement, ModifierFlags.Ambient)) { - return true; - } - } - return !!parent.initializer; + function isImplementation(node: Node): boolean { + if (!node) { + return false; + } + else if (isVariableLike(node)) { + if (node.initializer) { + return true; } - else if (isFunctionLike(parent)) { - return !!parent.body || hasModifier(parent, ModifierFlags.Ambient); + else if (node.kind === SyntaxKind.VariableDeclaration) { + const parentStatement = getParentStatementOfVariableDeclaration(node); + if (parentStatement && hasModifier(parentStatement, ModifierFlags.Ambient)) { + return true; + } } - - switch (parent.kind) { - case SyntaxKind.PropertyAssignment: + } + else if (isFunctionLike(node)) { + return !!node.body || hasModifier(node, ModifierFlags.Ambient); + } + else { + switch (node.kind) { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.EnumDeclaration: @@ -5095,12 +5077,12 @@ namespace ts { return true; } } - return false; } - function isTypeAssertionExpression(node: Node): node is TypeAssertion { - return node.kind === SyntaxKind.TypeAssertionExpression; + function getParentStatementOfVariableDeclaration(node: VariableDeclaration): VariableStatement { + return node.parent && node.parent.kind === SyntaxKind.VariableDeclarationList && node.parent.parent + && node.parent.parent.kind === SyntaxKind.VariableStatement && node.parent.parent; } function getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] { @@ -6197,24 +6179,8 @@ namespace ts { const start = findInComments ? container.getFullStart() : container.getStart(); const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, start, container.getEnd()); - // 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) - let parents: Symbol[]; - const cache: Map = createMap(); - - if (implementations && isRightSideOfPropertyAccess(searchLocation)) { - const localParentType = typeChecker.getTypeAtLocation((searchLocation.parent).expression); - if (localParentType) { - if (localParentType.symbol && localParentType.symbol.getFlags() & (SymbolFlags.Class | SymbolFlags.Interface) && localParentType.symbol !== searchSymbol.parent) { - parents = [localParentType.symbol]; - } - else if (localParentType.getFlags() & TypeFlags.UnionOrIntersection) { - parents = getSymbolsForComponentTypes(localParentType); - } - } - } + 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 @@ -6257,7 +6223,7 @@ namespace ts { const referenceSymbolDeclaration = referenceSymbol.valueDeclaration; const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration); const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, - /*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword, parents, cache); + /*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword, parents, inheritsFromCache); if (relatedSymbol) { addReferenceToRelatedSymbol(referenceLocation, relatedSymbol); @@ -6279,6 +6245,32 @@ namespace ts { } 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)); @@ -6387,19 +6379,19 @@ namespace ts { function getImplementationReferenceEntryForNode(refNode: Node, result: ReferenceEntry[]): void { // Check if we found a function/propertyAssignment/method with an implementation or initializer - if (isNameOfImplementation(refNode)) { + 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 - getReferenceEntryForShorthandPropertyAssignment(refNode, typeChecker, result); + getReferenceEntriesForShorthandPropertyAssignment(refNode, typeChecker, result); } // Check if the node is within an extends or implements clause - const containingHeritageClause = getContainingClassHeritageClause(refNode); - if (containingHeritageClause) { - result.push(getReferenceEntryFromNode(containingHeritageClause.parent)); + const containingClass = getContainingClassIfInHeritageClause(refNode); + if (containingClass) { + result.push(getReferenceEntryFromNode(containingClass)); return; } @@ -6410,14 +6402,19 @@ namespace ts { if (isVariableLike(parent) && parent.type === containingTypeReference && parent.initializer && isImplementationExpression(parent.initializer)) { maybeAdd(getReferenceEntryFromNode(parent.initializer)); } - else if (isFunctionLike(parent) && parent.type === containingTypeReference && parent.body && parent.body.kind === SyntaxKind.Block) { - forEachReturnStatement(parent.body, (returnStatement) => { - if (returnStatement.expression && isImplementationExpression(returnStatement.expression)) { - maybeAdd(getReferenceEntryFromNode(returnStatement.expression)); - } - }); + 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 (isTypeAssertionExpression(parent) && isImplementationExpression(parent.expression)) { + else if (isAssertionExpression(parent) && isImplementationExpression(parent.expression)) { maybeAdd(getReferenceEntryFromNode(parent.expression)); } } @@ -6435,13 +6432,13 @@ namespace ts { } } - function getSymbolsForComponentTypes(type: UnionOrIntersectionType, result: Symbol[] = []): Symbol[] { + 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) { - getSymbolsForComponentTypes(componentType, result); + getSymbolsForClassAndInterfaceComponents(componentType, result); } } return result; @@ -6460,23 +6457,23 @@ namespace ts { return topLevelTypeReference; } - function getContainingClassHeritageClause(node: Node): HeritageClause { - if (node) { + 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; + return node.parent.parent; } else if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression) { - return getContainingClassHeritageClause(node.parent); + return getContainingClassIfInHeritageClause(node.parent); } } return undefined; } /** - * Returns true if this is an expression that could be used to implement an interface. + * Returns true if this is an expression that can be considered an implementation */ function isImplementationExpression(node: Expression): boolean { // Unwrap parentheses @@ -6517,6 +6514,7 @@ namespace ts { return cachedResults[key]; } + // Set the key so that we don't infinitely recurse cachedResults[key] = false; const inherits = forEach(symbol.getDeclarations(), (declaration) => { @@ -6910,7 +6908,7 @@ namespace ts { } } - function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean, parents: Symbol[], cache: Map): Symbol { + 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; @@ -6966,6 +6964,7 @@ namespace ts { // 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 => inheritsFrom(rootSymbol.parent, parent, cache))) { return undefined; diff --git a/src/services/shims.ts b/src/services/shims.ts index 8771e0146959f..deab2addc879f 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -179,7 +179,7 @@ namespace ts { /** * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[] + * { fileName: string; textSpan: { start: number; length: number}; }[] */ getImplementationAtPosition(fileName: string, position: number): string; diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index b1adba20c526f..e484a1cd782cf 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -110,7 +110,7 @@ declare namespace FourSlashInterface { bof(): void; eof(): void; type(definitionIndex?: number): void; - implementation(implementationIndex?: number): void; + implementation(): void; position(position: number, fileIndex?: number): any; position(position: number, fileName?: string): any; file(index: number, content?: string, scriptKindName?: string): any; @@ -134,7 +134,7 @@ declare namespace FourSlashInterface { quickInfoIs(expectedText?: string, expectedDocumentation?: string): void; quickInfoExists(): void; typeDefinitionCountIs(expectedCount: number): void; - implementationCountIs(expectedCount: number): void; + implementationListIsEmpty(): void; isValidBraceCompletionAtPosition(openingBrace?: string): void; } class verify extends verifyNegatable { @@ -242,7 +242,7 @@ declare namespace FourSlashInterface { getSyntacticDiagnostics(expected: string): void; getSemanticDiagnostics(expected: string): void; ProjectInfo(expected: string[]): void; - allRangesAppearInImplementationList(): 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 index 465db091d3203..6fc8d9bf7ccdb 100644 --- a/tests/cases/fourslash/goToImplementationClassMethod_00.ts +++ b/tests/cases/fourslash/goToImplementationClassMethod_00.ts @@ -8,5 +8,4 @@ //// //// new Bar().hel/*reference*/lo; -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 8b1cb3e762d6b..7f59376a6ae34 100644 --- a/tests/cases/fourslash/goToImplementationClassMethod_01.ts +++ b/tests/cases/fourslash/goToImplementationClassMethod_01.ts @@ -14,8 +14,5 @@ //// x.he/*reference*/llo(); //// } -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); - -goTo.marker("declaration"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 3a93542f80692..81e72b871a274 100644 --- a/tests/cases/fourslash/goToImplementationEnum_00.ts +++ b/tests/cases/fourslash/goToImplementationEnum_00.ts @@ -9,5 +9,4 @@ //// //// Foo.Fo/*reference*/o1; -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 2e7203f3c8f68..43273b6b03a5c 100644 --- a/tests/cases/fourslash/goToImplementationEnum_01.ts +++ b/tests/cases/fourslash/goToImplementationEnum_01.ts @@ -9,5 +9,4 @@ //// //// Fo/*reference*/o; -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index ca5498c3492eb..38afd0cfb3dd0 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts @@ -21,8 +21,5 @@ //// constructor(public f: Foo = { [|hello() {/**3*/}|] } ) {} //// } -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); - -goTo.marker("declaration"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 5f5ba64338b7b..8bd328bcb54eb 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_01.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_01.ts @@ -18,8 +18,5 @@ //// //// whatever(new Bar()); -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); - -goTo.marker("declaration"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index f5b1f1eea9318..82dceaad91c1d 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_02.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_02.ts @@ -18,8 +18,5 @@ //// a.he/*function_call*/llo(); //// } -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); - -goTo.marker("declaration"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index cc22956bb0553..8826cc1eab4f1 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_03.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_03.ts @@ -21,5 +21,4 @@ //// new Bar().hel/*function_call*/lo(); //// new Bar()["hello"](); -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 32f5ff414843b..a9b825c717f5a 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_04.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_04.ts @@ -22,5 +22,4 @@ //// x.he/*function_call*/llo() //// } -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 5e8bd76a07247..b051e16a92998 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_05.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_05.ts @@ -34,5 +34,4 @@ //// x.he/*function_call*/llo() //// } -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index b7f275e6c07a7..b340270096da9 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_06.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_06.ts @@ -45,5 +45,4 @@ //// x.he/*function_call*/llo() //// } -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index e3db4b5157d7e..d2038f99efd8b 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_08.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_08.ts @@ -19,5 +19,4 @@ //// } -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); +verify.allRangesAppearInImplementationList("function_call"); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts index 922ea026888d7..84c57ec2bce37 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_09.ts @@ -27,8 +27,5 @@ //// hello() {} //// } -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); - -goTo.marker("element_access"); -verify.allRangesAppearInImplementationList(); +verify.allRangesAppearInImplementationList("function_call"); +verify.allRangesAppearInImplementationList("element_access"); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts index 76d4e750ad953..0604d2511f28a 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts @@ -44,6 +44,5 @@ //// } for (var i = 0; i < 2; i++) { - goTo.marker("function_call" + i); - verify.allRangesAppearInImplementationList(); + verify.allRangesAppearInImplementationList("function_call" + i); } diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts index 7d948006fb363..9528aa409b496 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts @@ -8,5 +8,5 @@ //// //// var x = { [|hello: () => {}|] }; //// var y = (((({ [|hello: () => {}|] })))); -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file + +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 index 43b59d021d3cb..70e49c10b45af 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceProperty_00.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceProperty_00.ts @@ -19,5 +19,4 @@ //// constructor(public f: Foo = { [|hello: 7|] } ) {} //// } -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 2725a5ab9d41c..14b80362917dd 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceProperty_01.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceProperty_01.ts @@ -12,5 +12,4 @@ //// foo.he/*reference*/llo; //// } -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index fc9f6aace072a..8fc36b4bab547 100644 --- a/tests/cases/fourslash/goToImplementationInterface_00.ts +++ b/tests/cases/fourslash/goToImplementationInterface_00.ts @@ -22,6 +22,4 @@ //// constructor(public f: Foo = [|{ hello() {/**3*/} }|] ) {} //// } - -goTo.marker("interface_definition"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 6d9d5c9750767..62d01da6ca8c9 100644 --- a/tests/cases/fourslash/goToImplementationInterface_01.ts +++ b/tests/cases/fourslash/goToImplementationInterface_01.ts @@ -21,5 +21,4 @@ //// var y: SuperBar = new SuperBar(); //// var z: AbstractBar = new NotAbstractBar(); -goTo.marker("interface_definition"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index af7f188ffa40a..27f29465a9a2c 100644 --- a/tests/cases/fourslash/goToImplementationInterface_02.ts +++ b/tests/cases/fourslash/goToImplementationInterface_02.ts @@ -17,11 +17,12 @@ //// }|]; //// } //// +//// let createFoo2 = (): Foo => [|({hello() {}})|]; +//// //// function createFooLike() { //// return { //// hello() {} //// }; //// } -goTo.marker("interface_definition"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index b198269d498b7..1c63852dafcad 100644 --- a/tests/cases/fourslash/goToImplementationInterface_03.ts +++ b/tests/cases/fourslash/goToImplementationInterface_03.ts @@ -6,5 +6,4 @@ //// //// var x = [|{ hello: () => {} }|]; -goTo.marker("interface_definition"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 3989c19436378..febc511388d44 100644 --- a/tests/cases/fourslash/goToImplementationInterface_04.ts +++ b/tests/cases/fourslash/goToImplementationInterface_04.ts @@ -17,6 +17,4 @@ //// constructor(public f: Foo = [|function(a) {}|] ) {} //// } - -goTo.marker("interface_definition"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index d0f87c6d6d4eb..3b31b89b52852 100644 --- a/tests/cases/fourslash/goToImplementationInterface_05.ts +++ b/tests/cases/fourslash/goToImplementationInterface_05.ts @@ -9,6 +9,4 @@ //// let bar2 = [|function(a) {}|]; //// - -goTo.marker("interface_definition"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 7ba2ebfc023eb..15f521328685d 100644 --- a/tests/cases/fourslash/goToImplementationInterface_06.ts +++ b/tests/cases/fourslash/goToImplementationInterface_06.ts @@ -11,6 +11,4 @@ //// let x: Foo = [|class { constructor (a: number) {} }|]; //// let y = [|class { constructor (a: number) {} }|]; - -goTo.marker("interface_definition"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 6a8665ec213d7..6d874b3dc7630 100644 --- a/tests/cases/fourslash/goToImplementationInterface_07.ts +++ b/tests/cases/fourslash/goToImplementationInterface_07.ts @@ -25,5 +25,4 @@ //// return true; //// } -goTo.marker("interface_definition"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index a9739c9fe1232..52b2773342e1f 100644 --- a/tests/cases/fourslash/goToImplementationInvalid.ts +++ b/tests/cases/fourslash/goToImplementationInvalid.ts @@ -8,5 +8,5 @@ for(var i = 0; i < 3; i++) { goTo.marker("" + i); - verify.implementationCountIs(0); + verify.implementationListIsEmpty(); } \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationLocal_00.ts b/tests/cases/fourslash/goToImplementationLocal_00.ts index 0d0ba9001b733..b97fadfcb02e7 100644 --- a/tests/cases/fourslash/goToImplementationLocal_00.ts +++ b/tests/cases/fourslash/goToImplementationLocal_00.ts @@ -5,5 +5,4 @@ //// he/*function_call*/llo(); //// [|function hello() {}|] -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index d2ccb976e956b..0bda3418354a5 100644 --- a/tests/cases/fourslash/goToImplementationLocal_01.ts +++ b/tests/cases/fourslash/goToImplementationLocal_01.ts @@ -5,5 +5,4 @@ //// const [|hello = function() {}|]; //// he/*function_call*/llo(); -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 47097652d811f..62bb8a71a1086 100644 --- a/tests/cases/fourslash/goToImplementationLocal_02.ts +++ b/tests/cases/fourslash/goToImplementationLocal_02.ts @@ -4,5 +4,5 @@ //// //// x.he/*function_call*/llo(); //// -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file + +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 index 46ad28a3e1de2..d3f25ea29dc96 100644 --- a/tests/cases/fourslash/goToImplementationLocal_03.ts +++ b/tests/cases/fourslash/goToImplementationLocal_03.ts @@ -8,5 +8,5 @@ //// //// hello = {}; //// -goTo.marker("local_var"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file + +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 index 3452749bdcfb0..9de9dcd0a9f5f 100644 --- a/tests/cases/fourslash/goToImplementationLocal_04.ts +++ b/tests/cases/fourslash/goToImplementationLocal_04.ts @@ -6,5 +6,5 @@ //// //// hello(); //// -goTo.marker("local_var"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file + +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 index e58343d25053e..969f4c25fc737 100644 --- a/tests/cases/fourslash/goToImplementationLocal_05.ts +++ b/tests/cases/fourslash/goToImplementationLocal_05.ts @@ -8,5 +8,5 @@ //// //// var [|someVar = new Bar()|]; //// someVa/*reference*/r.hello(); -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file + +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 index 74ef1d778663e..acdbaffc36f46 100644 --- a/tests/cases/fourslash/goToImplementationLocal_06.ts +++ b/tests/cases/fourslash/goToImplementationLocal_06.ts @@ -4,5 +4,5 @@ //// declare var [|someVar: string|]; //// someVa/*reference*/r -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file + +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 index 53159a458108a..3556c25abc0b7 100644 --- a/tests/cases/fourslash/goToImplementationLocal_07.ts +++ b/tests/cases/fourslash/goToImplementationLocal_07.ts @@ -4,5 +4,5 @@ //// [|declare function someFunction(): () => void;|] //// someFun/*reference*/ction(); -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file + +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 index 53159a458108a..3556c25abc0b7 100644 --- a/tests/cases/fourslash/goToImplementationLocal_08.ts +++ b/tests/cases/fourslash/goToImplementationLocal_08.ts @@ -4,5 +4,5 @@ //// [|declare function someFunction(): () => void;|] //// someFun/*reference*/ction(); -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file + +verify.allRangesAppearInImplementationList("reference"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationNamespace_01.ts b/tests/cases/fourslash/goToImplementationNamespace_01.ts index ea24336be7eab..b2e267d537871 100644 --- a/tests/cases/fourslash/goToImplementationNamespace_01.ts +++ b/tests/cases/fourslash/goToImplementationNamespace_01.ts @@ -2,11 +2,10 @@ // Should handle property access expressions on namespaces -//// namespace Foo { -//// [|export function hello() {}|] -//// } -//// +//// namespace Foo { +//// [|export function hello() {}|] +//// } +//// //// Foo.hell/*reference*/o(); -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index c4388df7e8a92..2d2ae960a99fc 100644 --- a/tests/cases/fourslash/goToImplementationNamespace_02.ts +++ b/tests/cases/fourslash/goToImplementationNamespace_02.ts @@ -2,11 +2,10 @@ // Should handle property access expressions on namespaces -//// module Foo { -//// [|export function hello() {}|] -//// } -//// +//// module Foo { +//// [|export function hello() {}|] +//// } +//// //// Foo.hell/*reference*/o(); -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 38aeb47feaee0..751d182ec00ab 100644 --- a/tests/cases/fourslash/goToImplementationNamespace_03.ts +++ b/tests/cases/fourslash/goToImplementationNamespace_03.ts @@ -24,5 +24,4 @@ //// //// } -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); +verify.allRangesAppearInImplementationList("reference"); diff --git a/tests/cases/fourslash/goToImplementationNamespace_04.ts b/tests/cases/fourslash/goToImplementationNamespace_04.ts index fc64275d906d3..ac0b85e43fe33 100644 --- a/tests/cases/fourslash/goToImplementationNamespace_04.ts +++ b/tests/cases/fourslash/goToImplementationNamespace_04.ts @@ -24,5 +24,4 @@ //// //// } -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); +verify.allRangesAppearInImplementationList("reference"); diff --git a/tests/cases/fourslash/goToImplementationNamespace_06.ts b/tests/cases/fourslash/goToImplementationNamespace_06.ts index 68b4898c6f732..f5e52d90b1c22 100644 --- a/tests/cases/fourslash/goToImplementationNamespace_06.ts +++ b/tests/cases/fourslash/goToImplementationNamespace_06.ts @@ -9,5 +9,4 @@ //// //// let x: typeof Foo = [|{ hello() {} }|]; -goTo.marker("declaration"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +verify.allRangesAppearInImplementationList("declaration"); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_01.ts b/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_01.ts index abb0aad0602c4..fb4763619a4ba 100644 --- a/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_01.ts +++ b/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_01.ts @@ -44,5 +44,4 @@ //// //// createBarUsingClassDeclaration().Fo/*reference*/o; -goTo.marker("reference"); -verify.allRangesAppearInImplementationList();; \ No newline at end of file +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 index 8db6cbce5deb4..a27480c82728a 100644 --- a/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_02.ts +++ b/tests/cases/fourslash/goToImplementationShorthandPropertyAssignment_02.ts @@ -18,5 +18,4 @@ //// x.h/*function_call*/ello(); //// } -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); +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 index b7def58ca591d..53377bdcdb004 100644 --- a/tests/cases/fourslash/goToImplementationSuper_00.ts +++ b/tests/cases/fourslash/goToImplementationSuper_00.ts @@ -12,5 +12,4 @@ //// } //// } -goTo.marker("super_call"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 11ba0719e5d24..fffa7caf71690 100644 --- a/tests/cases/fourslash/goToImplementationSuper_01.ts +++ b/tests/cases/fourslash/goToImplementationSuper_01.ts @@ -12,5 +12,4 @@ //// } //// } -goTo.marker("super_call"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 0e4a0ff9849a0..19212c9a77ac5 100644 --- a/tests/cases/fourslash/goToImplementationThis_00.ts +++ b/tests/cases/fourslash/goToImplementationThis_00.ts @@ -10,5 +10,4 @@ //// whatever() {} //// }|] -goTo.marker("this_call"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +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 index 001ebc08b9fa9..6761c6ec6c908 100644 --- a/tests/cases/fourslash/goToImplementationThis_01.ts +++ b/tests/cases/fourslash/goToImplementationThis_01.ts @@ -8,5 +8,4 @@ //// } //// }|] -goTo.marker("this_type"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file +verify.allRangesAppearInImplementationList("this_type"); \ No newline at end of file From b6f7dd798109f94af1936b2fed5cc18205c9922f Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Wed, 14 Sep 2016 13:57:03 -0700 Subject: [PATCH 12/13] More PR feedback --- src/harness/fourslash.ts | 10 ++++++---- src/services/services.ts | 30 +++++++++++++++++++----------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 92be81ba3f2e0..cd619a90debe8 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1681,12 +1681,14 @@ namespace FourSlash { } public verifyImplementationListIsEmpty(negative: boolean) { - 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, 0, this.messageAtLastKnownMarker("Implementations Count")); + 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) { diff --git a/src/services/services.ts b/src/services/services.ts index 53eddecee3fe3..366fc783c43b0 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -5031,9 +5031,10 @@ namespace ts { else { // Perform "Find all References" and retrieve only those that are implementations const referencedSymbols = getReferencedSymbolsForNode(node, program.getSourceFiles(), /*findInStrings*/false, /*findInComments*/false, /*implementations*/true); - - return flatMap(referencedSymbols, symbol => + const result = flatMap(referencedSymbols, symbol => map(symbol.references, ({ textSpan, fileName }) => ({ textSpan, fileName }))); + + return result && result.length > 0 ? result : undefined; } } @@ -5060,9 +5061,7 @@ namespace ts { } else if (node.kind === SyntaxKind.VariableDeclaration) { const parentStatement = getParentStatementOfVariableDeclaration(node); - if (parentStatement && hasModifier(parentStatement, ModifierFlags.Ambient)) { - return true; - } + return parentStatement && hasModifier(parentStatement, ModifierFlags.Ambient); } } else if (isFunctionLike(node)) { @@ -5081,8 +5080,10 @@ namespace ts { } function getParentStatementOfVariableDeclaration(node: VariableDeclaration): VariableStatement { - return node.parent && node.parent.kind === SyntaxKind.VariableDeclarationList && node.parent.parent - && node.parent.parent.kind === SyntaxKind.VariableStatement && node.parent.parent; + if (node.parent && node.parent.parent && node.parent.parent.kind === SyntaxKind.VariableStatement) { + Debug.assert(node.parent.kind === SyntaxKind.VariableDeclarationList); + return node.parent.parent; + } } function getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] { @@ -6491,16 +6492,23 @@ namespace ts { /** * 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. - * This also takes in a cache of previous results which makes this slightly more efficient and is + * 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 names to booleans indicating previous results + * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results */ - function inheritsFrom(child: Symbol, parent: Symbol, cachedResults: Map): boolean { + function explicitlyInheritsFrom(child: Symbol, parent: Symbol, cachedResults: Map): boolean { const parentIsInterface = parent.getFlags() & SymbolFlags.Interface; return searchHierarchy(child); @@ -6966,7 +6974,7 @@ namespace ts { 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 => inheritsFrom(rootSymbol.parent, parent, cache))) { + if (!forEach(parents, parent => explicitlyInheritsFrom(rootSymbol.parent, parent, cache))) { return undefined; } } From fd936999b133ac234fbea05bb50863e66978a779 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Wed, 14 Sep 2016 18:20:47 -0700 Subject: [PATCH 13/13] Fixing formatting --- src/services/findAllReferences.ts | 16 +++++------ src/services/goToImplementation.ts | 44 +++++++++++++++--------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 2127c40131567..dcda3e40b1a67 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -359,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, @@ -444,10 +444,10 @@ 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) - */ + * 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); @@ -722,7 +722,7 @@ namespace ts.FindAllReferences { // Set the key so that we don't infinitely recurse cachedResults[key] = false; - const inherits = forEach(symbol.getDeclarations(), (declaration) => { + const inherits = forEach(symbol.getDeclarations(), declaration => { if (isClassLike(declaration)) { if (parentIsInterface) { const interfaceReferences = getClassImplementsHeritageClauseElements(declaration); diff --git a/src/services/goToImplementation.ts b/src/services/goToImplementation.ts index 48c0791eb04bc..123d29630ad81 100644 --- a/src/services/goToImplementation.ts +++ b/src/services/goToImplementation.ts @@ -1,27 +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 }))); + 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; - } + return result && result.length > 0 ? result : undefined; } + } }