mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 21:36:50 -05:00
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:
committed by
GitHub
parent
1639a5a2c2
commit
0f6f3b79b5
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user