Skip to content

Commit 3f126a5

Browse files
author
Andy Hanson
committed
Allow to find all references for constructors
1 parent c72f5e2 commit 3f126a5

File tree

3 files changed

+188
-14
lines changed

3 files changed

+188
-14
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8752,7 +8752,7 @@ namespace ts {
87528752
// The location isn't a reference to the given symbol, meaning we're being asked
87538753
// a hypothetical question of what type the symbol would have if there was a reference
87548754
// to it at the given location. Since we have no control flow information for the
8755-
// hypotherical reference (control flow information is created and attached by the
8755+
// hypothetical reference (control flow information is created and attached by the
87568756
// binder), we simply return the declared type of the symbol.
87578757
return getTypeOfSymbol(symbol);
87588758
}

src/services/services.ts

Lines changed: 144 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2788,18 +2788,41 @@ namespace ts {
27882788
return node && node.parent && node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node;
27892789
}
27902790

2791+
function climbPastPropertyAccess(node: Node) {
2792+
return isRightSideOfPropertyAccess(node) ? node.parent : node;
2793+
}
2794+
27912795
function isCallExpressionTarget(node: Node): boolean {
2792-
if (isRightSideOfPropertyAccess(node)) {
2793-
node = node.parent;
2794-
}
2795-
return node && node.parent && node.parent.kind === SyntaxKind.CallExpression && (<CallExpression>node.parent).expression === node;
2796+
return isCallOrNewExpressionTarget(node, SyntaxKind.CallExpression);
27962797
}
27972798

