diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index 778a007ded5..ff2b819ec26 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -17,7 +17,7 @@ namespace ts.DocumentHighlights { } function getSemanticDocumentHighlights(node: Node, typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFilesToSearch: SourceFile[]): DocumentHighlights[] { - const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFilesToSearch, /*findInStrings*/false, /*findInComments*/false, /*implementations*/false); + const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFilesToSearch); return referencedSymbols && convertReferencedSymbols(referencedSymbols); } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 83006c66d77..df568f6df3e 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1,11 +1,11 @@ /* @internal */ namespace ts.FindAllReferences { - export function findReferencedSymbols(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, findInStrings: boolean, findInComments: boolean): ReferencedSymbol[] | undefined { + export function findReferencedSymbols(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, findInStrings: boolean, findInComments: boolean, isForRename: boolean): ReferencedSymbol[] | undefined { const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); - return getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFiles, findInStrings, findInComments, /*implementations*/false); + return getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFiles, findInStrings, findInComments, isForRename); } - export function getReferencedSymbolsForNode(typeChecker: TypeChecker, cancellationToken: CancellationToken, node: Node, sourceFiles: SourceFile[], findInStrings: boolean, findInComments: boolean, implementations: boolean): ReferencedSymbol[] | undefined { + export function getReferencedSymbolsForNode(typeChecker: TypeChecker, cancellationToken: CancellationToken, node: Node, sourceFiles: SourceFile[], findInStrings?: boolean, findInComments?: boolean, isForRename?: boolean, implementations?: boolean): ReferencedSymbol[] | undefined { if (!implementations) { const special = getReferencedSymbolsSpecial(node, sourceFiles, typeChecker, cancellationToken); if (special) { @@ -33,11 +33,16 @@ namespace ts.FindAllReferences { return undefined; } - const aliasedSymbol = followAliasIfNecessary(symbol, node, typeChecker); - const isShorthandModule = ts.isShorthandAmbientModuleSymbol(aliasedSymbol); - // Don't follow alias for shorthand modules because we lose information that way. - if (!isShorthandModule) { - symbol = aliasedSymbol; + const { symbol: aliasedSymbol, shorthandModuleSymbol } = followAliases(symbol, node, typeChecker, isForRename); + symbol = aliasedSymbol; + + // Build the set of symbols to search for, initially it has only the current symbol + const searchSymbols = populateSearchSymbolSet(symbol, node, typeChecker, implementations); + if (shorthandModuleSymbol) { + searchSymbols.push(shorthandModuleSymbol); + } + function isSearchedFor(symbol: Symbol): boolean { + return contains(searchSymbols, symbol); } // Compute the meaning from the location and the symbol it references @@ -48,12 +53,6 @@ namespace ts.FindAllReferences { const symbolToIndex: number[] = []; const inheritsFromCache: Map = createMap(); - // Build the set of symbols to search for, initially it has only the current symbol - const searchSymbols = populateSearchSymbolSet(symbol, node, typeChecker, implementations, isShorthandModule ? aliasedSymbol : undefined); - function isSearchedFor(symbol: Symbol): boolean { - return contains(searchSymbols, symbol); - } - // Get the text to search for. // Note: if this is an external module symbol, the name doesn't include quotes. const declaredName = stripQuotes(getDeclaredName(typeChecker, symbol, node)); @@ -116,6 +115,31 @@ namespace ts.FindAllReferences { return undefined; } + /** + * Follows aliases to get to the original declaration of a symbol. + * For a shorthand ambient module, we don't follow the alias to it, but we will need to add it to the set of search symbols. + */ + function followAliases(symbol: Symbol, node: Node, typeChecker: TypeChecker, isForRename: boolean): { symbol: Symbol, shorthandModuleSymbol?: Symbol } { + while (true) { + // When renaming a default import, only rename in the current file + if (isForRename && isImportDefaultSymbol(symbol)) { + return { symbol }; + } + + const aliasedSymbol = getAliasSymbolForPropertyNameSymbol(symbol, node, typeChecker); + // Don't follow alias if it goes to unknown symbol. This can happen if it points to an untyped module. + if (!aliasedSymbol || !aliasedSymbol.declarations) { + return { symbol }; + } + + if (ts.isShorthandAmbientModuleSymbol(aliasedSymbol)) { + return { symbol, shorthandModuleSymbol: aliasedSymbol }; + } + + symbol = aliasedSymbol; + } + } + function sourceFileHasName(sourceFile: SourceFile, name: string): boolean { return getNameTable(sourceFile).get(name) !== undefined; } @@ -157,29 +181,30 @@ namespace ts.FindAllReferences { } function getAliasSymbolForPropertyNameSymbol(symbol: Symbol, location: Node, typeChecker: TypeChecker): Symbol | undefined { - if (symbol.flags & SymbolFlags.Alias) { - // Default import get alias - const defaultImport = getDeclarationOfKind(symbol, SyntaxKind.ImportClause); - if (defaultImport) { - return typeChecker.getAliasedSymbol(symbol); - } - - const importOrExportSpecifier = forEach(symbol.declarations, - declaration => (declaration.kind === SyntaxKind.ImportSpecifier || - declaration.kind === SyntaxKind.ExportSpecifier) ? declaration : undefined); - if (importOrExportSpecifier && - // export { a } - (!importOrExportSpecifier.propertyName || - // export {a as class } where a is location - importOrExportSpecifier.propertyName === location)) { - // If Import specifier -> get alias - // else Export specifier -> get local target - return importOrExportSpecifier.kind === SyntaxKind.ImportSpecifier ? - typeChecker.getAliasedSymbol(symbol) : - typeChecker.getExportSpecifierLocalTargetSymbol(importOrExportSpecifier); - } + if (!(symbol.flags & SymbolFlags.Alias)) { + return undefined; + } + + // Default import get alias + const defaultImport = getDeclarationOfKind(symbol, SyntaxKind.ImportClause); + if (defaultImport) { + return typeChecker.getAliasedSymbol(symbol); + } + + const importOrExportSpecifier = forEach(symbol.declarations, + declaration => (declaration.kind === SyntaxKind.ImportSpecifier || + declaration.kind === SyntaxKind.ExportSpecifier) ? declaration : undefined); + if (importOrExportSpecifier && + // export { a } + (!importOrExportSpecifier.propertyName || + // export {a as class } where a is location + importOrExportSpecifier.propertyName === location)) { + // If Import specifier -> get alias + // else Export specifier -> get local target + return importOrExportSpecifier.kind === SyntaxKind.ImportSpecifier ? + typeChecker.getAliasedSymbol(symbol) : + typeChecker.getExportSpecifierLocalTargetSymbol(importOrExportSpecifier); } - return undefined; } function followAliasIfNecessary(symbol: Symbol, location: Node, typeChecker: TypeChecker): Symbol { @@ -1022,9 +1047,9 @@ namespace ts.FindAllReferences { } } - function populateSearchSymbolSet(symbol: Symbol, location: Node, typeChecker: TypeChecker, implementations: boolean, aliasSymbol?: Symbol): Symbol[] { + function populateSearchSymbolSet(symbol: Symbol, location: Node, typeChecker: TypeChecker, implementations: boolean): Symbol[] { // The search set contains at least the current symbol - let result = [symbol]; + const result = [symbol]; // If the location is name of property symbol from object literal destructuring pattern // Search the property symbol @@ -1037,10 +1062,6 @@ namespace ts.FindAllReferences { } } - if (aliasSymbol) { - result = result.concat(populateSearchSymbolSet(aliasSymbol, location, typeChecker, implementations)); - } - // 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 @@ -1072,7 +1093,7 @@ namespace ts.FindAllReferences { // Property Declaration symbol is a member of the class, so the symbol is stored in its class Declaration.symbol.members if (symbol.valueDeclaration && symbol.valueDeclaration.kind === SyntaxKind.Parameter && isParameterPropertyDeclaration(symbol.valueDeclaration)) { - result = result.concat(typeChecker.getSymbolsOfParameterPropertyDeclaration(symbol.valueDeclaration, symbol.name)); + addRange(result, typeChecker.getSymbolsOfParameterPropertyDeclaration(symbol.valueDeclaration, symbol.name)); } // If this is symbol of binding element without propertyName declaration in Object binding pattern @@ -1447,4 +1468,8 @@ namespace ts.FindAllReferences { return false; } + + function isImportDefaultSymbol(symbol: Symbol): boolean { + return symbol.declarations[0].kind === SyntaxKind.ImportClause; + } } diff --git a/src/services/goToImplementation.ts b/src/services/goToImplementation.ts index 123d29630ad..9135e1fe0f0 100644 --- a/src/services/goToImplementation.ts +++ b/src/services/goToImplementation.ts @@ -17,7 +17,7 @@ namespace ts.GoToImplementation { else { // Perform "Find all References" and retrieve only those that are implementations const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, - node, sourceFiles, /*findInStrings*/false, /*findInComments*/false, /*implementations*/true); + node, sourceFiles, /*findInStrings*/false, /*findInComments*/false, /*isForRename*/false, /*implementations*/true); const result = flatMap(referencedSymbols, symbol => map(symbol.references, ({ textSpan, fileName }) => ({ textSpan, fileName }))); diff --git a/src/services/services.ts b/src/services/services.ts index cb38b1d500e..00a8c9a9f2e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1406,25 +1406,25 @@ namespace ts { } function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] { - const referencedSymbols = findReferencedSymbols(fileName, position, findInStrings, findInComments); + const referencedSymbols = findReferencedSymbols(fileName, position, findInStrings, findInComments, /*isForRename*/true); return FindAllReferences.convertReferences(referencedSymbols); } function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] { - const referencedSymbols = findReferencedSymbols(fileName, position, /*findInStrings*/ false, /*findInComments*/ false); + const referencedSymbols = findReferencedSymbols(fileName, position, /*findInStrings*/ false, /*findInComments*/ false, /*isForRename*/false); return FindAllReferences.convertReferences(referencedSymbols); } function findReferences(fileName: string, position: number): ReferencedSymbol[] { - const referencedSymbols = findReferencedSymbols(fileName, position, /*findInStrings*/ false, /*findInComments*/ false); + const referencedSymbols = findReferencedSymbols(fileName, position, /*findInStrings*/ false, /*findInComments*/ false, /*isForRename*/false); // Only include referenced symbols that have a valid definition. return filter(referencedSymbols, rs => !!rs.definition); } - function findReferencedSymbols(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): ReferencedSymbol[] { + function findReferencedSymbols(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, isForRename: boolean): ReferencedSymbol[] { synchronizeHostData(); - return FindAllReferences.findReferencedSymbols(program.getTypeChecker(), cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position, findInStrings, findInComments); + return FindAllReferences.findReferencedSymbols(program.getTypeChecker(), cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position, findInStrings, findInComments, isForRename); } /// NavigateTo diff --git a/tests/cases/fourslash/renameDefaultImport.ts b/tests/cases/fourslash/renameDefaultImport.ts index 8d9b65d78d6..ff297ffa147 100644 --- a/tests/cases/fourslash/renameDefaultImport.ts +++ b/tests/cases/fourslash/renameDefaultImport.ts @@ -1,7 +1,7 @@ /// // @Filename: B.ts -////export default class [|B|] { +////export default class /*1*/[|B|] { //// test() { //// } ////} @@ -11,4 +11,17 @@ ////let b = new [|B|](); ////b.test(); -verify.rangesAreRenameLocations(); +goTo.marker("1"); +verify.occurrencesAtPositionCount(1); + +const [C, B0, B1] = test.ranges(); +verify.rangesReferenceEachOther(); + +goTo.rangeStart(C); +verify.renameLocations(false, false, [C, B0, B1]); + +const rangesInB = [B0, B1]; +for (const r of rangesInB) { + goTo.rangeStart(r); + verify.renameLocations(false, false, rangesInB); +} diff --git a/tests/cases/fourslash/renameDefaultImportDifferentName.ts b/tests/cases/fourslash/renameDefaultImportDifferentName.ts index aec699aadda..be968c9b883 100644 --- a/tests/cases/fourslash/renameDefaultImportDifferentName.ts +++ b/tests/cases/fourslash/renameDefaultImportDifferentName.ts @@ -1,7 +1,7 @@ /// // @Filename: B.ts -////export default class /*1*/C { +////export default class /*1*/[|C|] { //// test() { //// } ////} @@ -14,4 +14,14 @@ goTo.marker("1"); verify.occurrencesAtPositionCount(1); -verify.rangesAreRenameLocations(); +const [C, B0, B1] = test.ranges(); +verify.rangesReferenceEachOther(); + +goTo.rangeStart(C); +verify.renameLocations(false, false, [C, B0, B1]); + +const rangesInB = [B0, B1]; +for (const r of rangesInB) { + goTo.rangeStart(r); + verify.renameLocations(false, false, rangesInB); +} diff --git a/tests/cases/fourslash/renameImportAndExport.ts b/tests/cases/fourslash/renameImportAndExport.ts index fe8bce4b208..4545277c564 100644 --- a/tests/cases/fourslash/renameImportAndExport.ts +++ b/tests/cases/fourslash/renameImportAndExport.ts @@ -3,4 +3,6 @@ ////import [|a|] from "module"; ////export { [|a|] }; -verify.rangesAreRenameLocations(); +const [r0, r1] = test.ranges(); +verify.referencesOf(r1, [r0, r1]); +//verify.rangesAreRenameLocations();