mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-14 19:16:17 -06:00
Merge pull request #10540 from Microsoft/constructor_references
Allow to find all references for constructors
This commit is contained in:
commit
8038eb943e
@ -8758,7 +8758,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);
|
||||
}
|
||||
|
||||
@ -1535,5 +1535,4 @@ namespace ts {
|
||||
? ((fileName) => fileName)
|
||||
: ((fileName) => fileName.toLowerCase());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2671,10 +2671,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 &&
|
||||
(<HeritageClause>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 {
|
||||
|
||||
@ -2796,18 +2796,26 @@ namespace ts {
|
||||
return isRightSideOfPropertyAccess(node) ? node.parent : node;
|
||||
}
|
||||
|
||||
function climbPastManyPropertyAccesses(node: Node): Node {
|
||||
return isRightSideOfPropertyAccess(node) ? climbPastManyPropertyAccesses(node.parent) : node;
|
||||
/** 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 tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined {
|
||||
return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent);
|
||||
}
|
||||
|
||||
function isCallExpressionTarget(node: Node): boolean {
|
||||
node = climbPastPropertyAccess(node);
|
||||
return node && node.parent && node.parent.kind === SyntaxKind.CallExpression && (<CallExpression>node.parent).expression === node;
|
||||
return isCallOrNewExpressionTarget(node, SyntaxKind.CallExpression);
|
||||
}
|
||||
|
||||
function isNewExpressionTarget(node: Node): boolean {
|
||||
node = climbPastPropertyAccess(node);
|
||||
return node && node.parent && node.parent.kind === SyntaxKind.NewExpression && (<CallExpression>node.parent).expression === node;
|
||||
return isCallOrNewExpressionTarget(node, SyntaxKind.NewExpression);
|
||||
}
|
||||
|
||||
function isCallOrNewExpressionTarget(node: Node, kind: SyntaxKind) {
|
||||
const target = climbPastPropertyAccess(node);
|
||||
return target && target.parent && target.parent.kind === kind && (<CallExpression>target.parent).expression === target;
|
||||
}
|
||||
|
||||
function climbPastManyPropertyAccesses(node: Node): Node {
|
||||
return isRightSideOfPropertyAccess(node) ? climbPastManyPropertyAccesses(node.parent) : node;
|
||||
}
|
||||
|
||||
/** Returns a CallLikeExpression where `node` is the target being invoked. */
|
||||
@ -4625,7 +4633,7 @@ 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);
|
||||
let type: Type;
|
||||
|
||||
// Class at constructor site need to be shown as constructor apart from property,method, vars
|
||||
@ -6045,6 +6053,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);
|
||||
}
|
||||
@ -6089,6 +6098,8 @@ namespace ts {
|
||||
return getReferencesForSuperKeyword(node);
|
||||
}
|
||||
|
||||
// `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword,
|
||||
// so we have to specify that we want the constructor symbol.
|
||||
const symbol = typeChecker.getSymbolAtLocation(node);
|
||||
|
||||
if (!symbol && node.kind === SyntaxKind.StringLiteral) {
|
||||
@ -6163,7 +6174,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);
|
||||
@ -6189,6 +6200,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(<Identifier>location);
|
||||
@ -6453,7 +6468,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);
|
||||
@ -6469,12 +6485,94 @@ 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 {
|
||||
Debug.assert(isClassLike(searchSymbol.valueDeclaration));
|
||||
|
||||
const referenceClass = referenceLocation.parent;
|
||||
if (referenceSymbol === searchSymbol && isClassLike(referenceClass)) {
|
||||
Debug.assert(referenceClass.name === referenceLocation);
|
||||
// This is the class declaration containing the constructor.
|
||||
addReferences(findOwnConstructorCalls(searchSymbol));
|
||||
}
|
||||
else {
|
||||
// If this class appears in `extends C`, then the extending class' "super" calls are references.
|
||||
const classExtending = tryGetClassByExtendingIdentifier(referenceLocation);
|
||||
if (classExtending && isClassLike(classExtending) && followAliasIfNecessary(referenceSymbol, referenceLocation) === searchSymbol) {
|
||||
addReferences(superConstructorAccesses(classExtending));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addReferences(references: Node[]): void {
|
||||
if (references.length) {
|
||||
const referencedSymbol = getReferencedSymbol(searchSymbol);
|
||||
addRange(referencedSymbol.references, map(references, getReferenceEntryFromNode));
|
||||
}
|
||||
}
|
||||
|
||||
/** `classSymbol` is the class where the constructor was defined.
|
||||
* Reference the constructor and all calls to `new this()`.
|
||||
*/
|
||||
function findOwnConstructorCalls(classSymbol: Symbol): Node[] {
|
||||
const result: Node[] = [];
|
||||
|
||||
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(classSymbol.exports, 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)) {
|
||||
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 = (<ConstructorDeclaration>decl).body;
|
||||
if (body) {
|
||||
forEachDescendantOfKind(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];
|
||||
@ -6855,16 +6953,17 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol {
|
||||
if (searchSymbols.indexOf(referenceSymbol) >= 0) {
|
||||
return referenceSymbol;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, searchLocationIsConstructor);
|
||||
}
|
||||
|
||||
// If the reference location is in an object literal, try to get the contextual type for the
|
||||
@ -8388,6 +8487,15 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void) {
|
||||
forEachChild(node, child => {
|
||||
if (child.kind === kind) {
|
||||
action(child);
|
||||
}
|
||||
forEachDescendantOfKind(child, kind, action);
|
||||
});
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function getNameTable(sourceFile: SourceFile): Map<number> {
|
||||
if (!sourceFile.nameTable) {
|
||||
|
||||
46
tests/cases/fourslash/findAllReferencesOfConstructor.ts
Normal file
46
tests/cases/fourslash/findAllReferencesOfConstructor.ts
Normal file
@ -0,0 +1,46 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @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(); }
|
||||
////}
|
||||
// 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(); }
|
||||
////}
|
||||
|
||||
// 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();
|
||||
for (const ctr of ranges.slice(0, 3)) {
|
||||
verify.referencesOf(ctr, ranges);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////class C {
|
||||
//// [|constructor|](n: number);
|
||||
//// [|constructor|](){}
|
||||
////}
|
||||
|
||||
verify.rangesReferenceEachOther();
|
||||
Loading…
x
Reference in New Issue
Block a user