From 3f126a52f9b9f598acbfa01305424f47c5b0aeb1 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 24 Aug 2016 09:26:38 -0700 Subject: [PATCH 1/6] Allow to find all references for constructors --- src/compiler/checker.ts | 2 +- src/services/services.ts | 157 ++++++++++++++++-- .../findAllReferencesOfConstructor.ts | 43 +++++ 3 files changed, 188 insertions(+), 14 deletions(-) create mode 100644 tests/cases/fourslash/findAllReferencesOfConstructor.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2341a0f7dfd..46863997193 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8752,7 +8752,7 @@ namespace ts { // The location isn't a reference to the given symbol, meaning we're being asked // a hypothetical question of what type the symbol would have if there was a reference // to it at the given location. Since we have no control flow information for the - // hypotherical reference (control flow information is created and attached by the + // hypothetical reference (control flow information is created and attached by the // binder), we simply return the declared type of the symbol. return getTypeOfSymbol(symbol); } diff --git a/src/services/services.ts b/src/services/services.ts index a55f2cf61b0..c9ba2cd6056 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2788,18 +2788,41 @@ namespace ts { return node && node.parent && node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).name === node; } + function climbPastPropertyAccess(node: Node) { + return isRightSideOfPropertyAccess(node) ? node.parent : node; + } + function isCallExpressionTarget(node: Node): boolean { - if (isRightSideOfPropertyAccess(node)) { - node = node.parent; - } - return node && node.parent && node.parent.kind === SyntaxKind.CallExpression && (node.parent).expression === node; + return isCallOrNewExpressionTarget(node, SyntaxKind.CallExpression); } function isNewExpressionTarget(node: Node): boolean { - if (isRightSideOfPropertyAccess(node)) { - node = node.parent; + return isCallOrNewExpressionTarget(node, SyntaxKind.NewExpression); + } + + function isCallOrNewExpressionTarget(node: Node, kind: SyntaxKind) { + const target = climbPastPropertyAccess(node); + return target && target.parent && target.parent.kind === kind && (target.parent).expression === target; + } + + /** Get `C` given `N` if `N` is in the position `class C extends N` */ + function tryGetClassExtendingNode(node: Node): ClassLikeDeclaration | undefined { + const target = climbPastPropertyAccess(node); + + const expr = target.parent; + if (expr.kind !== SyntaxKind.ExpressionWithTypeArguments) { + return; + } + + const heritageClause = expr.parent; + if (heritageClause.kind !== SyntaxKind.HeritageClause) { + return; + } + + const classNode = heritageClause.parent; + if (getHeritageClause(classNode.heritageClauses, SyntaxKind.ExtendsKeyword) === heritageClause) { + return classNode; } - return node && node.parent && node.parent.kind === SyntaxKind.NewExpression && (node.parent).expression === node; } function isNameOfModuleDeclaration(node: Node) { @@ -4714,7 +4737,9 @@ namespace ts { if (functionDeclaration.kind === SyntaxKind.Constructor) { // show (constructor) Type(...) signature symbolKind = ScriptElementKind.constructorImplementationElement; - addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); + // For a constructor, `type` will be unknown. + const showSymbol = symbol.declarations[0].kind === SyntaxKind.Constructor ? symbol.parent : type.symbol; + addPrefixForAnyFunctionOrVar(showSymbol, symbolKind); } else { // (function/method) symbol(..signature) @@ -6008,6 +6033,7 @@ namespace ts { case SyntaxKind.Identifier: case SyntaxKind.ThisKeyword: // case SyntaxKind.SuperKeyword: TODO:GH#9268 + case SyntaxKind.ConstructorKeyword: case SyntaxKind.StringLiteral: return getReferencedSymbolsForNode(node, program.getSourceFiles(), findInStrings, findInComments); } @@ -6052,7 +6078,11 @@ namespace ts { return getReferencesForSuperKeyword(node); } - const symbol = typeChecker.getSymbolAtLocation(node); + const isConstructor = node.kind === SyntaxKind.ConstructorKeyword; + + // `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword, + // so we have to specify that we want the constructor symbol. + let symbol = isConstructor ? node.parent.symbol : typeChecker.getSymbolAtLocation(node); if (!symbol && node.kind === SyntaxKind.StringLiteral) { return getReferencesForStringLiteral(node, sourceFiles); @@ -6078,7 +6108,8 @@ namespace ts { // Get the text to search for. // Note: if this is an external module symbol, the name doesn't include quotes. - const declaredName = stripQuotes(getDeclaredName(typeChecker, symbol, node)); + const nameSymbol = isConstructor ? symbol.parent : symbol; // A constructor is referenced using the name of its class. + const declaredName = stripQuotes(getDeclaredName(typeChecker, nameSymbol, node)); // 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). @@ -6092,7 +6123,7 @@ namespace ts { getReferencesInNode(scope, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex); } else { - const internedName = getInternedName(symbol, node, declarations); + const internedName = isConstructor ? declaredName : getInternedName(symbol, node, declarations); for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested(); @@ -6430,12 +6461,98 @@ namespace ts { const referencedSymbol = getReferencedSymbol(shorthandValueSymbol); referencedSymbol.references.push(getReferenceEntryFromNode(referenceSymbolDeclaration.name)); } + else if (searchLocation.kind === SyntaxKind.ConstructorKeyword) { + findAdditionalConstructorReferences(referenceSymbol, referenceLocation); + } } }); } return; + /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ + function findAdditionalConstructorReferences(referenceSymbol: Symbol, referenceLocation: Node): void { + const searchClassSymbol = searchSymbol.parent; + Debug.assert(isClassLike(searchClassSymbol.valueDeclaration)); + + const referenceClass = referenceLocation.parent; + if (referenceSymbol === searchClassSymbol && isClassLike(referenceClass)) { + // This is the class declaration containing the constructor. + const calls = findOwnConstructorCalls(referenceSymbol, referenceClass); + addReferences(calls); + } + else { + // If this class appears in `extends C`, then the extending class' "super" calls are references. + const classExtending = tryGetClassExtendingNode(referenceLocation); + if (classExtending && isClassLike(classExtending)) { + if (getRelatedSymbol([searchClassSymbol], referenceSymbol, referenceLocation)) { + const supers = superConstructorAccesses(classExtending); + addReferences(supers); + } + } + } + } + + function addReferences(references: Node[]): void { + if (references.length) { + const referencedSymbol = getReferencedSymbol(searchSymbol); + addRange(referencedSymbol.references, map(references, getReferenceEntryFromNode)); + } + } + + /** `referenceLocation` is the class where the constructor was defined. + * Reference the constructor and all calls to `new this()`. + */ + function findOwnConstructorCalls(referenceSymbol: Symbol, referenceLocation: ClassLikeDeclaration): Node[] { + const result: Node[] = []; + + for (const decl of referenceSymbol.members["__constructor"].declarations) { + Debug.assert(decl.kind === SyntaxKind.Constructor); + const ctrKeyword = decl.getChildAt(0); + Debug.assert(ctrKeyword.kind === SyntaxKind.ConstructorKeyword); + result.push(ctrKeyword); + } + + forEachProperty(referenceSymbol.exports, member => { + const decl = member.valueDeclaration; + if (decl && decl.kind === SyntaxKind.MethodDeclaration) { + const body = (decl).body; + if (body) { + forEachDescendant(body, SyntaxKind.ThisKeyword, thisKeyword => { + if (isNewExpressionTarget(thisKeyword)) { + result.push(thisKeyword); + } + }); + } + } + }); + + return result; + } + + /** Find references to `super` in the constructor of an extending class. */ + function superConstructorAccesses(cls: ClassLikeDeclaration): Node[] { + const symbol = cls.symbol; + const ctr = symbol.members["__constructor"]; + if (!ctr) { + return []; + } + + const result: Node[] = []; + for (const decl of ctr.declarations) { + Debug.assert(decl.kind === SyntaxKind.Constructor); + const body = (decl).body; + if (body) { + forEachDescendant(body, SyntaxKind.SuperKeyword, node => { + if (isCallExpressionTarget(node)) { + result.push(node); + } + }); + } + }; + return result; + } + function getReferencedSymbol(symbol: Symbol): ReferencedSymbol { const symbolId = getSymbolId(symbol); let index = symbolToIndex[symbolId]; @@ -6809,8 +6926,8 @@ namespace ts { } } - function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol { - if (searchSymbols.indexOf(referenceSymbol) >= 0) { + function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol | undefined { + if (contains(searchSymbols, referenceSymbol)) { return referenceSymbol; } @@ -6821,6 +6938,11 @@ namespace ts { return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation); } + // If we are in a constructor and we didn't find the symbol yet, we should try looking for the constructor instead. + if (isNewExpressionTarget(referenceLocation) && referenceSymbol.members && referenceSymbol.members["__constructor"]) { + return getRelatedSymbol(searchSymbols, referenceSymbol.members["__constructor"], referenceLocation.parent); + } + // If the reference location is in an object literal, try to get the contextual type for the // object literal, lookup the property symbol in the contextual type, and use this symbol to // compare to our searchSymbol @@ -8342,6 +8464,15 @@ namespace ts { }; } + function forEachDescendant(node: Node, kind: SyntaxKind, action: (node: Node) => void) { + forEachChild(node, child => { + if (child.kind === kind) { + action(child); + } + forEachDescendant(child, kind, action); + }); + } + /* @internal */ export function getNameTable(sourceFile: SourceFile): Map { if (!sourceFile.nameTable) { diff --git a/tests/cases/fourslash/findAllReferencesOfConstructor.ts b/tests/cases/fourslash/findAllReferencesOfConstructor.ts new file mode 100644 index 00000000000..15d002862a1 --- /dev/null +++ b/tests/cases/fourslash/findAllReferencesOfConstructor.ts @@ -0,0 +1,43 @@ +/// + +// @Filename: a.ts +////export class C { +//// [|constructor|](n: number); +//// [|constructor|](); +//// [|constructor|](n?: number){} +//// static f() { +//// this.f(); +//// new [|this|](); +//// } +////} +////new [|C|](); +// Does not handle alias. +////const D = C; +////new D(); + +// @Filename: b.ts +////import { C } from "./a"; +////new [|C|](); + +// @Filename: c.ts +////import { C } from "./a"; +////class D extends C { +//// constructor() { +//// [|super|](); +//// super.method(); +//// } +//// method() { super(); } +////} +////class E implements C { +//// constructor() { super(); } +////} + +// Works with qualified names too +// @Filename: d.ts +////import * as a from "./a"; +////new a.[|C|](); +////class d extends a.C { constructor() { [|super|](); } + +const ranges = test.ranges(); +verify.referencesOf(ranges[0], ranges); +verify.referencesOf(ranges[1], ranges); From c1a291dffe8552f90ad372550ab52aa78f9b66ca Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 26 Aug 2016 10:06:27 -0700 Subject: [PATCH 2/6] Get the type of a constructor as the type of the class Fixes a bug when constructor overloads are improper and no signatures can be found. --- src/services/services.ts | 14 +++++++++----- .../fourslash/findAllReferencesOfConstructor.ts | 5 +++-- .../findAllReferencesOfConstructor_badOverload.ts | 8 ++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 tests/cases/fourslash/findAllReferencesOfConstructor_badOverload.ts diff --git a/src/services/services.ts b/src/services/services.ts index c9ba2cd6056..e58579e27f9 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4628,7 +4628,8 @@ namespace ts { const symbolFlags = symbol.flags; let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(symbol, symbolFlags, location); let hasAddedSymbolInfo: boolean; - const isThisExpression: boolean = location.kind === SyntaxKind.ThisKeyword && isExpression(location); + const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location); + const isConstructor = location.kind === SyntaxKind.ConstructorKeyword; let type: Type; // Class at constructor site need to be shown as constructor apart from property,method, vars @@ -4639,7 +4640,12 @@ namespace ts { } let signature: Signature; - type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location); + type = isThisExpression + ? typeChecker.getTypeAtLocation(location) + : isConstructor + // For constructor, get type of the class. + ? typeChecker.getTypeOfSymbolAtLocation(symbol.parent, location) + : typeChecker.getTypeOfSymbolAtLocation(symbol, location); if (type) { if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) { const right = (location.parent).name; @@ -4737,9 +4743,7 @@ namespace ts { if (functionDeclaration.kind === SyntaxKind.Constructor) { // show (constructor) Type(...) signature symbolKind = ScriptElementKind.constructorImplementationElement; - // For a constructor, `type` will be unknown. - const showSymbol = symbol.declarations[0].kind === SyntaxKind.Constructor ? symbol.parent : type.symbol; - addPrefixForAnyFunctionOrVar(showSymbol, symbolKind); + addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); } else { // (function/method) symbol(..signature) diff --git a/tests/cases/fourslash/findAllReferencesOfConstructor.ts b/tests/cases/fourslash/findAllReferencesOfConstructor.ts index 15d002862a1..27c253b1356 100644 --- a/tests/cases/fourslash/findAllReferencesOfConstructor.ts +++ b/tests/cases/fourslash/findAllReferencesOfConstructor.ts @@ -39,5 +39,6 @@ ////class d extends a.C { constructor() { [|super|](); } const ranges = test.ranges(); -verify.referencesOf(ranges[0], ranges); -verify.referencesOf(ranges[1], ranges); +for (const ctr of ranges.slice(0, 3)) { + verify.referencesOf(ctr, ranges); +} diff --git a/tests/cases/fourslash/findAllReferencesOfConstructor_badOverload.ts b/tests/cases/fourslash/findAllReferencesOfConstructor_badOverload.ts new file mode 100644 index 00000000000..77479bad113 --- /dev/null +++ b/tests/cases/fourslash/findAllReferencesOfConstructor_badOverload.ts @@ -0,0 +1,8 @@ +/// + +////class C { +//// [|constructor|](n: number); +//// [|constructor|](){} +////} + +verify.rangesReferenceEachOther(); From f90d8ddca53e011bc9d544d9436482bd0bb583a4 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 26 Aug 2016 10:40:10 -0700 Subject: [PATCH 3/6] Reuse code for tryGetClassExtendingIdentifier --- src/compiler/utilities.ts | 13 ++++++++++--- src/services/services.ts | 23 ++++------------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0c026fc6b6d..4d27730ab33 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2659,10 +2659,17 @@ namespace ts { return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment; } - export function isExpressionWithTypeArgumentsInClassExtendsClause(node: Node): boolean { - return node.kind === SyntaxKind.ExpressionWithTypeArguments && + /** Get `C` given `N` if `N` is in the position `class C extends N` where `N` is an ExpressionWithTypeArguments. */ + export function tryGetClassExtendingExpressionWithTypeArguments(node: Node): ClassLikeDeclaration | undefined { + if (node.kind === SyntaxKind.ExpressionWithTypeArguments && (node.parent).token === SyntaxKind.ExtendsKeyword && - isClassLike(node.parent.parent); + isClassLike(node.parent.parent)) { + return node.parent.parent; + } + } + + export function isExpressionWithTypeArgumentsInClassExtendsClause(node: Node): boolean { + return tryGetClassExtendingExpressionWithTypeArguments(node) !== undefined; } export function isEntityNameExpression(node: Expression): node is EntityNameExpression { diff --git a/src/services/services.ts b/src/services/services.ts index e58579e27f9..0b86fdb0d09 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2805,24 +2805,9 @@ namespace ts { return target && target.parent && target.parent.kind === kind && (target.parent).expression === target; } - /** Get `C` given `N` if `N` is in the position `class C extends N` */ - function tryGetClassExtendingNode(node: Node): ClassLikeDeclaration | undefined { - const target = climbPastPropertyAccess(node); - - const expr = target.parent; - if (expr.kind !== SyntaxKind.ExpressionWithTypeArguments) { - return; - } - - const heritageClause = expr.parent; - if (heritageClause.kind !== SyntaxKind.HeritageClause) { - return; - } - - const classNode = heritageClause.parent; - if (getHeritageClause(classNode.heritageClauses, SyntaxKind.ExtendsKeyword) === heritageClause) { - return classNode; - } + /** 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. */ + function tryGetClassExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { + return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent); } function isNameOfModuleDeclaration(node: Node) { @@ -6487,7 +6472,7 @@ namespace ts { } else { // If this class appears in `extends C`, then the extending class' "super" calls are references. - const classExtending = tryGetClassExtendingNode(referenceLocation); + const classExtending = tryGetClassExtendingIdentifier(referenceLocation); if (classExtending && isClassLike(classExtending)) { if (getRelatedSymbol([searchClassSymbol], referenceSymbol, referenceLocation)) { const supers = superConstructorAccesses(classExtending); From 7e6f18d7654e889b0c5005572cd5fe1e92811e5c Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 26 Aug 2016 13:46:12 -0700 Subject: [PATCH 4/6] Don't use constructor symbol for search -- use class symbol and filter out only 'new C()' uses. --- src/compiler/core.ts | 1 - src/services/services.ts | 54 ++++++++----------- .../findAllReferencesOfConstructor.ts | 2 + 3 files changed, 23 insertions(+), 34 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 27b27bc6532..636a89105fc 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1515,5 +1515,4 @@ namespace ts { ? ((fileName) => fileName) : ((fileName) => fileName.toLowerCase()); } - } diff --git a/src/services/services.ts b/src/services/services.ts index 0b86fdb0d09..47391d609b2 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4614,7 +4614,6 @@ namespace ts { let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(symbol, symbolFlags, location); let hasAddedSymbolInfo: boolean; const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location); - const isConstructor = location.kind === SyntaxKind.ConstructorKeyword; let type: Type; // Class at constructor site need to be shown as constructor apart from property,method, vars @@ -4625,12 +4624,7 @@ namespace ts { } let signature: Signature; - type = isThisExpression - ? typeChecker.getTypeAtLocation(location) - : isConstructor - // For constructor, get type of the class. - ? typeChecker.getTypeOfSymbolAtLocation(symbol.parent, location) - : typeChecker.getTypeOfSymbolAtLocation(symbol, location); + type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location); if (type) { if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) { const right = (location.parent).name; @@ -6067,11 +6061,9 @@ namespace ts { return getReferencesForSuperKeyword(node); } - const isConstructor = node.kind === SyntaxKind.ConstructorKeyword; - // `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword, // so we have to specify that we want the constructor symbol. - let symbol = isConstructor ? node.parent.symbol : typeChecker.getSymbolAtLocation(node); + const symbol = typeChecker.getSymbolAtLocation(node); if (!symbol && node.kind === SyntaxKind.StringLiteral) { return getReferencesForStringLiteral(node, sourceFiles); @@ -6097,8 +6089,7 @@ namespace ts { // Get the text to search for. // Note: if this is an external module symbol, the name doesn't include quotes. - const nameSymbol = isConstructor ? symbol.parent : symbol; // A constructor is referenced using the name of its class. - const declaredName = stripQuotes(getDeclaredName(typeChecker, nameSymbol, node)); + const declaredName = stripQuotes(getDeclaredName(typeChecker, symbol, node)); // 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). @@ -6112,7 +6103,7 @@ namespace ts { getReferencesInNode(scope, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex); } else { - const internedName = isConstructor ? declaredName : getInternedName(symbol, node, declarations); + const internedName = getInternedName(symbol, node, declarations); for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested(); @@ -6145,7 +6136,7 @@ namespace ts { }; } - function getAliasSymbolForPropertyNameSymbol(symbol: Symbol, location: Node): Symbol { + function getAliasSymbolForPropertyNameSymbol(symbol: Symbol, location: Node): Symbol | undefined { if (symbol.flags & SymbolFlags.Alias) { // Default import get alias const defaultImport = getDeclarationOfKind(symbol, SyntaxKind.ImportClause); @@ -6171,6 +6162,10 @@ namespace ts { return undefined; } + function followAliasIfNecessary(symbol: Symbol, location: Node): Symbol { + return getAliasSymbolForPropertyNameSymbol(symbol, location) || symbol; + } + function getPropertySymbolOfDestructuringAssignment(location: Node) { return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) && typeChecker.getPropertySymbolOfDestructuringAssignment(location); @@ -6434,7 +6429,8 @@ namespace ts { if (referenceSymbol) { const referenceSymbolDeclaration = referenceSymbol.valueDeclaration; const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration); - const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation); + const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, + /*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword); if (relatedSymbol) { const referencedSymbol = getReferencedSymbol(relatedSymbol); @@ -6461,23 +6457,19 @@ namespace ts { /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ function findAdditionalConstructorReferences(referenceSymbol: Symbol, referenceLocation: Node): void { - const searchClassSymbol = searchSymbol.parent; - Debug.assert(isClassLike(searchClassSymbol.valueDeclaration)); + Debug.assert(isClassLike(searchSymbol.valueDeclaration)); const referenceClass = referenceLocation.parent; - if (referenceSymbol === searchClassSymbol && isClassLike(referenceClass)) { + if (referenceSymbol === searchSymbol && isClassLike(referenceClass)) { + Debug.assert(referenceClass.name === referenceLocation); // This is the class declaration containing the constructor. - const calls = findOwnConstructorCalls(referenceSymbol, referenceClass); - addReferences(calls); + addReferences(findOwnConstructorCalls(referenceSymbol, referenceClass)); } else { // If this class appears in `extends C`, then the extending class' "super" calls are references. const classExtending = tryGetClassExtendingIdentifier(referenceLocation); - if (classExtending && isClassLike(classExtending)) { - if (getRelatedSymbol([searchClassSymbol], referenceSymbol, referenceLocation)) { - const supers = superConstructorAccesses(classExtending); - addReferences(supers); - } + if (classExtending && isClassLike(classExtending) && followAliasIfNecessary(referenceSymbol, referenceLocation) === searchSymbol) { + addReferences(superConstructorAccesses(classExtending)); } } } @@ -6915,21 +6907,17 @@ namespace ts { } } - function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol | undefined { + function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean): Symbol | undefined { if (contains(searchSymbols, referenceSymbol)) { - return referenceSymbol; + // If we are searching for constructor uses, they must be 'new' expressions. + return !(searchLocationIsConstructor && !isNewExpressionTarget(referenceLocation)) && referenceSymbol; } // If the reference symbol is an alias, check if what it is aliasing is one of the search // symbols but by looking up for related symbol of this alias so it can handle multiple level of indirectness. const aliasSymbol = getAliasSymbolForPropertyNameSymbol(referenceSymbol, referenceLocation); if (aliasSymbol) { - return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation); - } - - // If we are in a constructor and we didn't find the symbol yet, we should try looking for the constructor instead. - if (isNewExpressionTarget(referenceLocation) && referenceSymbol.members && referenceSymbol.members["__constructor"]) { - return getRelatedSymbol(searchSymbols, referenceSymbol.members["__constructor"], referenceLocation.parent); + return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, searchLocationIsConstructor); } // If the reference location is in an object literal, try to get the contextual type for the diff --git a/tests/cases/fourslash/findAllReferencesOfConstructor.ts b/tests/cases/fourslash/findAllReferencesOfConstructor.ts index 27c253b1356..b25508a60db 100644 --- a/tests/cases/fourslash/findAllReferencesOfConstructor.ts +++ b/tests/cases/fourslash/findAllReferencesOfConstructor.ts @@ -28,6 +28,8 @@ //// } //// method() { super(); } ////} +// Does not find 'super()' calls for a class that merely implements 'C', +// since those must be calling a different constructor. ////class E implements C { //// constructor() { super(); } ////} From 0dc976df1ebaebbf9a26457d1038589cecad08ca Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 26 Aug 2016 13:50:48 -0700 Subject: [PATCH 5/6] Remove unused parameter --- src/services/services.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 47391d609b2..b95feb92079 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -6463,7 +6463,7 @@ namespace ts { if (referenceSymbol === searchSymbol && isClassLike(referenceClass)) { Debug.assert(referenceClass.name === referenceLocation); // This is the class declaration containing the constructor. - addReferences(findOwnConstructorCalls(referenceSymbol, referenceClass)); + addReferences(findOwnConstructorCalls(searchSymbol)); } else { // If this class appears in `extends C`, then the extending class' "super" calls are references. @@ -6481,20 +6481,20 @@ namespace ts { } } - /** `referenceLocation` is the class where the constructor was defined. + /** `classSymbol` is the class where the constructor was defined. * Reference the constructor and all calls to `new this()`. */ - function findOwnConstructorCalls(referenceSymbol: Symbol, referenceLocation: ClassLikeDeclaration): Node[] { + function findOwnConstructorCalls(classSymbol: Symbol): Node[] { const result: Node[] = []; - for (const decl of referenceSymbol.members["__constructor"].declarations) { + for (const decl of classSymbol.members["__constructor"].declarations) { Debug.assert(decl.kind === SyntaxKind.Constructor); const ctrKeyword = decl.getChildAt(0); Debug.assert(ctrKeyword.kind === SyntaxKind.ConstructorKeyword); result.push(ctrKeyword); } - forEachProperty(referenceSymbol.exports, member => { + forEachProperty(classSymbol.exports, member => { const decl = member.valueDeclaration; if (decl && decl.kind === SyntaxKind.MethodDeclaration) { const body = (decl).body; From ab753652facf8a3a620b609159329cc5772db434 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 1 Sep 2016 13:02:47 -0700 Subject: [PATCH 6/6] Respond to PR comments --- src/services/services.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 7e9a356e738..c97455c3244 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2797,7 +2797,7 @@ namespace ts { } /** 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. */ - function tryGetClassExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { + function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent); } @@ -6506,7 +6506,7 @@ namespace ts { } else { // If this class appears in `extends C`, then the extending class' "super" calls are references. - const classExtending = tryGetClassExtendingIdentifier(referenceLocation); + const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); if (classExtending && isClassLike(classExtending) && followAliasIfNecessary(referenceSymbol, referenceLocation) === searchSymbol) { addReferences(superConstructorAccesses(classExtending)); } @@ -6538,7 +6538,7 @@ namespace ts { if (decl && decl.kind === SyntaxKind.MethodDeclaration) { const body = (decl).body; if (body) { - forEachDescendant(body, SyntaxKind.ThisKeyword, thisKeyword => { + forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { if (isNewExpressionTarget(thisKeyword)) { result.push(thisKeyword); } @@ -6563,7 +6563,7 @@ namespace ts { Debug.assert(decl.kind === SyntaxKind.Constructor); const body = (decl).body; if (body) { - forEachDescendant(body, SyntaxKind.SuperKeyword, node => { + forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { if (isCallExpressionTarget(node)) { result.push(node); } @@ -6956,7 +6956,7 @@ namespace ts { function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean): Symbol | undefined { if (contains(searchSymbols, referenceSymbol)) { // If we are searching for constructor uses, they must be 'new' expressions. - return !(searchLocationIsConstructor && !isNewExpressionTarget(referenceLocation)) && referenceSymbol; + return (!searchLocationIsConstructor || isNewExpressionTarget(referenceLocation)) && referenceSymbol; } // If the reference symbol is an alias, check if what it is aliasing is one of the search @@ -8487,12 +8487,12 @@ namespace ts { }; } - function forEachDescendant(node: Node, kind: SyntaxKind, action: (node: Node) => void) { + function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void) { forEachChild(node, child => { if (child.kind === kind) { action(child); } - forEachDescendant(child, kind, action); + forEachDescendantOfKind(child, kind, action); }); }