From e93748ac58eaea3995260af25b0562eaad68af0e Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 24 Feb 2015 01:19:48 -0800 Subject: [PATCH] Support find references on the new import/export syntax --- src/services/services.ts | 68 +++++++++++++++++-- .../fourslash/findAllRefsOnImportAliases.ts | 29 ++++++++ .../fourslash/findAllRefsOnImportAliases2.ts | 31 +++++++++ 3 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 tests/cases/fourslash/findAllRefsOnImportAliases.ts create mode 100644 tests/cases/fourslash/findAllRefsOnImportAliases2.ts diff --git a/src/services/services.ts b/src/services/services.ts index db6a09c1a71..708869fddf5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3986,7 +3986,7 @@ module ts { var searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), declarations); // Get the text to search for, we need to normalize it as external module names will have quote - var declaredName = getDeclaredName(symbol); + var declaredName = getDeclaredName(symbol, node); // Try to get the smallest valid scope that we can limit our search to; // otherwise we'll need to search globally (i.e. include each file). @@ -4003,7 +4003,7 @@ module ts { getReferencesInNode(sourceFiles[0], symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result); } else { - var internedName = getInternedName(symbol, declarations) + var internedName = getInternedName(symbol, node, declarations) forEach(sourceFiles, sourceFile => { cancellationToken.throwIfCancellationRequested(); @@ -4023,13 +4023,51 @@ module ts { return result; - function getDeclaredName(symbol: Symbol) { + function isImportOrExportSpecifierName(location: Node): boolean { + return location.parent && + (location.parent.kind === SyntaxKind.ImportSpecifier || location.parent.kind === SyntaxKind.ExportSpecifier) && + (location.parent).propertyName === location; + } + + function isImportOrExportSpecifierImportSymbol(symbol: Symbol) { + return (symbol.flags & SymbolFlags.Import) && forEach(symbol.declarations, declaration => { + return declaration.kind === SyntaxKind.ImportSpecifier || declaration.kind === SyntaxKind.ExportSpecifier; + }); + } + + function getDeclaredName(symbol: Symbol, location: Node) { + // Special case for function expressions, whose names are solely local to their bodies. + var functionExpression = forEach(symbol.declarations, d => d.kind === SyntaxKind.FunctionExpression ? d : undefined); + + // When a name gets interned into a SourceFile's 'identifiers' Map, + // its name is escaped and stored in the same way its symbol name/identifier + // name should be stored. Function expressions, however, are a special case, + // because despite sometimes having a name, the binder unconditionally binds them + // to a symbol with the name "__function". + if (functionExpression && functionExpression.name) { + var name = functionExpression.name.text; + } + + // If this is an export or import specifier it could have been renamed using the as syntax. + // if so we want to search for whatever under the cursor, the symbol is pointing to the alias (name) + // so check for the propertyName. + if (isImportOrExportSpecifierName(location)) { + return location.getText(); + } + var name = typeInfoResolver.symbolToString(symbol); return stripQuotes(name); } - function getInternedName(symbol: Symbol, declarations: Declaration[]): string { + function getInternedName(symbol: Symbol, location: Node, declarations: Declaration[]): string { + // If this is an export or import specifier it could have been renamed using the as syntax. + // if so we want to search for whatever under the cursor, the symbol is pointing to the alias (name) + // so check for the propertyName. + if (isImportOrExportSpecifierName(location)) { + return location.getText(); + } + // Special case for function expressions, whose names are solely local to their bodies. var functionExpression = forEach(declarations, d => d.kind === SyntaxKind.FunctionExpression ? d : undefined); @@ -4058,16 +4096,22 @@ module ts { function getSymbolScope(symbol: Symbol): Node { // If this is private property or method, the scope is the containing class - if (symbol.getFlags() && (SymbolFlags.Property | SymbolFlags.Method)) { + if (symbol.flags & (SymbolFlags.Property | SymbolFlags.Method)) { var privateDeclaration = forEach(symbol.getDeclarations(), d => (d.flags & NodeFlags.Private) ? d : undefined); if (privateDeclaration) { return getAncestor(privateDeclaration, SyntaxKind.ClassDeclaration); } } + // If the symbol is an import we would like to find it if we are looking for what it imports. + // So consider it visibile outside its declaration scope. + if (symbol.flags & SymbolFlags.Import) { + return undefined; + } + // if this symbol is visible from its parent container, e.g. exported, then bail out // if symbol correspond to the union property - bail out - if (symbol.parent || (symbol.getFlags() & SymbolFlags.UnionProperty)) { + if (symbol.parent || (symbol.flags & SymbolFlags.UnionProperty)) { return undefined; } @@ -4422,6 +4466,11 @@ module ts { // The search set contains at least the current symbol var result = [symbol]; + // If the symbol is an alias, add what it alaises to the list + if (isImportOrExportSpecifierImportSymbol(symbol)) { + result.push(typeInfoResolver.getAliasedSymbol(symbol)); + } + // If the location is in a context sensitive location (i.e. in an object literal) try // to get a contextual type for it, and add the property symbol from the contextual // type to the search set @@ -4498,6 +4547,13 @@ module ts { return true; } + // If the reference symbol is an alias, check if what it is aliasing is one of the search + // symbols. + if (isImportOrExportSpecifierImportSymbol(referenceSymbol) && + searchSymbols.indexOf(typeInfoResolver.getAliasedSymbol(referenceSymbol)) >= 0) { + return true; + } + // If the reference location is in an object literal, try to get the contextual type for the // object literal, lookup the property symbol in the contextual type, and use this symbol to // compare to our searchSymbol diff --git a/tests/cases/fourslash/findAllRefsOnImportAliases.ts b/tests/cases/fourslash/findAllRefsOnImportAliases.ts new file mode 100644 index 00000000000..dbfab33aa86 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsOnImportAliases.ts @@ -0,0 +1,29 @@ +/// + +//@Filename: a.ts +////export class /*1*/Class{ +////} + +//@Filename: b.ts +////import { /*2*/Class } from "a"; +//// +////var c = new /*3*/Class(); + +//@Filename: c.ts +////export { /*4*/Class } from "a"; + +goTo.file("a.ts"); +goTo.marker("1"); +verify.referencesCountIs(4); + +goTo.file("b.ts"); +goTo.marker("2"); +verify.referencesCountIs(4); + +goTo.marker("3"); +verify.referencesCountIs(4); + +goTo.file("c.ts"); +goTo.marker("4"); +verify.referencesCountIs(4); + diff --git a/tests/cases/fourslash/findAllRefsOnImportAliases2.ts b/tests/cases/fourslash/findAllRefsOnImportAliases2.ts new file mode 100644 index 00000000000..dca6b13bbd2 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsOnImportAliases2.ts @@ -0,0 +1,31 @@ +/// + +//@Filename: a.ts +////export class /*1*/Class{ +////} + +//@Filename: b.ts +////import { /*2*/Class as /*3*/C2} from "a"; +//// +////var c = new C2(); + +//@Filename: c.ts +////export { /*4*/Class as /*5*/C3 } from "a"; + +goTo.file("a.ts"); +goTo.marker("1"); +verify.referencesCountIs(3); + +goTo.file("b.ts"); +goTo.marker("2"); +verify.referencesCountIs(3); + +goTo.marker("3"); +verify.referencesCountIs(2); + +goTo.file("c.ts"); +goTo.marker("4"); +verify.referencesCountIs(3); + +goTo.marker("5"); +verify.referencesCountIs(1);