Merge pull request #10540 from Microsoft/constructor_references

Allow to find all references for constructors
This commit is contained in:
Andy 2016-09-01 13:24:01 -07:00 committed by GitHub
commit 8038eb943e
6 changed files with 186 additions and 18 deletions

View File

@ -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);
}

View File

@ -1535,5 +1535,4 @@ namespace ts {
? ((fileName) => fileName)
: ((fileName) => fileName.toLowerCase());
}
}

View File

@ -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 {

View File

@ -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) {

View 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);
}

View File

@ -0,0 +1,8 @@
/// <reference path="fourslash.ts" />
////class C {
//// [|constructor|](n: number);
//// [|constructor|](){}
////}
verify.rangesReferenceEachOther();