Fix find all references of inherited constructor (#30514)

* recursively look for inherited constructor references

* add test

* remove outdated comment

* add tests

* move function

* improve tests

* minor refactor

* fix convert params refactoring to deal with inherited constructor calls

* simplify refactor test
This commit is contained in:
Gabriela Araujo Britto
2019-03-22 15:17:50 -07:00
committed by GitHub
parent 1639a5a2c2
commit 0f6f3b79b5
7 changed files with 163 additions and 46 deletions

View File

@@ -586,25 +586,28 @@ namespace ts.FindAllReferences.Core {
}
else {
const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, !!options.isForRename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] });
// Try to get the smallest valid scope that we can limit our search to;
// otherwise we'll need to search globally (i.e. include each file).
const scope = getSymbolScope(symbol);
if (scope) {
getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(sourceFiles, scope)));
}
else {
// Global search
for (const sourceFile of state.sourceFiles) {
state.cancellationToken.throwIfCancellationRequested();
searchForName(sourceFile, search, state);
}
}
getReferencesInContainerOrFiles(symbol, state, search);
}
return result;
}
function getReferencesInContainerOrFiles(symbol: Symbol, state: State, search: Search): void {
// Try to get the smallest valid scope that we can limit our search to;
// otherwise we'll need to search globally (i.e. include each file).
const scope = getSymbolScope(symbol);
if (scope) {
getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(state.sourceFiles, scope)));
}
else {
// Global search
for (const sourceFile of state.sourceFiles) {
state.cancellationToken.throwIfCancellationRequested();
searchForName(sourceFile, search, state);
}
}
}
function getSpecialSearchKind(node: Node): SpecialSearchKind {
switch (node.kind) {
case SyntaxKind.ConstructorKeyword:
@@ -707,7 +710,6 @@ namespace ts.FindAllReferences.Core {
constructor(
readonly sourceFiles: ReadonlyArray<SourceFile>,
readonly sourceFilesSet: ReadonlyMap<true>,
/** True if we're searching for constructor references. */
readonly specialSearchKind: SpecialSearchKind,
readonly checker: TypeChecker,
readonly cancellationToken: CancellationToken,
@@ -1293,6 +1295,7 @@ namespace ts.FindAllReferences.Core {
const classExtending = tryGetClassByExtendingIdentifier(referenceLocation);
if (classExtending) {
findSuperConstructorAccesses(classExtending, pusher());
findInheritedConstructorReferences(classExtending, state);
}
}
}
@@ -1325,35 +1328,44 @@ namespace ts.FindAllReferences.Core {
* Reference the constructor and all calls to `new this()`.
*/
function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void {
for (const decl of classSymbol.members!.get(InternalSymbolName.Constructor)!.declarations) {
const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!;
Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword);
addNode(ctrKeyword);
const constructorSymbol = getClassConstructorSymbol(classSymbol);
if (constructorSymbol) {
for (const decl of constructorSymbol.declarations) {
const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!;
Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword);
addNode(ctrKeyword);
}
}
classSymbol.exports!.forEach(member => {
const decl = member.valueDeclaration;
if (decl && decl.kind === SyntaxKind.MethodDeclaration) {
const body = (<MethodDeclaration>decl).body;
if (body) {
forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => {
if (isNewExpressionTarget(thisKeyword)) {
addNode(thisKeyword);
}
});
if (classSymbol.exports) {
classSymbol.exports.forEach(member => {
const decl = member.valueDeclaration;
if (decl && decl.kind === SyntaxKind.MethodDeclaration) {
const body = (<MethodDeclaration>decl).body;
if (body) {
forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => {
if (isNewExpressionTarget(thisKeyword)) {
addNode(thisKeyword);
}
});
}
}
}
});
});
}
}
function getClassConstructorSymbol(classSymbol: Symbol): Symbol | undefined {
return classSymbol.members && classSymbol.members.get(InternalSymbolName.Constructor);
}
/** Find references to `super` in the constructor of an extending class. */
function findSuperConstructorAccesses(cls: ClassLikeDeclaration, addNode: (node: Node) => void): void {
const ctr = cls.symbol.members!.get(InternalSymbolName.Constructor);
if (!ctr) {
function findSuperConstructorAccesses(classDeclaration: ClassLikeDeclaration, addNode: (node: Node) => void): void {
const constructor = getClassConstructorSymbol(classDeclaration.symbol);
if (!constructor) {
return;
}
for (const decl of ctr.declarations) {
for (const decl of constructor.declarations) {
Debug.assert(decl.kind === SyntaxKind.Constructor);
const body = (<ConstructorDeclaration>decl).body;
if (body) {
@@ -1366,6 +1378,17 @@ namespace ts.FindAllReferences.Core {
}
}
function hasOwnConstructor(classDeclaration: ClassLikeDeclaration): boolean {
return !!getClassConstructorSymbol(classDeclaration.symbol);
}
function findInheritedConstructorReferences(classDeclaration: ClassLikeDeclaration, state: State): void {
if (hasOwnConstructor(classDeclaration)) return;
const classSymbol = classDeclaration.symbol;
const search = state.createSearch(/*location*/ undefined, classSymbol, /*comingFrom*/ undefined);
getReferencesInContainerOrFiles(classSymbol, state, search);
}
function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void {
// Check if we found a function/propertyAssignment/method with an implementation or initializer
if (isDeclarationName(refNode) && isImplementation(refNode.parent)) {

View File

@@ -99,7 +99,19 @@ namespace ts.refactor.convertParamsToDestructuredObject {
groupedReferences.valid = false;
continue;
}
if (contains(functionSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer)) {
/* We compare symbols because in some cases find all references wil return a reference that may or may not be to the refactored function.
Example from the refactorConvertParamsToDestructuredObject_methodCallUnion.ts test:
class A { foo(a: number, b: number) { return a + b; } }
class B { foo(c: number, d: number) { return c + d; } }
declare const ab: A | B;
ab.foo(1, 2);
Find all references will return `ab.foo(1, 2)` as a reference to A's `foo` but we could be calling B's `foo`.
When looking for constructor calls, however, the symbol on the constructor call reference is going to be the corresponding class symbol.
So we need to add a special case for this because when calling a constructor of a class through one of its subclasses,
the symbols are going to be different.
*/
if (contains(functionSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer) || isNewExpressionTarget(entry.node)) {
const decl = entryToDeclaration(entry);
if (decl) {
groupedReferences.declarations.push(decl);