diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2a2561907f7..4a3816f04d2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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 { diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 9726df1487d..fa56326b9f0 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -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, /** 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) && node.parent; } diff --git a/tests/cases/fourslash/findAllRefsClassWithStaticThisAccess.ts b/tests/cases/fourslash/findAllRefsClassWithStaticThisAccess.ts new file mode 100644 index 00000000000..9bb128e4556 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsClassWithStaticThisAccess.ts @@ -0,0 +1,12 @@ +/// + +////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] }]);