diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b7cc0f0de6de3..5c63d8bb60c34 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -395,7 +395,7 @@ namespace ts { function isShorthandAmbientModule(node: Node): boolean { // The only kind of module that can be missing a body is a shorthand ambient module. - return node.kind === SyntaxKind.ModuleDeclaration && (!(node).body); + return node && node.kind === SyntaxKind.ModuleDeclaration && (!(node).body); } export function isBlockScopedContainerTopLevel(node: Node): boolean { @@ -3104,7 +3104,11 @@ namespace ts { } export function getLocalSymbolForExportDefault(symbol: Symbol) { - return symbol && symbol.valueDeclaration && hasModifier(symbol.valueDeclaration, ModifierFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined; + return isExportDefaultSymbol(symbol) ? symbol.valueDeclaration.localSymbol : undefined; + } + + export function isExportDefaultSymbol(symbol: Symbol): boolean { + return symbol && symbol.valueDeclaration && hasModifier(symbol.valueDeclaration, ModifierFlags.Default); } /** Return ".ts", ".d.ts", or ".tsx", if that is the extension. */ diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index 778a007ded58c..ff2b819ec265a 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 82a5da453b7db..28474839cf3c9 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1,49 +1,27 @@ /* @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) { - if (isTypeKeyword(node.kind)) { - return getAllReferencesForKeyword(sourceFiles, node.kind, cancellationToken); - } - - // Labels - if (isLabelName(node)) { - if (isJumpStatementTarget(node)) { - const labelDefinition = getTargetLabel((node.parent), (node).text); - // if we have a label definition, look within its statement for references, if not, then - // the label is undefined and we have no results.. - return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition, cancellationToken); - } - else { - // it is a label definition and not a target, search within the parent labeledStatement - return getLabelReferencesInNode(node.parent, node, cancellationToken); - } - } - - if (isThis(node)) { - return getReferencesForThisKeyword(node, sourceFiles, typeChecker, cancellationToken); - } - - if (node.kind === SyntaxKind.SuperKeyword) { - return getReferencesForSuperKeyword(node, typeChecker, cancellationToken); + const special = getReferencedSymbolsSpecial(node, sourceFiles, typeChecker, cancellationToken); + if (special) { + return special; } } // `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword, // so we have to specify that we want the constructor symbol. - const symbol = typeChecker.getSymbolAtLocation(node); - - if (!implementations && !symbol && node.kind === SyntaxKind.StringLiteral) { - return getReferencesForStringLiteral(node, sourceFiles, typeChecker, cancellationToken); - } + let symbol = typeChecker.getSymbolAtLocation(node); // Could not find a symbol e.g. unknown identifier if (!symbol) { + if (!implementations && node.kind === SyntaxKind.StringLiteral) { + return getReferencesForStringLiteral(node, sourceFiles, typeChecker, cancellationToken); + } // Can't have references to something that we have no symbol for. return undefined; } @@ -55,9 +33,23 @@ namespace ts.FindAllReferences { return undefined; } + 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); + } + // Compute the meaning from the location and the symbol it references const searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), declarations); + const result: ReferencedSymbol[] = []; + // Maps from a symbol ID to the ReferencedSymbol entry in 'result'. + const symbolToIndex: number[] = []; + const inheritsFromCache: Map = createMap(); + // 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)); @@ -65,33 +57,107 @@ namespace ts.FindAllReferences { // 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). const scope = getSymbolScope(symbol); - - // Maps from a symbol ID to the ReferencedSymbol entry in 'result'. - const symbolToIndex: number[] = []; - - let result: ReferencedSymbol[]; if (scope) { - result = []; - getReferencesInNode(scope, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex, implementations, typeChecker, cancellationToken); + getRefs(scope, declaredName); } else { - const internedName = getInternedName(symbol, node); + const isDefault = isExportDefaultSymbol(symbol); + const internedName = isDefault ? symbol.valueDeclaration.localSymbol.name : getInternedName(symbol, node); for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested(); - if (sourceFileHasName(sourceFile, internedName)) { - result = result || []; - getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex, implementations, typeChecker, cancellationToken); + const searchName = (isDefault ? getDefaultImportName(symbol, sourceFile, typeChecker) : undefined) || + (sourceFileHasName(sourceFile, internedName) ? declaredName : undefined); + if (searchName !== undefined) { + getRefs(sourceFile, searchName); } } } return result; + + function getRefs(scope: ts.Node, searchName: string): void { + getReferencesInNode(scope, symbol, searchName, node, searchMeaning, findInStrings, findInComments, result, + symbolToIndex, implementations, typeChecker, cancellationToken, searchSymbols, inheritsFromCache); + } + } + + /** getReferencedSymbols for special node kinds. */ + function getReferencedSymbolsSpecial(node: Node, sourceFiles: SourceFile[], typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] | undefined { + if (isTypeKeyword(node.kind)) { + return getAllReferencesForKeyword(sourceFiles, node.kind, cancellationToken); + } + + // Labels + if (isLabelName(node)) { + if (isJumpStatementTarget(node)) { + const labelDefinition = getTargetLabel((node.parent), (node).text); + // if we have a label definition, look within its statement for references, if not, then + // the label is undefined and we have no results.. + return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition, cancellationToken); + } + else { + // it is a label definition and not a target, search within the parent labeledStatement + return getLabelReferencesInNode(node.parent, node, cancellationToken); + } + } + + if (isThis(node)) { + return getReferencesForThisKeyword(node, sourceFiles, typeChecker, cancellationToken); + } + + if (node.kind === SyntaxKind.SuperKeyword) { + return getReferencesForSuperKeyword(node, typeChecker, cancellationToken); + } + + 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; } + /** + * Given a symbol, see if any of the imports in a source file reference it. + * Only call this if `symbol` is a default export. + */ + function getDefaultImportName(symbol: Symbol, sourceFile: SourceFile, checker: ts.TypeChecker): string | undefined { + for (const importSpecifier of sourceFile.imports) { + const importDecl = importSpecifier.parent as ts.ImportDeclaration; + Debug.assert(importDecl.moduleSpecifier === importSpecifier); + const defaultName = importDecl.importClause.name; + const defaultReferencedSymbol = checker.getAliasedSymbol(checker.getSymbolAtLocation(defaultName)); + if (symbol === defaultReferencedSymbol) { + return defaultName.text; + } + } + return undefined; + } + function getDefinition(symbol: Symbol, node: Node, typeChecker: TypeChecker): ReferencedSymbolDefinitionInfo { const { displayParts, symbolKind } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, node.getSourceFile(), getContainerNode(node), node); const name = displayParts.map(p => p.text).join(""); @@ -112,29 +178,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); - } + if (!(symbol.flags & SymbolFlags.Alias)) { + return undefined; + } - 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); - } + // 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 { @@ -166,14 +233,9 @@ namespace ts.FindAllReferences { // 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. if (isImportOrExportSpecifierName(location)) { - return location.getText(); + return location.text; } - // Try to get the local symbol if we're dealing with an 'export default' - // since that symbol has the "true" name. - const localExportDefaultSymbol = getLocalSymbolForExportDefault(symbol); - symbol = localExportDefaultSymbol || symbol; - return stripQuotes(symbol.name); } @@ -390,7 +452,9 @@ namespace ts.FindAllReferences { symbolToIndex: number[], implementations: boolean, typeChecker: TypeChecker, - cancellationToken: CancellationToken): void { + cancellationToken: CancellationToken, + searchSymbols: Symbol[], + inheritsFromCache: Map): void { const sourceFile = container.getSourceFile(); @@ -398,9 +462,6 @@ namespace ts.FindAllReferences { const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, start, container.getEnd(), cancellationToken); const parents = getParentSymbolsOfPropertyAccess(); - const inheritsFromCache: Map = createMap(); - // Build the set of symbols to search for, initially it has only the current symbol - const searchSymbols = populateSearchSymbolSet(searchSymbol, searchLocation, typeChecker, implementations); for (const position of possiblePositions) { cancellationToken.throwIfCancellationRequested(); @@ -445,12 +506,12 @@ namespace ts.FindAllReferences { addReferenceToRelatedSymbol(referenceLocation, relatedSymbol); } /* Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment - * has two meaning : property name and property value. Therefore when we do findAllReference at the position where - * an identifier is declared, the language service should return the position of the variable declaration as well as - * the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the - * position of property accessing, the referenceEntry of such position will be handled in the first case. - */ - else if (!(referenceSymbol.flags & SymbolFlags.Transient) && searchSymbols.indexOf(shorthandValueSymbol) >= 0) { + * has two meanings: property name and property value. Therefore when we do findAllReference at the position where + * an identifier is declared, the language service should return the position of the variable declaration as well as + * the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the + * position of property accessing, the referenceEntry of such position will be handled in the first case. + */ + else if (!(referenceSymbol.flags & SymbolFlags.Transient) && contains(searchSymbols, shorthandValueSymbol)) { addReferenceToRelatedSymbol(referenceSymbolDeclaration.name, shorthandValueSymbol); } else if (searchLocation.kind === SyntaxKind.ConstructorKeyword) { @@ -461,10 +522,10 @@ namespace ts.FindAllReferences { return; /* If we are just looking for implementations and this is a property access expression, we need to get the - * symbol of the local type of the symbol the property is being accessed on. This is because our search - * symbol may have a different parent symbol if the local type's symbol does not declare the property - * being accessed (i.e. it is declared in some parent class or interface) - */ + * symbol of the local type of the symbol the property is being accessed on. This is because our search + * symbol may have a different parent symbol if the local type's symbol does not declare the property + * being accessed (i.e. it is declared in some parent class or interface) + */ function getParentSymbolsOfPropertyAccess(): Symbol[] | undefined { if (implementations) { const propertyAccessExpression = getPropertyAccessExpressionFromRightHandSide(searchLocation); @@ -482,10 +543,6 @@ namespace ts.FindAllReferences { } } - function getPropertyAccessExpressionFromRightHandSide(node: Node): PropertyAccessExpression { - return isRightSideOfPropertyAccess(node) && node.parent; - } - /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ function findAdditionalConstructorReferences(referenceSymbol: Symbol, referenceLocation: Node): void { Debug.assert(isClassLike(searchSymbol.valueDeclaration)); @@ -494,7 +551,7 @@ namespace ts.FindAllReferences { if (referenceSymbol === searchSymbol && isClassLike(referenceClass)) { Debug.assert(referenceClass.name === referenceLocation); // This is the class declaration containing the constructor. - addReferences(findOwnConstructorCalls(searchSymbol)); + addReferences(findOwnConstructorCalls(searchSymbol, sourceFile)); } else { // If this class appears in `extends C`, then the extending class' "super" calls are references. @@ -512,58 +569,6 @@ namespace ts.FindAllReferences { } } - /** `classSymbol` is the class where the constructor was defined. - * Reference the constructor and all calls to `new this()`. - */ - function findOwnConstructorCalls(classSymbol: Symbol): Node[] { - const result: Node[] = []; - - for (const decl of classSymbol.members.get("__constructor").declarations) { - const ctrKeyword = ts.findChildOfKind(decl, ts.SyntaxKind.ConstructorKeyword, sourceFile)! - Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); - result.push(ctrKeyword); - } - - classSymbol.exports.forEach(member => { - const decl = member.valueDeclaration; - if (decl && decl.kind === SyntaxKind.MethodDeclaration) { - const body = (decl).body; - if (body) { - forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { - if (isNewExpressionTarget(thisKeyword)) { - result.push(thisKeyword); - } - }); - } - } - }); - - return result; - } - - /** Find references to `super` in the constructor of an extending class. */ - function superConstructorAccesses(cls: ClassLikeDeclaration): Node[] { - const symbol = cls.symbol; - const ctr = symbol.members.get("__constructor"); - if (!ctr) { - return []; - } - - const result: Node[] = []; - for (const decl of ctr.declarations) { - Debug.assert(decl.kind === SyntaxKind.Constructor); - const body = (decl).body; - if (body) { - forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { - if (isCallExpressionTarget(node)) { - result.push(node); - } - }); - } - }; - return result; - } - function getReferencedSymbol(symbol: Symbol): ReferencedSymbol { const symbolId = getSymbolId(symbol); let index = symbolToIndex[symbolId]; @@ -591,6 +596,62 @@ namespace ts.FindAllReferences { } } + function getPropertyAccessExpressionFromRightHandSide(node: Node): PropertyAccessExpression { + return isRightSideOfPropertyAccess(node) && node.parent; + } + + /** `classSymbol` is the class where the constructor was defined. + * Reference the constructor and all calls to `new this()`. + */ + function findOwnConstructorCalls(classSymbol: Symbol, sourceFile: SourceFile): Node[] { + const result: Node[] = []; + + for (const decl of classSymbol.members.get("__constructor").declarations) { + const ctrKeyword = ts.findChildOfKind(decl, ts.SyntaxKind.ConstructorKeyword, sourceFile)! + Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); + result.push(ctrKeyword); + } + + classSymbol.exports.forEach(member => { + const decl = member.valueDeclaration; + if (decl && decl.kind === SyntaxKind.MethodDeclaration) { + const body = (decl).body; + if (body) { + forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { + if (isNewExpressionTarget(thisKeyword)) { + result.push(thisKeyword); + } + }); + } + } + }); + + return result; + } + + /** Find references to `super` in the constructor of an extending class. */ + function superConstructorAccesses(cls: ClassLikeDeclaration): Node[] { + const symbol = cls.symbol; + const ctr = symbol.members.get("__constructor"); + if (!ctr) { + return []; + } + + const result: Node[] = []; + for (const decl of ctr.declarations) { + Debug.assert(decl.kind === SyntaxKind.Constructor); + const body = (decl).body; + if (body) { + forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { + if (isCallExpressionTarget(node)) { + result.push(node); + } + }); + } + }; + return result; + } + function getImplementationReferenceEntryForNode(refNode: Node, result: ReferenceEntry[], typeChecker: TypeChecker): void { // Check if we found a function/propertyAssignment/method with an implementation or initializer if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { @@ -805,13 +866,13 @@ namespace ts.FindAllReferences { const sourceFile = searchSpaceNode.getSourceFile(); const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode.getStart(), searchSpaceNode.getEnd(), cancellationToken); - forEach(possiblePositions, position => { + for (const position of possiblePositions) { cancellationToken.throwIfCancellationRequested(); const node = getTouchingWord(sourceFile, position); if (!node || node.kind !== SyntaxKind.SuperKeyword) { - return; + continue; } const container = getSuperContainer(node, /*stopOnFunctions*/ false); @@ -822,7 +883,7 @@ namespace ts.FindAllReferences { if (container && (ModifierFlags.Static & getModifierFlags(container)) === staticFlag && container.parent.symbol === searchSpaceNode.symbol) { references.push(getReferenceEntryFromNode(node)); } - }); + } const definition = getDefinition(searchSpaceNode.symbol, superKeyword, typeChecker); return [{ definition, references }]; @@ -985,7 +1046,7 @@ namespace ts.FindAllReferences { 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 @@ -998,22 +1059,6 @@ namespace ts.FindAllReferences { } } - // If the symbol is an alias, add what it aliases to the list - // import {a} from "mod"; - // export {a} - // If the symbol is an alias to default declaration, add what it aliases to the list - // declare "mod" { export default class B { } } - // import B from "mod"; - //// For export specifiers, the exported name can be referring 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) - const aliasSymbol = getAliasSymbolForPropertyNameSymbol(symbol, location, typeChecker); - 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 @@ -1045,7 +1090,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 @@ -1057,7 +1102,7 @@ namespace ts.FindAllReferences { // If this is a union property, add all the symbols from all its source symbols in all unioned types. // If the symbol is an instantiation from a another symbol (e.g. widened symbol) , add the root the list - forEach(typeChecker.getRootSymbols(symbol), rootSymbol => { + for (const rootSymbol of typeChecker.getRootSymbols(symbol)) { if (rootSymbol !== symbol) { result.push(rootSymbol); } @@ -1066,7 +1111,7 @@ namespace ts.FindAllReferences { if (!implementations && rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap(), typeChecker); } - }); + } return result; } @@ -1130,10 +1175,10 @@ namespace ts.FindAllReferences { } } - function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean, parents: Symbol[] | undefined, cache: Map, typeChecker: TypeChecker): Symbol { + function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean, parents: Symbol[] | undefined, cache: Map, typeChecker: TypeChecker): Symbol | undefined { if (contains(searchSymbols, referenceSymbol)) { // If we are searching for constructor uses, they must be 'new' expressions. - return (!searchLocationIsConstructor || isNewExpressionTarget(referenceLocation)) && referenceSymbol; + return (!searchLocationIsConstructor || isNewExpressionTarget(referenceLocation)) ? referenceSymbol : undefined; } // If the reference symbol is an alias, check if what it is aliasing is one of the search @@ -1148,9 +1193,8 @@ namespace ts.FindAllReferences { // compare to our searchSymbol const containingObjectLiteralElement = getContainingObjectLiteralElement(referenceLocation); if (containingObjectLiteralElement) { - const contextualSymbol = forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement, typeChecker), contextualSymbol => { - return forEach(typeChecker.getRootSymbols(contextualSymbol), s => searchSymbols.indexOf(s) >= 0 ? s : undefined); - }); + const contextualSymbol = forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement, typeChecker), contextualSymbol => + find(typeChecker.getRootSymbols(contextualSymbol), symbol => contains(searchSymbols, symbol))); if (contextualSymbol) { return contextualSymbol; @@ -1161,7 +1205,7 @@ namespace ts.FindAllReferences { // In below eg. get 'property' from type of elems iterating type // for ( { property: p2 } of elems) { } const propertySymbol = getPropertySymbolOfDestructuringAssignment(referenceLocation, typeChecker); - if (propertySymbol && searchSymbols.indexOf(propertySymbol) >= 0) { + if (propertySymbol && contains(searchSymbols, propertySymbol)) { return propertySymbol; } } @@ -1170,7 +1214,7 @@ namespace ts.FindAllReferences { // then include the binding element in the related symbols // let { a } : { a }; const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(referenceSymbol, typeChecker); - if (bindingElementPropertySymbol && searchSymbols.indexOf(bindingElementPropertySymbol) >= 0) { + if (bindingElementPropertySymbol && contains(searchSymbols, bindingElementPropertySymbol)) { return bindingElementPropertySymbol; } @@ -1178,7 +1222,7 @@ namespace ts.FindAllReferences { // Or a union property, use its underlying unioned symbols return forEach(typeChecker.getRootSymbols(referenceSymbol), rootSymbol => { // if it is in the list, then we are done - if (searchSymbols.indexOf(rootSymbol) >= 0) { + if (contains(searchSymbols, rootSymbol)) { return rootSymbol; } @@ -1195,7 +1239,7 @@ namespace ts.FindAllReferences { const result: Symbol[] = []; getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap(), typeChecker); - return forEach(result, s => searchSymbols.indexOf(s) >= 0 ? s : undefined); + return find(result, symbol => contains(searchSymbols, symbol)); } return undefined; @@ -1214,7 +1258,8 @@ namespace ts.FindAllReferences { return (node.name).text; } - function getPropertySymbolsFromContextualType(node: ObjectLiteralElement, typeChecker: TypeChecker): Symbol[] { + /** Gets all symbols for one property. Does not get symbols for every property. */ + function getPropertySymbolsFromContextualType(node: ObjectLiteralElement, typeChecker: TypeChecker): Symbol[] | undefined { const objectLiteral = node.parent; const contextualType = typeChecker.getContextualType(objectLiteral); const name = getNameFromObjectLiteralElement(node); @@ -1420,4 +1465,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 123d29630ad81..9135e1fe0f038 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 cb38b1d500ee6..00a8c9a9f2e70 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/src/services/utilities.ts b/src/services/utilities.ts index a1680da69c320..f83f9ef823170 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1319,7 +1319,7 @@ namespace ts { return name; } - export function isImportOrExportSpecifierName(location: Node): boolean { + export function isImportOrExportSpecifierName(location: Node): location is Identifier { return location.parent && (location.parent.kind === SyntaxKind.ImportSpecifier || location.parent.kind === SyntaxKind.ExportSpecifier) && (location.parent).propertyName === location; diff --git a/tests/cases/fourslash/findAllRefsForDefaultExport.ts b/tests/cases/fourslash/findAllRefsForDefaultExport.ts new file mode 100644 index 0000000000000..a27227b907a74 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsForDefaultExport.ts @@ -0,0 +1,11 @@ +/// + +// @Filename: a.ts +////export default function /*def*/[|f|]() {} + +// @Filename: b.ts +////import [|g|] from "./a"; +/////*ref*/[|g|](); + +verify.rangesReferenceEachOther(); +verify.goToDefinition("ref", "def"); diff --git a/tests/cases/fourslash/renameDefaultImport.ts b/tests/cases/fourslash/renameDefaultImport.ts index 8d9b65d78d655..ff297ffa14741 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 aec699aadda6f..be968c9b8830d 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); +}