Skip to content

Commit

Permalink
Include static 'this' accesses in references of class (microsoft#20483)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy authored Dec 18, 2017
1 parent aa9e2ba commit 9a62454
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 12 deletions.
12 changes: 12 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
75 changes: 63 additions & 12 deletions src/services/findAllReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -961,24 +987,49 @@ 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);
}
});
}
}

Expand Down
12 changes: 12 additions & 0 deletions tests/cases/fourslash/findAllRefsClassWithStaticThisAccess.ts
Original file line number Diff line number Diff line change
@@ -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] }]);

0 comments on commit 9a62454

Please sign in to comment.