27982799
function isNewExpressionTarget(node: Node): boolean {
2799-
if (isRightSideOfPropertyAccess(node)) {
2800-
node = node.parent;
2800+
return isCallOrNewExpressionTarget(node, SyntaxKind.NewExpression);
2801+
}
2802+
2803+
function isCallOrNewExpressionTarget(node: Node, kind: SyntaxKind) {
2804+
const target = climbPastPropertyAccess(node);
2805+
return target && target.parent && target.parent.kind === kind && (<CallExpression>target.parent).expression === target;
2806+
}
2807+
2808+
/** Get `C` given `N` if `N` is in the position `class C extends N` */
2809+
function tryGetClassExtendingNode(node: Node): ClassLikeDeclaration | undefined {
2810+
const target = climbPastPropertyAccess(node);
2811+
2812+
const expr = target.parent;
2813+
if (expr.kind !== SyntaxKind.ExpressionWithTypeArguments) {
2814+
return;
2815+
}
2816+
2817+
const heritageClause = expr.parent;
2818+
if (heritageClause.kind !== SyntaxKind.HeritageClause) {
2819+
return;
2820+
}
2821+
2822+
const classNode = <ClassLikeDeclaration>heritageClause.parent;
2823+
if (getHeritageClause(classNode.heritageClauses, SyntaxKind.ExtendsKeyword) === heritageClause) {
2824+
return classNode;
28012825
}
2802-
return node && node.parent && node.parent.kind === SyntaxKind.NewExpression && (<CallExpression>node.parent).expression === node;
28032826
}
28042827

28052828
function isNameOfModuleDeclaration(node: Node) {
@@ -4714,7 +4737,9 @@ namespace ts {
47144737
if (functionDeclaration.kind === SyntaxKind.Constructor) {
47154738
// show (constructor) Type(...) signature
47164739
symbolKind = ScriptElementKind.constructorImplementationElement;
4717-
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
4740+
// For a constructor, `type` will be unknown.
4741+
const showSymbol = symbol.declarations[0].kind === SyntaxKind.Constructor ? symbol.parent : type.symbol;
4742+
addPrefixForAnyFunctionOrVar(showSymbol, symbolKind);
47184743
}
47194744
else {
47204745
// (function/method) symbol(..signature)
@@ -6008,6 +6033,7 @@ namespace ts {
60086033
case SyntaxKind.Identifier:
60096034
case SyntaxKind.ThisKeyword:
60106035
// case SyntaxKind.SuperKeyword: TODO:GH#9268
6036+
case SyntaxKind.ConstructorKeyword:
60116037
case SyntaxKind.StringLiteral:
60126038
return getReferencedSymbolsForNode(node, program.getSourceFiles(), findInStrings, findInComments);
60136039
}
@@ -6052,7 +6078,11 @@ namespace ts {
60526078
return getReferencesForSuperKeyword(node);
60536079
}
60546080

6055-
const symbol = typeChecker.getSymbolAtLocation(node);
6081+
const isConstructor = node.kind === SyntaxKind.ConstructorKeyword;
6082+
6083+
// `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword,
6084+
// so we have to specify that we want the constructor symbol.
6085+
let symbol = isConstructor ? node.parent.symbol : typeChecker.getSymbolAtLocation(node);
60566086

60576087
if (!symbol && node.kind === SyntaxKind.StringLiteral) {
60586088
return getReferencesForStringLiteral(<StringLiteral>node, sourceFiles);
@@ -6078,7 +6108,8 @@ namespace ts {
60786108

60796109
// Get the text to search for.
60806110
// Note: if this is an external module symbol, the name doesn't include quotes.
6081-
const declaredName = stripQuotes(getDeclaredName(typeChecker, symbol, node));
6111+
const nameSymbol = isConstructor ? symbol.parent : symbol; // A constructor is referenced using the name of its class.
6112+
const declaredName = stripQuotes(getDeclaredName(typeChecker, nameSymbol, node));
60826113

60836114
// Try to get the smallest valid scope that we can limit our search to;
60846115
// otherwise we'll need to search globally (i.e. include each file).
@@ -6092,7 +6123,7 @@ namespace ts {
60926123
getReferencesInNode(scope, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex);
60936124
}
60946125
else {
6095-
const internedName = getInternedName(symbol, node, declarations);
6126+
const internedName = isConstructor ? declaredName : getInternedName(symbol, node, declarations);
60966127
for (const sourceFile of sourceFiles) {
60976128
cancellationToken.throwIfCancellationRequested();
60986129

@@ -6430,12 +6461,98 @@ namespace ts {
64306461
const referencedSymbol = getReferencedSymbol(shorthandValueSymbol);
64316462
referencedSymbol.references.push(getReferenceEntryFromNode(referenceSymbolDeclaration.name));
64326463
}
6464+
else if (searchLocation.kind === SyntaxKind.ConstructorKeyword) {
6465+
findAdditionalConstructorReferences(referenceSymbol, referenceLocation);
6466+
}
64336467
}
64346468
});
64356469
}
64366470

64376471
return;
64386472

6473+
/** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */
6474+
function findAdditionalConstructorReferences(referenceSymbol: Symbol, referenceLocation: Node): void {
6475+
const searchClassSymbol = searchSymbol.parent;
6476+
Debug.assert(isClassLike(searchClassSymbol.valueDeclaration));
6477+
6478+
const referenceClass = referenceLocation.parent;
6479+
if (referenceSymbol === searchClassSymbol && isClassLike(referenceClass)) {
6480+
// This is the class declaration containing the constructor.
6481+
const calls = findOwnConstructorCalls(referenceSymbol, <ClassLikeDeclaration>referenceClass);
6482+
addReferences(calls);
6483+
}
6484+
else {
6485+
// If this class appears in `extends C`, then the extending class' "super" calls are references.
6486+
const classExtending = tryGetClassExtendingNode(referenceLocation);
6487+
if (classExtending && isClassLike(classExtending)) {
6488+
if (getRelatedSymbol([searchClassSymbol], referenceSymbol, referenceLocation)) {
6489+
const supers = superConstructorAccesses(classExtending);
6490+
addReferences(supers);
6491+
}
6492+
}
6493+
}
6494+
}
6495+
6496+
function addReferences(references: Node[]): void {
6497+
if (references.length) {
6498+
const referencedSymbol = getReferencedSymbol(searchSymbol);
6499+
addRange(referencedSymbol.references, map(references, getReferenceEntryFromNode));
6500+
}
6501+
}
6502+
6503+
/** `referenceLocation` is the class where the constructor was defined.
6504+
* Reference the constructor and all calls to `new this()`.
6505+
*/
6506+
function findOwnConstructorCalls(referenceSymbol: Symbol, referenceLocation: ClassLikeDeclaration): Node[] {
6507+
const result: Node[] = [];
6508+
6509+
for (const decl of referenceSymbol.members["__constructor"].declarations) {
6510+
Debug.assert(decl.kind === SyntaxKind.Constructor);
6511+
const ctrKeyword = decl.getChildAt(0);
6512+
Debug.assert(ctrKeyword.kind === SyntaxKind.ConstructorKeyword);
6513+
result.push(ctrKeyword);
6514+
}
6515+
6516+
forEachProperty(referenceSymbol.exports, member => {
6517+
const decl = member.valueDeclaration;
6518+
if (decl && decl.kind === SyntaxKind.MethodDeclaration) {
6519+
const body = (<MethodDeclaration>decl).body;
6520+
if (body) {
6521+
forEachDescendant(body, SyntaxKind.ThisKeyword, thisKeyword => {
6522+
if (isNewExpressionTarget(thisKeyword)) {
6523+
result.push(thisKeyword);
6524+
}
6525+
});
6526+
}
6527+
}
6528+
});
6529+
6530+
return result;
6531+
}
6532+
6533+
/** Find references to `super` in the constructor of an extending class. */
6534+
function superConstructorAccesses(cls: ClassLikeDeclaration): Node[] {
6535+
const symbol = cls.symbol;
6536+
const ctr = symbol.members["__constructor"];
6537+
if (!ctr) {
6538+
return [];
6539+
}
6540+
6541+
const result: Node[] = [];
6542+
for (const decl of ctr.declarations) {
6543+
Debug.assert(decl.kind === SyntaxKind.Constructor);
6544+
const body = (<ConstructorDeclaration>decl).body;
6545+
if (body) {
6546+
forEachDescendant(body, SyntaxKind.SuperKeyword, node => {
6547+
if (isCallExpressionTarget(node)) {
6548+
result.push(node);
6549+
}
6550+
});
6551+
}
6552+
};
6553+
return result;
6554+
}
6555+
64396556
function getReferencedSymbol(symbol: Symbol): ReferencedSymbol {
64406557
const symbolId = getSymbolId(symbol);
64416558
let index = symbolToIndex[symbolId];
@@ -6809,8 +6926,8 @@ namespace ts {
68096926
}
68106927
}
68116928

6812-
function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol {
6813-
if (searchSymbols.indexOf(referenceSymbol) >= 0) {
6929+
function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol | undefined {
6930+
if (contains(searchSymbols, referenceSymbol)) {
68146931
return referenceSymbol;
68156932
}
68166933

@@ -6821,6 +6938,11 @@ namespace ts {
68216938
return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation);
68226939
}
68236940

6941+
// If we are in a constructor and we didn't find the symbol yet, we should try looking for the constructor instead.
6942+
if (isNewExpressionTarget(referenceLocation) && referenceSymbol.members && referenceSymbol.members["__constructor"]) {
6943+
return getRelatedSymbol(searchSymbols, referenceSymbol.members["__constructor"], referenceLocation.parent);
6944+
}
6945+
68246946
// If the reference location is in an object literal, try to get the contextual type for the
68256947
// object literal, lookup the property symbol in the contextual type, and use this symbol to
68266948
// compare to our searchSymbol
@@ -8342,6 +8464,15 @@ namespace ts {
83428464
};
83438465
}
83448466

8467+
function forEachDescendant(node: Node, kind: SyntaxKind, action: (node: Node) => void) {
8468+
forEachChild(node, child => {
8469+
if (child.kind === kind) {
8470+
action(child);
8471+
}
8472+
forEachDescendant(child, kind, action);
8473+
});
8474+
}
8475+
83458476
/* @internal */
83468477
export function getNameTable(sourceFile: SourceFile): Map<number> {
83478478
if (!sourceFile.nameTable) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @Filename: a.ts
4+
////export class C {
5+
//// [|constructor|](n: number);
6+
//// [|constructor|]();
7+
//// [|constructor|](n?: number){}
8+
//// static f() {
9+
//// this.f();
10+
//// new [|this|]();
11+
//// }
12+
////}
13+
////new [|C|]();
14+
// Does not handle alias.
15+
////const D = C;
16+
////new D();
17+
18+
// @Filename: b.ts
19+
////import { C } from "./a";
20+
////new [|C|]();
21+
22+
// @Filename: c.ts
23+
////import { C } from "./a";
24+
////class D extends C {
25+
//// constructor() {
26+
//// [|super|]();
27+
//// super.method();
28+
//// }
29+
//// method() { super(); }
30+
////}
31+
////class E implements C {
32+
//// constructor() { super(); }
33+
////}
34+
35+
// Works with qualified names too
36+
// @Filename: d.ts
37+
////import * as a from "./a";
38+
////new a.[|C|]();
39+
////class d extends a.C { constructor() { [|super|](); }
40+
41+
const ranges = test.ranges();
42+
verify.referencesOf(ranges[0], ranges);
43+
verify.referencesOf(ranges[1], ranges);

0 commit comments

Comments
 (0)