Skip to content

Commit 1ccabe4

Browse files
author
Andy Hanson
committed
All to find all references for constructors
1 parent ddb5a00 commit 1ccabe4

3 files changed

Lines changed: 178 additions & 16 deletions

File tree

src/compiler/checker.ts

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

src/services/services.ts

Lines changed: 137 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,8 @@ namespace ts {
300300
}
301301
}
302302
// For syntactic classifications, all trivia are classcified together, including jsdoc comments.
303-
// For that to work, the jsdoc comments should still be the leading trivia of the first child.
304-
// Restoring the scanner position ensures that.
303+
// For that to work, the jsdoc comments should still be the leading trivia of the first child.
304+
// Restoring the scanner position ensures that.
305305
pos = this.pos;
306306
forEachChild(this, processNode, processNodes);
307307
if (pos < this.end) {
@@ -2789,18 +2789,41 @@ namespace ts {
27892789
return node && node.parent && node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node;
27902790
}
27912791

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

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

28062829
function isNameOfModuleDeclaration(node: Node) {
@@ -4715,7 +4738,9 @@ namespace ts {
47154738
if (functionDeclaration.kind === SyntaxKind.Constructor) {
47164739
// show (constructor) Type(...) signature
47174740
symbolKind = ScriptElementKind.constructorImplementationElement;
4718-
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
4741+
// For a constructor, `type` will be unknown.
4742+
const showSymbol = symbol.declarations[0].kind === SyntaxKind.Constructor ? symbol.parent : type.symbol;
4743+
addPrefixForAnyFunctionOrVar(showSymbol, symbolKind);
47194744
}
47204745
else {
47214746
// (function/method) symbol(..signature)
@@ -6009,6 +6034,7 @@ namespace ts {
60096034
case SyntaxKind.Identifier:
60106035
case SyntaxKind.ThisKeyword:
60116036
// case SyntaxKind.SuperKeyword: TODO:GH#9268
6037+
case SyntaxKind.ConstructorKeyword:
60126038
case SyntaxKind.StringLiteral:
60136039
return getReferencedSymbolsForNode(node, program.getSourceFiles(), findInStrings, findInComments);
60146040
}
@@ -6053,7 +6079,11 @@ namespace ts {
60536079
return getReferencesForSuperKeyword(node);
60546080
}
60556081

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

60586088
if (!symbol && node.kind === SyntaxKind.StringLiteral) {
60596089
return getReferencesForStringLiteral(<StringLiteral>node, sourceFiles);
@@ -6079,7 +6109,8 @@ namespace ts {
60796109

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

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

@@ -6431,12 +6462,87 @@ namespace ts {
64316462
const referencedSymbol = getReferencedSymbol(shorthandValueSymbol);
64326463
referencedSymbol.references.push(getReferenceEntryFromNode(referenceSymbolDeclaration.name));
64336464
}
6465+
else if (searchLocation.kind === SyntaxKind.ConstructorKeyword) {
6466+
findAdditionalConstructorReferences(referenceSymbol, referenceLocation);
6467+
}
64346468
}
64356469
});
64366470
}
64376471

64386472
return;
64396473

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

6572+
6573+
64666574
function getReferencesForSuperKeyword(superKeyword: Node): ReferencedSymbol[] {
64676575
let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false);
64686576
if (!searchSpaceNode) {
@@ -6810,8 +6918,8 @@ namespace ts {
68106918
}
68116919
}
68126920

6813-
function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol {
6814-
if (searchSymbols.indexOf(referenceSymbol) >= 0) {
6921+
function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol | undefined {
6922+
if (contains(searchSymbols, referenceSymbol)) {
68156923
return referenceSymbol;
68166924
}
68176925

@@ -6822,6 +6930,11 @@ namespace ts {
68226930
return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation);
68236931
}
68246932

6933+
// If we are in a constructor and we didn't find the symbol yet, we should try looking for the constructor instead.
6934+
if (isNewExpressionTarget(referenceLocation) && referenceSymbol.members && referenceSymbol.members["__constructor"]) {
6935+
return getRelatedSymbol(searchSymbols, referenceSymbol.members["__constructor"], referenceLocation.parent);
6936+
}
6937+
68256938
// If the reference location is in an object literal, try to get the contextual type for the
68266939
// object literal, lookup the property symbol in the contextual type, and use this symbol to
68276940
// compare to our searchSymbol
@@ -8343,6 +8456,15 @@ namespace ts {
83438456
};
83448457
}
83458458

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

0 commit comments

Comments
 (0)