Include static 'this' accesses in references of class (#20483)

This commit is contained in:
Andy
2017-12-18 10:54:22 -08:00
committed by GitHub
parent aa9e2ba318
commit 9a62454485
3 changed files with 87 additions and 12 deletions
+12
View File
@@ -5233,6 +5233,18 @@ namespace ts {
return node && (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor);
}
/* @internal */
export function isMethodOrAccessor(node: Node): node is MethodDeclaration | AccessorDeclaration {
switch (node.kind) {
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return true;
default:
return false;
}
}
// Type members
export function isTypeElement(node: Node): node is TypeElement {
+63 -12
View File
@@ -376,7 +376,7 @@ namespace ts.FindAllReferences.Core {
const searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), symbol.declarations);
const result: SymbolAndEntries[] = [];
const state = new State(sourceFiles, /*isForConstructor*/ node.kind === SyntaxKind.ConstructorKeyword, checker, cancellationToken, searchMeaning, options, result);
const state = new State(sourceFiles, getSpecialSearchKind(node), checker, cancellationToken, searchMeaning, options, result);
if (node.kind === SyntaxKind.DefaultKeyword) {
addReference(node, symbol, node, state);
@@ -403,6 +403,21 @@ namespace ts.FindAllReferences.Core {
return result;
}
function getSpecialSearchKind(node: Node): SpecialSearchKind {
switch (node.kind) {
case SyntaxKind.ConstructorKeyword:
return SpecialSearchKind.Constructor;
case SyntaxKind.Identifier:
if (isClassLike(node.parent)) {
Debug.assert(node.parent.name === node);
return SpecialSearchKind.Class;
}
// falls through
default:
return SpecialSearchKind.None;
}
}
/** Handle a few special cases relating to export/import specifiers. */
function skipPastExportOrImportSpecifier(symbol: Symbol, node: Node, checker: TypeChecker): Symbol {
const { parent } = node;
@@ -439,6 +454,12 @@ namespace ts.FindAllReferences.Core {
includes(symbol: Symbol): boolean;
}
const enum SpecialSearchKind {
None,
Constructor,
Class,
}
/**
* Holds all state needed for the finding references.
* Unlike `Search`, there is only one `State`.
@@ -472,7 +493,7 @@ namespace ts.FindAllReferences.Core {
constructor(
readonly sourceFiles: ReadonlyArray<SourceFile>,
/** True if we're searching for constructor references. */
readonly isForConstructor: boolean,
readonly specialSearchKind: SpecialSearchKind,
readonly checker: TypeChecker,
readonly cancellationToken: CancellationToken,
readonly searchMeaning: SemanticMeaning,
@@ -845,11 +866,16 @@ namespace ts.FindAllReferences.Core {
return;
}
if (state.isForConstructor) {
findConstructorReferences(referenceLocation, sourceFile, search, state);
}
else {
addReference(referenceLocation, relatedSymbol, search.location, state);
switch (state.specialSearchKind) {
case SpecialSearchKind.None:
addReference(referenceLocation, relatedSymbol, search.location, state);
break;
case SpecialSearchKind.Constructor:
addConstructorReferences(referenceLocation, sourceFile, search, state);
break;
case SpecialSearchKind.Class:
addClassStaticThisReferences(referenceLocation, search, state);
break;
}
getImportOrExportReferences(referenceLocation, referenceSymbol, search, state);
@@ -961,27 +987,52 @@ namespace ts.FindAllReferences.Core {
}
/** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */
function findConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void {
function addConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void {
if (isNewExpressionTarget(referenceLocation)) {
addReference(referenceLocation, search.symbol, search.location, state);
}
const pusher = state.referenceAdder(search.symbol, search.location);
const pusher = () => state.referenceAdder(search.symbol, search.location);
if (isClassLike(referenceLocation.parent)) {
Debug.assert(referenceLocation.parent.name === referenceLocation);
// This is the class declaration containing the constructor.
findOwnConstructorReferences(search.symbol, sourceFile, pusher);
findOwnConstructorReferences(search.symbol, sourceFile, pusher());
}
else {
// If this class appears in `extends C`, then the extending class' "super" calls are references.
const classExtending = tryGetClassByExtendingIdentifier(referenceLocation);
if (classExtending && isClassLike(classExtending)) {
findSuperConstructorAccesses(classExtending, pusher);
if (classExtending) {
findSuperConstructorAccesses(classExtending, pusher());
}
}
}
function addClassStaticThisReferences(referenceLocation: Node, search: Search, state: State): void {
addReference(referenceLocation, search.symbol, search.location, state);
if (isClassLike(referenceLocation.parent)) {
Debug.assert(referenceLocation.parent.name === referenceLocation);
// This is the class declaration.
addStaticThisReferences(referenceLocation.parent, state.referenceAdder(search.symbol, search.location));
}
}
function addStaticThisReferences(classLike: ClassLikeDeclaration, pusher: (node: Node) => void): void {
for (const member of classLike.members) {
if (!(isMethodOrAccessor(member) && hasModifier(member, ModifierFlags.Static))) {
continue;
}
member.body.forEachChild(function cb(node) {
if (node.kind === SyntaxKind.ThisKeyword) {
pusher(node);
}
else if (!isFunctionLike(node)) {
node.forEachChild(cb);
}
});
}
}
function getPropertyAccessExpressionFromRightHandSide(node: Node): PropertyAccessExpression {
return isRightSideOfPropertyAccess(node) && <PropertyAccessExpression>node.parent;
}
@@ -0,0 +1,12 @@
/// <reference path="fourslash.ts" />
////class [|{| "isWriteAccess": true, "isDefinition": true |}C|] {
//// static s() {
//// [|this|];
//// }
//// static get f() { return [|this|]; }
////}
const [r0, r1, r2] = test.ranges();
verify.referenceGroups(r0, [{ definition: "class C", ranges: [r0, r1, r2] }]);
verify.referenceGroups([r1, r2], [{ definition: "this: typeof C", ranges: [r1, r2] }]);