From 595342615b8e9c729626668f3a1eff3c764fb77b Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Thu, 16 Mar 2017 15:53:05 -0700 Subject: [PATCH] abstract code in FindReferences from concrete way of creating result reference entry to seamlessly reuse the same code for gotoImplementation (#14637) --- src/harness/fourslash.ts | 28 +++ src/server/client.ts | 4 +- src/services/documentHighlights.ts | 3 +- src/services/findAllReferences.ts | 181 +++++++++++------- src/services/goToImplementation.ts | 12 +- src/services/types.ts | 22 ++- .../goToImplementationClassMethod_00.ts | 2 +- .../goToImplementationInterface_00.ts | 4 +- .../goToImplementationInterface_07.ts | 2 + 9 files changed, 172 insertions(+), 86 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 3507d0f7b71..8ef4d946694 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1859,17 +1859,41 @@ namespace FourSlash { const unsatisfiedRanges: Range[] = []; + const delayedErrors: string[] = []; for (const range of ranges) { const length = range.end - range.start; const matchingImpl = ts.find(implementations, impl => range.fileName === impl.fileName && range.start === impl.textSpan.start && length === impl.textSpan.length); if (matchingImpl) { + if (range.marker && range.marker.data) { + const expected = <{ displayParts?: ts.SymbolDisplayPart[], parts: string[], kind?: string }>range.marker.data; + if (expected.displayParts) { + if (!ts.arrayIsEqualTo(expected.displayParts, matchingImpl.displayParts, displayPartIsEqualTo)) { + delayedErrors.push(`Mismatched display parts: expected ${JSON.stringify(expected.displayParts)}, actual ${JSON.stringify(matchingImpl.displayParts)}`); + } + } + else if (expected.parts) { + const actualParts = matchingImpl.displayParts.map(p => p.text); + if (!ts.arrayIsEqualTo(expected.parts, actualParts)) { + delayedErrors.push(`Mismatched non-tagged display parts: expected ${JSON.stringify(expected.parts)}, actual ${JSON.stringify(actualParts)}`); + } + } + if (expected.kind !== undefined) { + if (expected.kind !== matchingImpl.kind) { + delayedErrors.push(`Mismatched kind: expected ${JSON.stringify(expected.kind)}, actual ${JSON.stringify(matchingImpl.kind)}`); + } + } + } + matchingImpl.matched = true; } else { unsatisfiedRanges.push(range); } } + if (delayedErrors.length) { + this.raiseError(delayedErrors.join("\n")); + } const unmatchedImplementations = implementations.filter(impl => !impl.matched); if (unmatchedImplementations.length || unsatisfiedRanges.length) { @@ -1894,6 +1918,10 @@ namespace FourSlash { function implementationsAreEqual(a: ImplementationLocationInformation, b: ImplementationLocationInformation) { return a.fileName === b.fileName && TestState.textSpansEqual(a.textSpan, b.textSpan); } + + function displayPartIsEqualTo(a: ts.SymbolDisplayPart, b: ts.SymbolDisplayPart): boolean { + return a.kind === b.kind && a.text === b.text; + } } public getMarkers(): Marker[] { diff --git a/src/server/client.ts b/src/server/client.ts index 6f784db1682..8e81a423837 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -382,7 +382,9 @@ namespace ts.server { const end = this.lineOffsetToPosition(fileName, entry.end); return { fileName, - textSpan: ts.createTextSpanFromBounds(start, end) + textSpan: ts.createTextSpanFromBounds(start, end), + kind: ScriptElementKind.unknown, + displayParts: [] }; }); } diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index 502dfc59825..7308ad235fc 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -17,7 +17,8 @@ namespace ts.DocumentHighlights { } function getSemanticDocumentHighlights(node: Node, typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFilesToSearch: SourceFile[]): DocumentHighlights[] { - const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFilesToSearch); + const context = new FindAllReferences.DefaultFindReferencesContext(typeChecker, cancellationToken); + const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(context, node, sourceFilesToSearch); return referencedSymbols && convertReferencedSymbols(referencedSymbols); } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 9e5eb0351cc..725b9b99506 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1,21 +1,75 @@ /* @internal */ namespace ts.FindAllReferences { + + export interface FindReferencesContext { + readonly typeChecker: TypeChecker; + readonly cancellationToken: CancellationToken; + getReferenceEntryFromNode(node: Node, isInString?: boolean): T; + getReferenceEntryForSpanInFile(fileName: string, span: TextSpan): T; + } + + export class DefaultFindReferencesContext implements FindReferencesContext { + constructor(readonly typeChecker: TypeChecker, readonly cancellationToken: CancellationToken) { + } + getReferenceEntryFromNode(node: Node, isInString?: boolean): ReferenceEntry { + const reference = getReferenceEntryFromNode(node); + if (isInString) { + reference.isInString = true; + } + return reference; + } + getReferenceEntryForSpanInFile(fileName: string, textSpan: TextSpan): ReferenceEntry { + return { textSpan, fileName, isWriteAccess: false, isDefinition: false }; + } + } + + export class FindImplementationsContext implements FindReferencesContext { + constructor(readonly typeChecker: TypeChecker, readonly cancellationToken: CancellationToken) { + } + getReferenceEntryFromNode(node: Node): ImplementationLocation { + const entry = getReferenceEntryFromNode(node); + const symbol = this.typeChecker.getSymbolAtLocation(isDeclaration(node) && node.name ? node.name : node); + if (symbol) { + const def = getDefinition(symbol, node, this.typeChecker); + entry.kind = def.kind; + entry.displayParts = def.displayParts; + } + else if (node.kind === SyntaxKind.ObjectLiteralExpression) { + entry.kind = ScriptElementKind.interfaceElement; + entry.displayParts = [punctuationPart(SyntaxKind.OpenParenToken), textPart("object literal"), punctuationPart(SyntaxKind.CloseParenToken)] + } + else if (node.kind === SyntaxKind.ClassExpression) { + entry.kind = ScriptElementKind.localClassElement; + entry.displayParts = [punctuationPart(SyntaxKind.OpenParenToken), textPart("anonymous local class"), punctuationPart(SyntaxKind.CloseParenToken)] + } + else { + entry.kind = getNodeKind(node); + entry.displayParts = []; + } + return entry; + } + getReferenceEntryForSpanInFile(fileName: string, textSpan: TextSpan): ImplementationLocation { + return { textSpan, fileName, kind: ScriptElementKind.unknown, displayParts: [] }; + } + } + export function findReferencedSymbols(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, findInStrings: boolean, findInComments: boolean, isForRename: boolean): ReferencedSymbol[] | undefined { const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); - return getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFiles, findInStrings, findInComments, isForRename); + return getReferencedSymbolsForNode(new DefaultFindReferencesContext(typeChecker, cancellationToken), node, sourceFiles, findInStrings, findInComments, isForRename); } export function convertReferences(referenceSymbols: ReferencedSymbol[]): ReferenceEntry[] { return referenceSymbols && flatMap(referenceSymbols, r => r.references); } - export function getReferencedSymbolsForNode(typeChecker: TypeChecker, cancellationToken: CancellationToken, node: Node, sourceFiles: SourceFile[], findInStrings?: boolean, findInComments?: boolean, isForRename?: boolean, implementations?: boolean): ReferencedSymbol[] | undefined { + export function getReferencedSymbolsForNode(context: FindReferencesContext, node: Node, sourceFiles: SourceFile[], findInStrings?: boolean, findInComments?: boolean, isForRename?: boolean, implementations?: boolean): ReferencedSymbolOf[] | undefined { if (!implementations) { - const special = getReferencedSymbolsSpecial(node, sourceFiles, typeChecker, cancellationToken); + const special = getReferencedSymbolsSpecial(node, sourceFiles, context); if (special) { return special; } } + const { typeChecker, cancellationToken } = context; // `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword, // so we have to specify that we want the constructor symbol. @@ -24,7 +78,7 @@ namespace ts.FindAllReferences { // Could not find a symbol e.g. unknown identifier if (!symbol) { if (!implementations && node.kind === SyntaxKind.StringLiteral) { - return getReferencesForStringLiteral(node, sourceFiles, typeChecker, cancellationToken); + return getReferencesForStringLiteral(node, sourceFiles, context); } // Can't have references to something that we have no symbol for. return undefined; @@ -49,7 +103,7 @@ namespace ts.FindAllReferences { // Compute the meaning from the location and the symbol it references const searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), declarations); - const result: ReferencedSymbol[] = []; + const result: ReferencedSymbolOf[] = []; // Maps from a symbol ID to the ReferencedSymbol entry in 'result'. const symbolToIndex: number[] = []; const inheritsFromCache: Map = createMap(); @@ -81,14 +135,14 @@ namespace ts.FindAllReferences { function getRefs(scope: ts.Node, searchName: string): void { getReferencesInNode(scope, symbol, searchName, node, searchMeaning, findInStrings, findInComments, result, - symbolToIndex, implementations, typeChecker, cancellationToken, searchSymbols, inheritsFromCache); + symbolToIndex, implementations, context, searchSymbols, inheritsFromCache); } } /** getReferencedSymbols for special node kinds. */ - function getReferencedSymbolsSpecial(node: Node, sourceFiles: SourceFile[], typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] | undefined { + function getReferencedSymbolsSpecial(node: Node, sourceFiles: SourceFile[], context: FindReferencesContext): ReferencedSymbolOf[] | undefined { if (isTypeKeyword(node.kind)) { - return getAllReferencesForKeyword(sourceFiles, node.kind, cancellationToken); + return getAllReferencesForKeyword(context, sourceFiles, node.kind); } // Labels @@ -97,20 +151,20 @@ namespace ts.FindAllReferences { 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, cancellationToken); + return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition, context); } else { // it is a label definition and not a target, search within the parent labeledStatement - return getLabelReferencesInNode(node.parent, node, cancellationToken); + return getLabelReferencesInNode(node.parent, node, context); } } if (isThis(node)) { - return getReferencesForThisKeyword(node, sourceFiles, typeChecker, cancellationToken); + return getReferencesForThisKeyword(node, sourceFiles, context); } if (node.kind === SyntaxKind.SuperKeyword) { - return getReferencesForSuperKeyword(node, typeChecker, cancellationToken); + return getReferencesForSuperKeyword(node, context); } return undefined; @@ -352,10 +406,11 @@ namespace ts.FindAllReferences { return positions; } - function getLabelReferencesInNode(container: Node, targetLabel: Identifier, cancellationToken: CancellationToken): ReferencedSymbol[] { - const references: ReferenceEntry[] = []; + function getLabelReferencesInNode(container: Node, targetLabel: Identifier, context: FindReferencesContext): ReferencedSymbolOf[] { + const references: T[] = []; const sourceFile = container.getSourceFile(); const labelName = targetLabel.text; + const { cancellationToken } = context; const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container.getStart(), container.getEnd(), cancellationToken); forEach(possiblePositions, position => { cancellationToken.throwIfCancellationRequested(); @@ -368,7 +423,7 @@ namespace ts.FindAllReferences { // Only pick labels that are either the target label, or have a target that is the target label if (node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel)) { - references.push(getReferenceEntryFromNode(node)); + references.push(context.getReferenceEntryFromNode(node)); } }); @@ -404,12 +459,13 @@ namespace ts.FindAllReferences { } } - function getAllReferencesForKeyword(sourceFiles: SourceFile[], keywordKind: ts.SyntaxKind, cancellationToken: CancellationToken): ReferencedSymbol[] { + function getAllReferencesForKeyword(context: FindReferencesContext, sourceFiles: SourceFile[], keywordKind: ts.SyntaxKind): ReferencedSymbolOf[] { + const { cancellationToken } = context; const name = tokenToString(keywordKind); - const references: ReferenceEntry[] = []; + const references: T[] = []; for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested(); - addReferencesForKeywordInFile(sourceFile, keywordKind, name, cancellationToken, references); + addReferencesForKeywordInFile(sourceFile, keywordKind, name, context, references); } if (!references.length) return undefined; @@ -427,18 +483,14 @@ namespace ts.FindAllReferences { return [{ definition, references }]; } - function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, cancellationToken: CancellationToken, references: Push): void { + function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, context: FindReferencesContext, references: Push): void { + const { cancellationToken } = context; const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); for (const position of possiblePositions) { cancellationToken.throwIfCancellationRequested(); const referenceLocation = getTouchingPropertyName(sourceFile, position); if (referenceLocation.kind === kind) { - references.push({ - textSpan: createTextSpanFromNode(referenceLocation), - fileName: sourceFile.fileName, - isWriteAccess: false, - isDefinition: false, - }); + references.push(context.getReferenceEntryForSpanInFile(sourceFile.fileName, createTextSpanFromNode(referenceLocation))); } } } @@ -447,22 +499,22 @@ namespace ts.FindAllReferences { * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). * searchLocation: a node where the search value */ - function getReferencesInNode(container: Node, + function getReferencesInNode(container: Node, searchSymbol: Symbol, searchText: string, searchLocation: Node, searchMeaning: SemanticMeaning, findInStrings: boolean, findInComments: boolean, - result: ReferencedSymbol[], + result: ReferencedSymbolOf[], symbolToIndex: number[], implementations: boolean, - typeChecker: TypeChecker, - cancellationToken: CancellationToken, + context: FindReferencesContext, searchSymbols: Symbol[], inheritsFromCache: Map): void { const sourceFile = container.getSourceFile(); + const { typeChecker, cancellationToken } = context; const start = findInComments ? container.getFullStart() : container.getStart(); const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, start, container.getEnd(), cancellationToken); @@ -486,12 +538,7 @@ namespace ts.FindAllReferences { // 'FindReferences' will just filter out these results. result.push({ definition: undefined, - references: [{ - fileName: sourceFile.fileName, - textSpan: createTextSpan(position, searchText.length), - isWriteAccess: false, - isDefinition: false - }] + references: [context.getReferenceEntryForSpanInFile(sourceFile.fileName, createTextSpan(position, searchText.length))] }); } continue; @@ -571,11 +618,11 @@ namespace ts.FindAllReferences { function addReferences(references: Node[]): void { if (references.length) { const referencedSymbol = getReferencedSymbol(searchSymbol); - addRange(referencedSymbol.references, map(references, getReferenceEntryFromNode)); + addRange(referencedSymbol.references, map(references, n => context.getReferenceEntryFromNode(n))); } } - function getReferencedSymbol(symbol: Symbol): ReferencedSymbol { + function getReferencedSymbol(symbol: Symbol): ReferencedSymbolOf { const symbolId = getSymbolId(symbol); let index = symbolToIndex[symbolId]; if (index === undefined) { @@ -594,10 +641,10 @@ namespace ts.FindAllReferences { function addReferenceToRelatedSymbol(node: Node, relatedSymbol: Symbol) { const references = getReferencedSymbol(relatedSymbol).references; if (implementations) { - getImplementationReferenceEntryForNode(node, references, typeChecker); + getImplementationReferenceEntryForNode(node, references, context); } else { - references.push(getReferenceEntryFromNode(node)); + references.push(context.getReferenceEntryFromNode(node)); } } } @@ -658,21 +705,21 @@ namespace ts.FindAllReferences { return result; } - function getImplementationReferenceEntryForNode(refNode: Node, result: ReferenceEntry[], typeChecker: TypeChecker): void { + function getImplementationReferenceEntryForNode(refNode: Node, result: T[], context: FindReferencesContext): void { // Check if we found a function/propertyAssignment/method with an implementation or initializer if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { - result.push(getReferenceEntryFromNode(refNode.parent)); + result.push(context.getReferenceEntryFromNode(refNode.parent)); } else if (refNode.kind === SyntaxKind.Identifier) { if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { // Go ahead and dereference the shorthand assignment by going to its definition - getReferenceEntriesForShorthandPropertyAssignment(refNode, typeChecker, result); + getReferenceEntriesForShorthandPropertyAssignment(refNode, context, result); } // Check if the node is within an extends or implements clause const containingClass = getContainingClassIfInHeritageClause(refNode); if (containingClass) { - result.push(getReferenceEntryFromNode(containingClass)); + result.push(context.getReferenceEntryFromNode(containingClass)); return; } @@ -681,22 +728,22 @@ namespace ts.FindAllReferences { if (containingTypeReference) { const parent = containingTypeReference.parent; if (isVariableLike(parent) && parent.type === containingTypeReference && parent.initializer && isImplementationExpression(parent.initializer)) { - maybeAdd(getReferenceEntryFromNode(parent.initializer)); + maybeAdd(context.getReferenceEntryFromNode(parent.initializer)); } else if (isFunctionLike(parent) && parent.type === containingTypeReference && parent.body) { if (parent.body.kind === SyntaxKind.Block) { forEachReturnStatement(parent.body, returnStatement => { if (returnStatement.expression && isImplementationExpression(returnStatement.expression)) { - maybeAdd(getReferenceEntryFromNode(returnStatement.expression)); + maybeAdd(context.getReferenceEntryFromNode(returnStatement.expression)); } }); } else if (isImplementationExpression(parent.body)) { - maybeAdd(getReferenceEntryFromNode(parent.body)); + maybeAdd(context.getReferenceEntryFromNode(parent.body)); } } else if (isAssertionExpression(parent) && isImplementationExpression(parent.expression)) { - maybeAdd(getReferenceEntryFromNode(parent.expression)); + maybeAdd(context.getReferenceEntryFromNode(parent.expression)); } } } @@ -706,7 +753,7 @@ namespace ts.FindAllReferences { // 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) { + function maybeAdd(a: T) { if (!forEach(result, b => a.fileName === b.fileName && a.textSpan.start === b.textSpan.start && a.textSpan.length === b.textSpan.length)) { result.push(a); } @@ -845,7 +892,7 @@ namespace ts.FindAllReferences { } } - function getReferencesForSuperKeyword(superKeyword: Node, typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { + function getReferencesForSuperKeyword(superKeyword: Node, context: FindReferencesContext): ReferencedSymbolOf[] { let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); if (!searchSpaceNode) { return undefined; @@ -868,7 +915,8 @@ namespace ts.FindAllReferences { return undefined; } - const references: ReferenceEntry[] = []; + const references: T[] = []; + const { typeChecker, cancellationToken } = context; const sourceFile = searchSpaceNode.getSourceFile(); const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode.getStart(), searchSpaceNode.getEnd(), cancellationToken); @@ -887,7 +935,7 @@ namespace ts.FindAllReferences { // Now make sure the owning class is the same as the search-space // and has the same static qualifier as the original 'super's owner. if (container && (ModifierFlags.Static & getModifierFlags(container)) === staticFlag && container.parent.symbol === searchSpaceNode.symbol) { - references.push(getReferenceEntryFromNode(node)); + references.push(context.getReferenceEntryFromNode(node)); } } @@ -895,8 +943,9 @@ namespace ts.FindAllReferences { return [{ definition, references }]; } - function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[], typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { + function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[], context: FindReferencesContext): ReferencedSymbolOf[] { let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); + const { typeChecker, cancellationToken } = context; // Whether 'this' occurs in a static context within a class. let staticFlag = ModifierFlags.Static; @@ -930,7 +979,7 @@ namespace ts.FindAllReferences { return undefined; } - const references: ReferenceEntry[] = []; + const references: T[] = []; let possiblePositions: number[]; if (searchSpaceNode.kind === SyntaxKind.SourceFile) { @@ -963,7 +1012,7 @@ namespace ts.FindAllReferences { references: references }]; - function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: ReferenceEntry[]): void { + function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: T[]): void { forEach(possiblePositions, position => { cancellationToken.throwIfCancellationRequested(); @@ -978,13 +1027,13 @@ namespace ts.FindAllReferences { case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionDeclaration: if (searchSpaceNode.symbol === container.symbol) { - result.push(getReferenceEntryFromNode(node)); + result.push(context.getReferenceEntryFromNode(node)); } break; case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: if (isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol) { - result.push(getReferenceEntryFromNode(node)); + result.push(context.getReferenceEntryFromNode(node)); } break; case SyntaxKind.ClassExpression: @@ -992,12 +1041,12 @@ namespace ts.FindAllReferences { // Make sure the container belongs to the same class // and has the appropriate static modifier from the original container. if (container.parent && searchSpaceNode.symbol === container.parent.symbol && (getModifierFlags(container) & ModifierFlags.Static) === staticFlag) { - result.push(getReferenceEntryFromNode(node)); + result.push(context.getReferenceEntryFromNode(node)); } break; case SyntaxKind.SourceFile: if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { - result.push(getReferenceEntryFromNode(node)); + result.push(context.getReferenceEntryFromNode(node)); } break; } @@ -1005,7 +1054,8 @@ namespace ts.FindAllReferences { } } - function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { + function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], context: FindReferencesContext): ReferencedSymbolOf[] { + const { typeChecker, cancellationToken } = context; const type = getStringLiteralTypeForNode(node, typeChecker); if (!type) { @@ -1013,7 +1063,7 @@ namespace ts.FindAllReferences { return undefined; } - const references: ReferenceEntry[] = []; + const references: T[] = []; for (const sourceFile of sourceFiles) { const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, type.text, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); @@ -1033,7 +1083,7 @@ namespace ts.FindAllReferences { references: references }]; - function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: ReferenceEntry[]): void { + function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: T[]): void { for (const position of possiblePositions) { cancellationToken.throwIfCancellationRequested(); @@ -1044,9 +1094,7 @@ namespace ts.FindAllReferences { const type = getStringLiteralTypeForNode(node, typeChecker); if (type === searchType) { - const reference = getReferenceEntryFromNode(node); - reference.isInString = true; - references.push(reference); + references.push(context.getReferenceEntryFromNode(node, /*isInString*/ true)); } } } @@ -1359,14 +1407,15 @@ namespace ts.FindAllReferences { } } - export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, typeChecker: TypeChecker, result: ReferenceEntry[]): void { + export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, context: FindReferencesContext, result: T[]): void { + const { typeChecker } = context; const refSymbol = typeChecker.getSymbolAtLocation(node); const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); if (shorthandSymbol) { for (const declaration of shorthandSymbol.getDeclarations()) { if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { - result.push(getReferenceEntryFromNode(declaration)); + result.push(context.getReferenceEntryFromNode(declaration)); } } } diff --git a/src/services/goToImplementation.ts b/src/services/goToImplementation.ts index 9135e1fe0f0..dd3c06174af 100644 --- a/src/services/goToImplementation.ts +++ b/src/services/goToImplementation.ts @@ -1,25 +1,25 @@ /* @internal */ namespace ts.GoToImplementation { export function getImplementationAtPosition(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): ImplementationLocation[] { + const context = new FindAllReferences.FindImplementationsContext(typeChecker, cancellationToken); // 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); + const result: ImplementationLocation[] = []; + FindAllReferences.getReferenceEntriesForShorthandPropertyAssignment(node, context, 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)]; + return symbol.valueDeclaration && [context.getReferenceEntryFromNode(symbol.valueDeclaration)]; } else { // Perform "Find all References" and retrieve only those that are implementations - const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, + const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(context, node, sourceFiles, /*findInStrings*/false, /*findInComments*/false, /*isForRename*/false, /*implementations*/true); - const result = flatMap(referencedSymbols, symbol => - map(symbol.references, ({ textSpan, fileName }) => ({ textSpan, fileName }))); + const result = flatMap(referencedSymbols, symbol => symbol.references); return result && result.length > 0 ? result : undefined; } diff --git a/src/services/types.ts b/src/services/types.ts index 3e5d31b586b..856b03330bf 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -354,22 +354,23 @@ namespace ts { caretOffset: number; } - export interface RenameLocation { + export interface DocumentSpan { textSpan: TextSpan; fileName: string; } - export interface ReferenceEntry { - textSpan: TextSpan; - fileName: string; + export interface RenameLocation extends DocumentSpan { + } + + export interface ReferenceEntry extends DocumentSpan { isWriteAccess: boolean; isDefinition: boolean; isInString?: true; } - export interface ImplementationLocation { - textSpan: TextSpan; - fileName: string; + export interface ImplementationLocation extends DocumentSpan { + kind: string; + displayParts: SymbolDisplayPart[]; } export interface DocumentHighlights { @@ -478,9 +479,12 @@ namespace ts { displayParts: SymbolDisplayPart[]; } - export interface ReferencedSymbol { + export interface ReferencedSymbolOf { definition: ReferencedSymbolDefinitionInfo; - references: ReferenceEntry[]; + references: T[]; + } + + export interface ReferencedSymbol extends ReferencedSymbolOf { } export enum SymbolDisplayPartKind { diff --git a/tests/cases/fourslash/goToImplementationClassMethod_00.ts b/tests/cases/fourslash/goToImplementationClassMethod_00.ts index 6fc8d9bf7cc..09bece0c1a2 100644 --- a/tests/cases/fourslash/goToImplementationClassMethod_00.ts +++ b/tests/cases/fourslash/goToImplementationClassMethod_00.ts @@ -3,7 +3,7 @@ // Should handle calls made on members declared in a class //// class Bar { -//// [|hello() {}|] +//// [|{|"parts": ["(","method",")"," ","Bar",".","hello","(",")",":"," ","void"], "kind": "method"|}hello() {}|] //// } //// //// new Bar().hel/*reference*/lo; diff --git a/tests/cases/fourslash/goToImplementationInterface_00.ts b/tests/cases/fourslash/goToImplementationInterface_00.ts index 8fc36b4bab5..33ff9fbd045 100644 --- a/tests/cases/fourslash/goToImplementationInterface_00.ts +++ b/tests/cases/fourslash/goToImplementationInterface_00.ts @@ -8,12 +8,12 @@ //// //// interface Baz extends Foo {} //// -//// var bar: Foo = [|{ hello: helloImpl /**0*/ }|]; +//// var bar: Foo = [|{|"parts": ["(","object literal",")"], "kind": "interface"|}{ hello: helloImpl /**0*/ }|]; //// var baz: Foo[] = [|[{ hello: helloImpl /**4*/ }]|]; //// //// function helloImpl () {} //// -//// function whatever(x: Foo = [|{ hello() {/**1*/} }|] ) { +//// function whatever(x: Foo = [|{|"parts": ["(","object literal",")"], "kind": "interface"|}{ hello() {/**1*/} }|] ) { //// } //// //// class Bar { diff --git a/tests/cases/fourslash/goToImplementationInterface_07.ts b/tests/cases/fourslash/goToImplementationInterface_07.ts index 6d874b3dc76..960072004b4 100644 --- a/tests/cases/fourslash/goToImplementationInterface_07.ts +++ b/tests/cases/fourslash/goToImplementationInterface_07.ts @@ -19,6 +19,8 @@ //// let x7: (new() => Foo) = [|class { hello () { /**constructorType*/} }|]; //// let x8: Foo[] = [|[{ hello () { /**arrayType*/} }]|]; //// let x9: { y: Foo } = [|{ y: { hello () { /**typeLiteral*/} } }|]; +//// let x10 = [|{|"parts": ["(","anonymous local class",")"], "kind": "local class"|}class implements Foo { hello() {} }|] +//// let x11 = [|{|"parts": ["(","local class",")"," ","C"], "kind": "local class"|}class C implements Foo { hello() {} }|] //// //// // Should not do anything for type predicates //// function isFoo(a: any): a is Foo {