diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index e2d2bb3886bf3..a89152e043c28 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -411,7 +411,7 @@ module ts { while (true) { node = node.parent; if (!node) { - return node; + return undefined; } switch (node.kind) { case SyntaxKind.ArrowFunction: @@ -434,6 +434,23 @@ module ts { } } + export function getSuperContainer(node: Node): Node { + while (true) { + node = node.parent; + if (!node) { + return undefined; + } + switch (node.kind) { + case SyntaxKind.Property: + case SyntaxKind.Method: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return node; + } + } + } + export function hasRestParameters(s: SignatureDeclaration): boolean { return s.parameters.length > 0 && (s.parameters[s.parameters.length - 1].flags & NodeFlags.Rest) !== 0; } diff --git a/src/services/services.ts b/src/services/services.ts index 12f7e0ce24e01..c8e73f657a445 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2134,7 +2134,6 @@ module ts { return result; } - /// Find references function getOccurrencesAtPosition(filename: string, position: number): ReferenceEntry[] { synchronizeHostData(); @@ -2146,7 +2145,7 @@ module ts { return undefined; } - if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || + if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.SuperKeyword || isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isNameOfExternalModuleImportOrDeclaration(node)) { return getReferencesForNode(node, [sourceFile]); } @@ -2378,7 +2377,9 @@ module ts { } if (node.kind !== SyntaxKind.Identifier && - node.kind !== SyntaxKind.ThisKeyword && + // TODO (drosen): This should be enabled in a later release - currently breaks rename. + //node.kind !== SyntaxKind.ThisKeyword && + //node.kind !== SyntaxKind.SuperKeyword && !isLiteralNameOfPropertyDeclarationOrIndexAccess(node) && !isNameOfExternalModuleImportOrDeclaration(node)) { return undefined; @@ -2406,6 +2407,10 @@ module ts { return getReferencesForThisKeyword(node, sourceFiles); } + if (node.kind === SyntaxKind.SuperKeyword) { + return getReferencesForSuperKeyword(node); + } + var symbol = typeInfoResolver.getSymbolInfo(node); // Could not find a symbol e.g. unknown identifier @@ -2627,32 +2632,75 @@ module ts { } } - function getReferencesForThisKeyword(thisKeyword: Node, sourceFiles: SourceFile[]) { - // Get the owner" of the 'this' keyword. - var thisContainer = getThisContainer(thisKeyword, /* includeArrowFunctions */ false); + function getReferencesForSuperKeyword(superKeyword: Node): ReferenceEntry[]{ + var searchSpaceNode = getSuperContainer(superKeyword); + if (!searchSpaceNode) { + return undefined; + } + // Whether 'super' occurs in a static context within a class. + var staticFlag = NodeFlags.Static; + + switch (searchSpaceNode.kind) { + case SyntaxKind.Property: + case SyntaxKind.Method: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + staticFlag &= searchSpaceNode.flags; + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class + break; + default: + return undefined; + } + + var result: ReferenceEntry[] = []; - var searchSpaceNode: Node; + var sourceFile = searchSpaceNode.getSourceFile(); + var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode.getStart(), searchSpaceNode.getEnd()); + forEach(possiblePositions, position => { + cancellationToken.throwIfCancellationRequested(); - // Whether 'this' occurs in a static context within a class; + var node = getNodeAtPosition(sourceFile, position); + + if (!node || node.kind !== SyntaxKind.SuperKeyword) { + return; + } + + var container = getSuperContainer(node); + + // If we have a 'super' container, we must have an enclosing class. + // Now make sure the owning class is the same as the search-space + // and has the same static qualifier as the original 'super's owner. + if (container && (NodeFlags.Static & container.flags) === staticFlag && container.parent.symbol === searchSpaceNode.symbol) { + result.push(getReferenceEntryFromNode(node)); + } + }); + + return result; + } + + function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[]): ReferenceEntry[] { + var searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); + + // Whether 'this' occurs in a static context within a class. var staticFlag = NodeFlags.Static; - switch (thisContainer.kind) { + switch (searchSpaceNode.kind) { case SyntaxKind.Property: case SyntaxKind.Method: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: - searchSpaceNode = thisContainer.parent; // should be the owning class - staticFlag &= thisContainer.flags + staticFlag &= searchSpaceNode.flags + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class break; case SyntaxKind.SourceFile: - if (isExternalModule(thisContainer)) { + if (isExternalModule(searchSpaceNode)) { return undefined; } // Fall through case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: - searchSpaceNode = thisContainer; break; default: return undefined; @@ -2683,31 +2731,24 @@ module ts { return; } - // Get the owner of the 'this' keyword. - // This *should* be a node that occurs somewhere within searchSpaceNode. var container = getThisContainer(node, /* includeArrowFunctions */ false); - switch (container.kind) { - case SyntaxKind.Property: - case SyntaxKind.Method: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // Make sure the container belongs to the same class - // and has the appropriate static modifier from the original container. - if (searchSpaceNode.symbol === container.parent.symbol && (container.flags & NodeFlags.Static) === staticFlag) { + switch (searchSpaceNode.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + if (searchSpaceNode.symbol === container.symbol) { result.push(getReferenceEntryFromNode(node)); } break; - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - if (searchSpaceNode.symbol === container.symbol) { + case SyntaxKind.ClassDeclaration: + // Make sure the container belongs to the same class + // and has the appropriate static modifier from the original container. + if (container.parent && searchSpaceNode.symbol === container.parent.symbol && (container.flags & NodeFlags.Static) === staticFlag) { result.push(getReferenceEntryFromNode(node)); } break; case SyntaxKind.SourceFile: - // Add all 'this' keywords that belong to the top-level scope. - if (searchSpaceNode.kind === SyntaxKind.SourceFile && !isExternalModule(searchSpaceNode)) { + if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { result.push(getReferenceEntryFromNode(node)); } break; diff --git a/tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts b/tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts index bc31ffd1579c3..4b9f7a450ec46 100644 --- a/tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts +++ b/tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts @@ -12,4 +12,7 @@ goTo.file("file1.ts"); goTo.marker(); -verify.referencesCountIs(8); \ No newline at end of file + +// TODO (drosen): The CURRENT behavior is that findAllRefs doesn't work on 'this' or 'super' keywords. +// This should change down the line. +verify.referencesCountIs(0); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesSuper.ts b/tests/cases/fourslash/getOccurrencesSuper.ts new file mode 100644 index 0000000000000..f53d9aca62e4d --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSuper.ts @@ -0,0 +1,64 @@ +/// + +////class SuperType { +//// superMethod() { +//// } +//// +//// static superStaticMethod() { +//// return 10; +//// } +////} +//// +////class SubType extends SuperType { +//// public prop1 = [|s/**/uper|].superMethod; +//// private prop2 = [|super|].superMethod; +//// +//// constructor() { +//// [|super|](); +//// } +//// +//// public method1() { +//// return [|super|].superMethod(); +//// } +//// +//// private method2() { +//// return [|super|].superMethod(); +//// } +//// +//// public method3() { +//// var x = () => [|super|].superMethod(); +//// +//// // Bad but still gets highlighted +//// function f() { +//// [|super|].superMethod(); +//// } +//// } +//// +//// // Bad but still gets highlighted. +//// public static statProp1 = super.superStaticMethod; +//// +//// public static staticMethod1() { +//// return super.superStaticMethod(); +//// } +//// +//// private static staticMethod2() { +//// return super.superStaticMethod(); +//// } +//// +//// // Are not actually 'super' keywords. +//// super = 10; +//// static super = 20; +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesSuper2.ts b/tests/cases/fourslash/getOccurrencesSuper2.ts new file mode 100644 index 0000000000000..1392170ae188f --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSuper2.ts @@ -0,0 +1,64 @@ +/// + +////class SuperType { +//// superMethod() { +//// } +//// +//// static superStaticMethod() { +//// return 10; +//// } +////} +//// +////class SubType extends SuperType { +//// public prop1 = super.superMethod; +//// private prop2 = super.superMethod; +//// +//// constructor() { +//// super(); +//// } +//// +//// public method1() { +//// return super.superMethod(); +//// } +//// +//// private method2() { +//// return super.superMethod(); +//// } +//// +//// public method3() { +//// var x = () => super.superMethod(); +//// +//// // Bad but still gets highlighted +//// function f() { +//// super.superMethod(); +//// } +//// } +//// +//// // Bad but still gets highlighted. +//// public static statProp1 = [|super|].superStaticMethod; +//// +//// public static staticMethod1() { +//// return [|super|].superStaticMethod(); +//// } +//// +//// private static staticMethod2() { +//// return [|supe/**/r|].superStaticMethod(); +//// } +//// +//// // Are not actually 'super' keywords. +//// super = 10; +//// static super = 20; +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesSuperNegatives.ts b/tests/cases/fourslash/getOccurrencesSuperNegatives.ts new file mode 100644 index 0000000000000..8c76da33c121e --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSuperNegatives.ts @@ -0,0 +1,27 @@ +/// + +////function f(x = [|super|]) { +//// [|super|]; +////} +//// +////module M { +//// [|super|]; +//// function f(x = [|super|]) { +//// [|super|]; +//// } +//// +//// class A { +//// } +//// +//// class B extends A { +//// constructor() { +//// super(); +//// } +//// } +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + verify.occurrencesAtPositionCount(0); +});