Skip to content

Commit 8038eb9

Browse files
author
Andy
authored
Merge pull request #10540 from Microsoft/constructor_references
Allow to find all references for constructors
2 parents 6ddcdcd + ab75365 commit 8038eb9

File tree

6 files changed

+186
-18
lines changed

6 files changed

+186
-18
lines changed

src/compiler/checker.ts

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

src/compiler/core.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1535,5 +1535,4 @@ namespace ts {
15351535
? ((fileName) => fileName)
15361536
: ((fileName) => fileName.toLowerCase());
15371537
}
1538-
15391538
}

src/compiler/utilities.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2671,10 +2671,17 @@ namespace ts {
26712671
return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment;
26722672
}
26732673

2674-
export function isExpressionWithTypeArgumentsInClassExtendsClause(node: Node): boolean {
2675-
return node.kind === SyntaxKind.ExpressionWithTypeArguments &&
2674+
/** Get `C` given `N` if `N` is in the position `class C extends N` where `N` is an ExpressionWithTypeArguments. */
2675+
export function tryGetClassExtendingExpressionWithTypeArguments(node: Node): ClassLikeDeclaration | undefined {
2676+
if (node.kind === SyntaxKind.ExpressionWithTypeArguments &&
26762677
(<HeritageClause>node.parent).token === SyntaxKind.ExtendsKeyword &&
2677-
isClassLike(node.parent.parent);
2678+
isClassLike(node.parent.parent)) {
2679+
return node.parent.parent;
2680+
}
2681+
}
2682+
2683+
export function isExpressionWithTypeArgumentsInClassExtendsClause(node: Node): boolean {
2684+
return tryGetClassExtendingExpressionWithTypeArguments(node) !== undefined;
26782685
}
26792686

26802687
export function isEntityNameExpression(node: Expression): node is EntityNameExpression {

src/services/services.ts

Lines changed: 121 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2796,18 +2796,26 @@ namespace ts {
27962796
return isRightSideOfPropertyAccess(node) ? node.parent : node;
27972797
}
27982798

2799-
function climbPastManyPropertyAccesses(node: Node): Node {
2800-
return isRightSideOfPropertyAccess(node) ? climbPastManyPropertyAccesses(node.parent) : node;
2799+
/** Get `C` given `N` if `N` is in the position `class C extends N` or `class C extends foo.N` where `N` is an identifier. */
2800+
function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined {
2801+
return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent);
28012802
}
28022803

28032804
function isCallExpressionTarget(node: Node): boolean {
2804-
node = climbPastPropertyAccess(node);
2805-
return node && node.parent && node.parent.kind === SyntaxKind.CallExpression && (<CallExpression>node.parent).expression === node;
2805+
return isCallOrNewExpressionTarget(node, SyntaxKind.CallExpression);
28062806
}
28072807

28082808
function isNewExpressionTarget(node: Node): boolean {
2809-
node = climbPastPropertyAccess(node);
2810-
return node && node.parent && node.parent.kind === SyntaxKind.NewExpression && (<CallExpression>node.parent).expression === node;
2809+
return isCallOrNewExpressionTarget(node, SyntaxKind.NewExpression);
2810+
}
2811+
2812+
function isCallOrNewExpressionTarget(node: Node, kind: SyntaxKind) {
2813+
const target = climbPastPropertyAccess(node);
2814+
return target && target.parent && target.parent.kind === kind && (<CallExpression>target.parent).expression === target;
2815+
}
2816+
2817+
function climbPastManyPropertyAccesses(node: Node): Node {
2818+
return isRightSideOfPropertyAccess(node) ? climbPastManyPropertyAccesses(node.parent) : node;
28112819
}
28122820

28132821
/** Returns a CallLikeExpression where `node` is the target being invoked. */
@@ -4625,7 +4633,7 @@ namespace ts {
46254633
const symbolFlags = symbol.flags;
46264634
let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(symbol, symbolFlags, location);
46274635
let hasAddedSymbolInfo: boolean;
4628-
const isThisExpression: boolean = location.kind === SyntaxKind.ThisKeyword && isExpression(location);
4636+
const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location);
46294637
let type: Type;
46304638

46314639
// Class at constructor site need to be shown as constructor apart from property,method, vars
@@ -6045,6 +6053,7 @@ namespace ts {
60456053
case SyntaxKind.Identifier:
60466054
case SyntaxKind.ThisKeyword:
60476055
// case SyntaxKind.SuperKeyword: TODO:GH#9268
6056+
case SyntaxKind.ConstructorKeyword:
60486057
case SyntaxKind.StringLiteral:
60496058
return getReferencedSymbolsForNode(node, program.getSourceFiles(), findInStrings, findInComments);
60506059
}
@@ -6089,6 +6098,8 @@ namespace ts {
60896098
return getReferencesForSuperKeyword(node);
60906099
}
60916100

6101+
// `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword,
6102+
// so we have to specify that we want the constructor symbol.
60926103
const symbol = typeChecker.getSymbolAtLocation(node);
60936104

60946105
if (!symbol && node.kind === SyntaxKind.StringLiteral) {
@@ -6163,7 +6174,7 @@ namespace ts {
61636174
};
61646175
}
61656176

6166-
function getAliasSymbolForPropertyNameSymbol(symbol: Symbol, location: Node): Symbol {
6177+
function getAliasSymbolForPropertyNameSymbol(symbol: Symbol, location: Node): Symbol | undefined {
61676178
if (symbol.flags & SymbolFlags.Alias) {
61686179
// Default import get alias
61696180
const defaultImport = getDeclarationOfKind(symbol, SyntaxKind.ImportClause);
@@ -6189,6 +6200,10 @@ namespace ts {
61896200
return undefined;
61906201
}
61916202

6203+
function followAliasIfNecessary(symbol: Symbol, location: Node): Symbol {
6204+
return getAliasSymbolForPropertyNameSymbol(symbol, location) || symbol;
6205+
}
6206+
61926207
function getPropertySymbolOfDestructuringAssignment(location: Node) {
61936208
return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) &&
61946209
typeChecker.getPropertySymbolOfDestructuringAssignment(<Identifier>location);
@@ -6453,7 +6468,8 @@ namespace ts {
64536468
if (referenceSymbol) {
64546469
const referenceSymbolDeclaration = referenceSymbol.valueDeclaration;
64556470
const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration);
6456-
const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation);
6471+
const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation,
6472+
/*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword);
64576473

64586474
if (relatedSymbol) {
64596475
const referencedSymbol = getReferencedSymbol(relatedSymbol);
@@ -6469,12 +6485,94 @@ namespace ts {
64696485
const referencedSymbol = getReferencedSymbol(shorthandValueSymbol);
64706486
referencedSymbol.references.push(getReferenceEntryFromNode(referenceSymbolDeclaration.name));
64716487
}
6488+
else if (searchLocation.kind === SyntaxKind.ConstructorKeyword) {
6489+
findAdditionalConstructorReferences(referenceSymbol, referenceLocation);
6490+
}
64726491
}
64736492
});
64746493
}
64756494

64766495
return;
64776496

6497+
/** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */
6498+
function findAdditionalConstructorReferences(referenceSymbol: Symbol, referenceLocation: Node): void {
6499+
Debug.assert(isClassLike(searchSymbol.valueDeclaration));
6500+
6501+
const referenceClass = referenceLocation.parent;
6502+
if (referenceSymbol === searchSymbol && isClassLike(referenceClass)) {
6503+
Debug.assert(referenceClass.name === referenceLocation);
6504+
// This is the class declaration containing the constructor.
6505+
addReferences(findOwnConstructorCalls(searchSymbol));
6506+
}
6507+
else {
6508+
// If this class appears in `extends C`, then the extending class' "super" calls are references.
6509+
const classExtending = tryGetClassByExtendingIdentifier(referenceLocation);
6510+
if (classExtending && isClassLike(classExtending) && followAliasIfNecessary(referenceSymbol, referenceLocation) === searchSymbol) {
6511+
addReferences(superConstructorAccesses(classExtending));
6512+
}
6513+
}
6514+
}
6515+
6516+
function addReferences(references: Node[]): void {
6517+
if (references.length) {
6518+
const referencedSymbol = getReferencedSymbol(searchSymbol);
6519+
addRange(referencedSymbol.references, map(references, getReferenceEntryFromNode));
6520+
}
6521+
}
6522+
6523+
/** `classSymbol` is the class where the constructor was defined.
6524+
* Reference the constructor and all calls to `new this()`.
6525+
*/
6526+
function findOwnConstructorCalls(classSymbol: Symbol): Node[] {
6527+
const result: Node[] = [];
6528+
6529+
for (const decl of classSymbol.members["__constructor"].declarations) {
6530+
Debug.assert(decl.kind === SyntaxKind.Constructor);
6531+
const ctrKeyword = decl.getChildAt(0);
6532+
Debug.assert(ctrKeyword.kind === SyntaxKind.ConstructorKeyword);
6533+
result.push(ctrKeyword);
6534+
}
6535+
6536+
forEachProperty(classSymbol.exports, member => {
6537+
const decl = member.valueDeclaration;
6538+
if (decl && decl.kind === SyntaxKind.MethodDeclaration) {
6539+
const body = (<MethodDeclaration>decl).body;
6540+
if (body) {
6541+
forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => {
6542+
if (isNewExpressionTarget(thisKeyword)) {
6543+
result.push(thisKeyword);
6544+
}
6545+
});
6546+
}
6547+
}
6548+
});
6549+
6550+
return result;
6551+
}
6552+
6553+
/** Find references to `super` in the constructor of an extending class. */
6554+
function superConstructorAccesses(cls: ClassLikeDeclaration): Node[] {
6555+
const symbol = cls.symbol;
6556+
const ctr = symbol.members["__constructor"];
6557+
if (!ctr) {
6558+
return [];
6559+
}
6560+
6561+
const result: Node[] = [];
6562+
for (const decl of ctr.declarations) {
6563+
Debug.assert(decl.kind === SyntaxKind.Constructor);
6564+
const body = (<ConstructorDeclaration>decl).body;
6565+
if (body) {
6566+
forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => {
6567+
if (isCallExpressionTarget(node)) {
6568+
result.push(node);
6569+
}
6570+
});
6571+
}
6572+
};
6573+
return result;
6574+
}
6575+
64786576
function getReferencedSymbol(symbol: Symbol): ReferencedSymbol {
64796577
const symbolId = getSymbolId(symbol);
64806578
let index = symbolToIndex[symbolId];
@@ -6855,16 +6953,17 @@ namespace ts {
68556953
}
68566954
}
68576955

6858-
function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol {
6859-
if (searchSymbols.indexOf(referenceSymbol) >= 0) {
6860-
return referenceSymbol;
6956+
function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean): Symbol | undefined {
6957+
if (contains(searchSymbols, referenceSymbol)) {
6958+
// If we are searching for constructor uses, they must be 'new' expressions.
6959+
return (!searchLocationIsConstructor || isNewExpressionTarget(referenceLocation)) && referenceSymbol;
68616960
}
68626961

68636962
// If the reference symbol is an alias, check if what it is aliasing is one of the search
68646963
// symbols but by looking up for related symbol of this alias so it can handle multiple level of indirectness.
68656964
const aliasSymbol = getAliasSymbolForPropertyNameSymbol(referenceSymbol, referenceLocation);
68666965
if (aliasSymbol) {
6867-
return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation);
6966+
return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, searchLocationIsConstructor);
68686967
}
68696968

68706969
// If the reference location is in an object literal, try to get the contextual type for the
@@ -8388,6 +8487,15 @@ namespace ts {
83888487
};
83898488
}
83908489

8490+
function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void) {
8491+
forEachChild(node, child => {
8492+
if (child.kind === kind) {
8493+
action(child);
8494+
}
8495+
forEachDescendantOfKind(child, kind, action);
8496+
});
8497+
}
8498+
83918499
/* @internal */
83928500
export function getNameTable(sourceFile: SourceFile): Map<number> {
83938501
if (!sourceFile.nameTable) {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
// Does not find 'super()' calls for a class that merely implements 'C',
32+
// since those must be calling a different constructor.
33+
////class E implements C {
34+
//// constructor() { super(); }
35+
////}
36+
37+
// Works with qualified names too
38+
// @Filename: d.ts
39+
////import * as a from "./a";
40+
////new a.[|C|]();
41+
////class d extends a.C { constructor() { [|super|](); }
42+
43+
const ranges = test.ranges();
44+
for (const ctr of ranges.slice(0, 3)) {
45+
verify.referencesOf(ctr, ranges);
46+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////class C {
4+
//// [|constructor|](n: number);
5+
//// [|constructor|](){}
6+
////}
7+
8+
verify.rangesReferenceEachOther();

0 commit comments

Comments
 (0)