Support filtering references based on meaning
This commit is contained in:
parent
f8122977a7
commit
3251b7e234
5 changed files with 241 additions and 5 deletions
|
@ -1370,6 +1370,12 @@ module ts {
|
|||
node.parent.kind === SyntaxKind.IndexedAccess && (<IndexedAccess>node.parent).index === node;
|
||||
}
|
||||
|
||||
enum SearchMeaning {
|
||||
Value = 0x1,
|
||||
Type = 0x2,
|
||||
Namespace = 0x4
|
||||
}
|
||||
|
||||
// A cache of completion entries for keywords, these do not change between sessions
|
||||
var keywordCompletions:CompletionEntry[] = [];
|
||||
for (var i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) {
|
||||
|
@ -2208,11 +2214,14 @@ module ts {
|
|||
|
||||
var result: ReferenceEntry[];
|
||||
|
||||
// Compute the meaning from the location and the symbol it references
|
||||
var searchMeaning = getIntersectingMeaningFromDeclaration(getMeaningFromLocation(node), symbol.getDeclarations());
|
||||
|
||||
var scope = getSymbolScope(symbol, node);
|
||||
|
||||
if (scope) {
|
||||
result = [];
|
||||
getReferencesInNode(scope, symbol, node, result);
|
||||
getReferencesInNode(scope, symbol, node, searchMeaning, result);
|
||||
}
|
||||
else {
|
||||
var symbolName = symbol.getName();
|
||||
|
@ -2222,7 +2231,7 @@ module ts {
|
|||
|
||||
if (sourceFile.getBloomFilter().probablyContains(symbolName)) {
|
||||
if (!result) result = [];
|
||||
getReferencesInNode(sourceFile, symbol, node, result);
|
||||
getReferencesInNode(sourceFile, symbol, node, searchMeaning, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2314,7 +2323,7 @@ module ts {
|
|||
return false;
|
||||
}
|
||||
|
||||
function getReferencesInNode(container: Node, searchSymbol: Symbol, searchLocation: Node, result: ReferenceEntry[]): void {
|
||||
function getReferencesInNode(container: Node, searchSymbol: Symbol, searchLocation: Node, searchMeaning: SearchMeaning, result: ReferenceEntry[]): void {
|
||||
var searchSymbolName = searchSymbol.getName();
|
||||
|
||||
var sourceFile = container.getSourceFile();
|
||||
|
@ -2332,6 +2341,10 @@ module ts {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!(getMeaningFromLocation(referenceLocation) & searchMeaning)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var referenceSymbol = typeInfoResolver.getSymbolInfo(referenceLocation);
|
||||
|
||||
// Could not find a symbol e.g. node is string or number keyword,
|
||||
|
@ -2452,6 +2465,120 @@ module ts {
|
|||
};
|
||||
}
|
||||
|
||||
function getMeaningFromDeclaration(node: Declaration): SearchMeaning {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.Parameter:
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
case SyntaxKind.Property:
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
case SyntaxKind.EnumMember:
|
||||
case SyntaxKind.Method:
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.CatchBlock:
|
||||
return SearchMeaning.Value;
|
||||
|
||||
case SyntaxKind.TypeParameter:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.TypeLiteral:
|
||||
return SearchMeaning.Type;
|
||||
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
return SearchMeaning.Value | SearchMeaning.Type;
|
||||
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
if ((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral) {
|
||||
return SearchMeaning.Namespace | SearchMeaning.Value;
|
||||
}
|
||||
else if (isInstantiated(node)) {
|
||||
return SearchMeaning.Namespace | SearchMeaning.Value;
|
||||
}
|
||||
else {
|
||||
return SearchMeaning.Namespace;
|
||||
}
|
||||
break;
|
||||
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
return SearchMeaning.Value | SearchMeaning.Type | SearchMeaning.Namespace;
|
||||
}
|
||||
Debug.fail("Unkown declaration type");
|
||||
}
|
||||
|
||||
function isTypeReference(node: Node): boolean {
|
||||
if (node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node)
|
||||
node = node.parent;
|
||||
|
||||
return node.parent.kind === SyntaxKind.TypeReference;
|
||||
}
|
||||
|
||||
function isNamespaceReference(node: Node): boolean {
|
||||
var root = node;
|
||||
var isLastClause = true;
|
||||
if (root.parent.kind === SyntaxKind.QualifiedName) {
|
||||
while (root.parent && root.parent.kind === SyntaxKind.QualifiedName)
|
||||
root = root.parent;
|
||||
|
||||
isLastClause = (<QualifiedName>root).right === node;
|
||||
}
|
||||
|
||||
return root.parent.kind === SyntaxKind.TypeReference && !isLastClause;
|
||||
}
|
||||
|
||||
function getMeaningFromLocation(node: Node): SearchMeaning {
|
||||
if (isDeclarationIdentifier(<Identifier>node)) {
|
||||
return getMeaningFromDeclaration(node.parent);
|
||||
}
|
||||
else if (isTypeReference(node)) {
|
||||
return SearchMeaning.Type;
|
||||
}
|
||||
else if (isNamespaceReference(node)) {
|
||||
return SearchMeaning.Namespace;
|
||||
}
|
||||
else {
|
||||
return SearchMeaning.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations
|
||||
/// of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class
|
||||
/// then we need to widen the search to include type positions as well.
|
||||
/// On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated
|
||||
/// module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module)
|
||||
/// do not intersect in any of the three spaces.
|
||||
function getIntersectingMeaningFromDeclaration(initialMeaning: SearchMeaning, declarations: Declaration[]): SearchMeaning {
|
||||
var meaning = initialMeaning;
|
||||
var incomplete = true;
|
||||
if (declarations) {
|
||||
do {
|
||||
// The result is order-sensitive, for instance if initialMeaning === Namespace, and declarations = [class, instantiated module]
|
||||
// we need to consider both as they initialMeaning intersects with the module in the namespace space, and the module
|
||||
// intersects with the class in the value space.
|
||||
// To achieve that we will keep iterating until the result stabilizes.
|
||||
|
||||
// Remeber the last meaning
|
||||
var lastIterationMeaning = meaning;
|
||||
|
||||
for (var i = 0, n = declarations.length; i < n; i++) {
|
||||
var declarationMeaning = getMeaningFromDeclaration(declarations[i]);
|
||||
|
||||
if (declarationMeaning & meaning) {
|
||||
searchMeaning |= declarationMeaning;
|
||||
}
|
||||
}
|
||||
|
||||
// meaning becomes the old one
|
||||
meaning = searchMeaning;
|
||||
|
||||
} while (meaning !== lastIterationMeaning);
|
||||
}
|
||||
return meaning;
|
||||
}
|
||||
|
||||
/// A node is considedered a writeAccess iff it is a name of a declaration or a target of an assignment
|
||||
function isWriteAccess(node: Node): boolean {
|
||||
if (node.kind === SyntaxKind.Identifier && isDeclarationIdentifier(<Identifier>node)) {
|
||||
|
@ -2487,7 +2614,6 @@ module ts {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Syntactic features
|
||||
function getSyntaxTree(filename: string): TypeScript.SyntaxTree {
|
||||
filename = TypeScript.switchToForwardSlashes(filename);
|
||||
|
|
21
tests/cases/fourslash/referencesForMergedDeclarations.ts
Normal file
21
tests/cases/fourslash/referencesForMergedDeclarations.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/// <reference path='fourslash.ts'/>
|
||||
|
||||
////interface /*type1*/Foo {
|
||||
////}
|
||||
////
|
||||
////module /*namespace1*/Foo {
|
||||
//// export interface Bar { }
|
||||
////}
|
||||
////
|
||||
////function /*value1*/Foo(): void {
|
||||
////}
|
||||
////
|
||||
////var f1: /*namespace2*/Foo.Bar;
|
||||
////var f2: /*type2*/Foo;
|
||||
/////*value2*/Foo.bind(this);
|
||||
|
||||
|
||||
test.markers().forEach(m => {
|
||||
goTo.position(m.position, m.fileName);
|
||||
verify.referencesCountIs(2);
|
||||
});
|
21
tests/cases/fourslash/referencesForMergedDeclarations2.ts
Normal file
21
tests/cases/fourslash/referencesForMergedDeclarations2.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/// <reference path='fourslash.ts'/>
|
||||
|
||||
////module ATest {
|
||||
//// export interface Bar { }
|
||||
////}
|
||||
////
|
||||
////function ATest() { }
|
||||
////
|
||||
////import /*definition*/alias = ATest;
|
||||
////
|
||||
////var a: /*namespace*/alias.Bar;
|
||||
/////*value*/alias.call(this);
|
||||
|
||||
goTo.marker("definition");
|
||||
verify.referencesCountIs(3);
|
||||
|
||||
goTo.marker("namespace");
|
||||
verify.referencesCountIs(3);
|
||||
|
||||
goTo.marker("value");
|
||||
verify.referencesCountIs(3);
|
39
tests/cases/fourslash/referencesForMergedDeclarations3.ts
Normal file
39
tests/cases/fourslash/referencesForMergedDeclarations3.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/// <reference path='fourslash.ts'/>
|
||||
|
||||
// class and uninstanciated module
|
||||
|
||||
////class testClass {
|
||||
//// static staticMethod() { }
|
||||
//// method() { }
|
||||
////}
|
||||
////
|
||||
////module testClass {
|
||||
//// export interface Bar {
|
||||
////
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
////var c1: /*class1*/testClass;
|
||||
////var c2: /*module*/testClass.Bar;
|
||||
/////*class2*/testClass.staticMethod();
|
||||
/////*class3*/testClass.prototype.method();
|
||||
/////*class4*/testClass.bind(this);
|
||||
////new /*class5*/testClass();
|
||||
|
||||
goTo.marker("module");
|
||||
verify.referencesCountIs(2);
|
||||
|
||||
goTo.marker("class1");
|
||||
verify.referencesCountIs(6);
|
||||
|
||||
goTo.marker("class2");
|
||||
verify.referencesCountIs(6);
|
||||
|
||||
goTo.marker("class3");
|
||||
verify.referencesCountIs(6);
|
||||
|
||||
goTo.marker("class4");
|
||||
verify.referencesCountIs(6);
|
||||
|
||||
goTo.marker("class5");
|
||||
verify.referencesCountIs(6);
|
29
tests/cases/fourslash/referencesForMergedDeclarations4.ts
Normal file
29
tests/cases/fourslash/referencesForMergedDeclarations4.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/// <reference path='fourslash.ts'/>
|
||||
|
||||
// class and instanciated module
|
||||
|
||||
////class testClass {
|
||||
//// static staticMethod() { }
|
||||
//// method() { }
|
||||
////}
|
||||
////
|
||||
////module testClass {
|
||||
//// export interface Bar {
|
||||
////
|
||||
//// }
|
||||
//// export var s = 0;
|
||||
////}
|
||||
////
|
||||
////var c1: /*1*/testClass;
|
||||
////var c2: /*2*/testClass.Bar;
|
||||
/////*3*/testClass.staticMethod();
|
||||
/////*4*/testClass.prototype.method();
|
||||
/////*5*/testClass.bind(this);
|
||||
/////*6*/testClass.s;
|
||||
////new /*7*/testClass();
|
||||
|
||||
// Instanciated Module and class intersect in the value space, so we consider them all one group
|
||||
test.markers().forEach(m => {
|
||||
goTo.position(m.position, m.fileName);
|
||||
verify.referencesCountIs(9);
|
||||
});
|
Loading…
Reference in a new issue