diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e73bbea8fa..281468d842 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -83,6 +83,7 @@ namespace ts { getSymbolsInScope, getSymbolAtLocation, getShorthandAssignmentValueSymbol, + getExportSpecifierLocalTargetSymbol, getTypeAtLocation: getTypeOfNode, typeToString, getSymbolDisplayBuilder, @@ -15062,11 +15063,18 @@ namespace ts { // This is necessary as an identifier in short-hand property assignment can contains two meaning: // property name and property value. if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) { - return resolveEntityName((location).name, SymbolFlags.Value); + return resolveEntityName((location).name, SymbolFlags.Value | SymbolFlags.Alias); } return undefined; } + /** Returns the target of an export specifier without following aliases */ + function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier): Symbol { + return (node.parent.parent).moduleSpecifier ? + getExternalModuleMember(node.parent.parent, node) : + resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); + } + function getTypeOfNode(node: Node): Type { if (isInsideWithStatementBody(node)) { // We cannot answer semantic questions within a with block, do not proceed any further diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9c5690901b..ce4ffe05f9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1725,6 +1725,7 @@ namespace ts { getSymbolAtLocation(node: Node): Symbol; getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): Symbol[]; getShorthandAssignmentValueSymbol(location: Node): Symbol; + getExportSpecifierLocalTargetSymbol(location: ExportSpecifier): Symbol; getTypeAtLocation(node: Node): Type; typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string; diff --git a/src/services/services.ts b/src/services/services.ts index 0086519f90..09c666015c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -5517,10 +5517,8 @@ namespace ts { }; } - function isImportOrExportSpecifierImportSymbol(symbol: Symbol) { - return (symbol.flags & SymbolFlags.Alias) && forEach(symbol.declarations, declaration => { - return declaration.kind === SyntaxKind.ImportSpecifier || declaration.kind === SyntaxKind.ExportSpecifier; - }); + function isImportSpecifierSymbol(symbol: Symbol) { + return (symbol.flags & SymbolFlags.Alias) && !!getDeclarationOfKind(symbol, SyntaxKind.ImportSpecifier); } function getInternedName(symbol: Symbol, location: Node, declarations: Declaration[]): string { @@ -5964,8 +5962,17 @@ namespace ts { let result = [symbol]; // If the symbol is an alias, add what it alaises to the list - if (isImportOrExportSpecifierImportSymbol(symbol)) { - result.push(typeChecker.getAliasedSymbol(symbol)); + if (isImportSpecifierSymbol(symbol)) { + result.push(typeChecker.getAliasedSymbol(symbol)); + } + + // For export specifiers, the exported name can be refering to a local symbol, e.g.: + // import {a} from "mod"; + // export {a as somethingElse} + // We want the *local* declaration of 'a' as declared in the import, + // *not* as declared within "mod" (or farther) + if (location.parent.kind === SyntaxKind.ExportSpecifier) { + result.push(typeChecker.getExportSpecifierLocalTargetSymbol(location.parent)); } // If the location is in a context sensitive location (i.e. in an object literal) try @@ -6055,13 +6062,24 @@ namespace ts { // If the reference symbol is an alias, check if what it is aliasing is one of the search // symbols. - if (isImportOrExportSpecifierImportSymbol(referenceSymbol)) { + if (isImportSpecifierSymbol(referenceSymbol)) { const aliasedSymbol = typeChecker.getAliasedSymbol(referenceSymbol); if (searchSymbols.indexOf(aliasedSymbol) >= 0) { return aliasedSymbol; } } + // For export specifiers, it can be a local symbol, e.g. + // import {a} from "mod"; + // export {a as somethingElse} + // We want the local target of the export (i.e. the import symbol) and not the final target (i.e. "mod".a) + if (referenceLocation.parent.kind === SyntaxKind.ExportSpecifier) { + const aliasedSymbol = typeChecker.getExportSpecifierLocalTargetSymbol(referenceLocation.parent); + if (searchSymbols.indexOf(aliasedSymbol) >= 0) { + return aliasedSymbol; + } + } + // 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/renameImportAndExport.ts b/tests/cases/fourslash/renameImportAndExport.ts new file mode 100644 index 0000000000..495e15c1e7 --- /dev/null +++ b/tests/cases/fourslash/renameImportAndExport.ts @@ -0,0 +1,10 @@ +/// + +////import [|a|] from "module"; +////export { [|a|] }; + +let ranges = test.ranges() +for (let range of ranges) { + goTo.position(range.start); + verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); +} diff --git a/tests/cases/fourslash/renameImportAndShorthand.ts b/tests/cases/fourslash/renameImportAndShorthand.ts new file mode 100644 index 0000000000..bc4746aebd --- /dev/null +++ b/tests/cases/fourslash/renameImportAndShorthand.ts @@ -0,0 +1,10 @@ +/// + +////import [|foo|] from 'bar'; +////const bar = { [|foo|] }; + +let ranges = test.ranges() +for (let range of ranges) { + goTo.position(range.start); + verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); +} diff --git a/tests/cases/fourslash/renameImportNamespaceAndShorthand.ts b/tests/cases/fourslash/renameImportNamespaceAndShorthand.ts new file mode 100644 index 0000000000..a6b06c1140 --- /dev/null +++ b/tests/cases/fourslash/renameImportNamespaceAndShorthand.ts @@ -0,0 +1,10 @@ +/// + +////import * as [|foo|] from 'bar'; +////const bar = { [|foo|] }; + +let ranges = test.ranges() +for (let range of ranges) { + goTo.position(range.start); + verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); +} diff --git a/tests/cases/fourslash/renameImportOfExportEquals.ts b/tests/cases/fourslash/renameImportOfExportEquals.ts new file mode 100644 index 0000000000..9d71ee907c --- /dev/null +++ b/tests/cases/fourslash/renameImportOfExportEquals.ts @@ -0,0 +1,18 @@ +/// + +////declare namespace N { +//// export var x: number; +////} +////declare module "mod" { +//// export = N; +////} +////declare module "test" { +//// import * as [|N|] from "mod"; +//// export { [|N|] }; // Renaming N here would rename +////} + +let ranges = test.ranges() +for (let range of ranges) { + goTo.position(range.start); + verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); +} diff --git a/tests/cases/fourslash/renameImportRequire.ts b/tests/cases/fourslash/renameImportRequire.ts new file mode 100644 index 0000000000..c26cc80b61 --- /dev/null +++ b/tests/cases/fourslash/renameImportRequire.ts @@ -0,0 +1,12 @@ +/// + +////import [|e|] = require("mod4"); +////[|e|]; +////a = { [|e|] }; +////export { [|e|] }; + +let ranges = test.ranges() +for (let range of ranges) { + goTo.position(range.start); + verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); +}