/* @internal */ namespace ts.FindAllReferences { 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, isForRename); } export function convertReferences(referenceSymbols: ReferencedSymbol[]): ReferenceEntry[] { return referenceSymbols && flatMap(referenceSymbols, r => r.references); } 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) { 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. 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; } const declarations = symbol.declarations; // The symbol was an internal symbol and does not have a declaration e.g. undefined symbol if (!declarations || !declarations.length) { 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)); // 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); if (scope) { getRefs(scope, declaredName); } else { const isDefault = isExportDefaultSymbol(symbol); const internedName = isDefault ? symbol.valueDeclaration.localSymbol.name : getInternedName(symbol, node); for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested(); 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 = defaultName && 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(""); const declarations = symbol.declarations; if (!declarations || declarations.length === 0) { return undefined; } return { containerKind: "", containerName: "", name, kind: symbolKind, fileName: declarations[0].getSourceFile().fileName, textSpan: createTextSpan(declarations[0].getStart(), 0), displayParts }; } function getAliasSymbolForPropertyNameSymbol(symbol: Symbol, location: Node, typeChecker: TypeChecker): Symbol | undefined { 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); } } function followAliasIfNecessary(symbol: Symbol, location: Node, typeChecker: TypeChecker): Symbol { return getAliasSymbolForPropertyNameSymbol(symbol, location, typeChecker) || symbol; } function getPropertySymbolOfDestructuringAssignment(location: Node, typeChecker: TypeChecker) { return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) && typeChecker.getPropertySymbolOfDestructuringAssignment(location); } function isObjectBindingPatternElementWithoutPropertyName(symbol: Symbol) { const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement); return bindingElement && bindingElement.parent.kind === SyntaxKind.ObjectBindingPattern && !bindingElement.propertyName; } function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, typeChecker: TypeChecker) { if (isObjectBindingPatternElementWithoutPropertyName(symbol)) { const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement); const typeOfPattern = typeChecker.getTypeAtLocation(bindingElement.parent); return typeOfPattern && typeChecker.getPropertyOfType(typeOfPattern, (bindingElement.name).text); } return undefined; } function getInternedName(symbol: Symbol, location: Node): 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. if (isImportOrExportSpecifierName(location)) { return location.text; } return stripQuotes(symbol.name); } /** * Determines the smallest scope in which a symbol may have named references. * Note that not every construct has been accounted for. This function can * probably be improved. * * @returns undefined if the scope cannot be determined, implying that * a reference to a symbol can occur anywhere. */ function getSymbolScope(symbol: Symbol): Node { // If this is the symbol of a named function expression or named class expression, // then named references are limited to its own scope. const valueDeclaration = symbol.valueDeclaration; if (valueDeclaration && (valueDeclaration.kind === SyntaxKind.FunctionExpression || valueDeclaration.kind === SyntaxKind.ClassExpression)) { return valueDeclaration; } // If this is private property or method, the scope is the containing class if (symbol.flags & (SymbolFlags.Property | SymbolFlags.Method)) { const privateDeclaration = forEach(symbol.getDeclarations(), d => (getModifierFlags(d) & ModifierFlags.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 visible outside its declaration scope. if (symbol.flags & SymbolFlags.Alias) { return undefined; } // If symbol is of object binding pattern element without property name we would want to // look for property too and that could be anywhere if (isObjectBindingPatternElementWithoutPropertyName(symbol)) { 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.flags & SymbolFlags.Transient && (symbol).checkFlags & CheckFlags.SyntheticProperty)) { return undefined; } let scope: Node; const declarations = symbol.getDeclarations(); if (declarations) { for (const declaration of declarations) { const container = getContainerNode(declaration); if (!container) { return undefined; } if (scope && scope !== container) { // Different declarations have different containers, bail out return undefined; } if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { // This is a global variable and not an external module, any declaration defined // within this scope is visible outside the file return undefined; } // The search scope is the container node scope = container; } } return scope; } function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, start: number, end: number, cancellationToken: CancellationToken): number[] { const positions: number[] = []; /// TODO: Cache symbol existence for files to save text search // Also, need to make this work for unicode escapes. // Be resilient in the face of a symbol with no name or zero length name if (!symbolName || !symbolName.length) { return positions; } const text = sourceFile.text; const sourceLength = text.length; const symbolNameLength = symbolName.length; let position = text.indexOf(symbolName, start); while (position >= 0) { cancellationToken.throwIfCancellationRequested(); // If we are past the end, stop looking if (position > end) break; // We found a match. Make sure it's not part of a larger word (i.e. the char // before and after it have to be a non-identifier char). const endPosition = position + symbolNameLength; if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) && (endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) { // Found a real match. Keep searching. positions.push(position); } position = text.indexOf(symbolName, position + symbolNameLength + 1); } return positions; } function getLabelReferencesInNode(container: Node, targetLabel: Identifier, cancellationToken: CancellationToken): ReferencedSymbol[] { const references: ReferenceEntry[] = []; const sourceFile = container.getSourceFile(); const labelName = targetLabel.text; const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container.getStart(), container.getEnd(), cancellationToken); forEach(possiblePositions, position => { cancellationToken.throwIfCancellationRequested(); const node = getTouchingWord(sourceFile, position); if (!node || node.getWidth() !== labelName.length) { return; } // Only pick labels that are either the target label, or have a target that is the target label if (node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel)) { references.push(getReferenceEntryFromNode(node)); } }); const definition: ReferencedSymbolDefinitionInfo = { containerKind: "", containerName: "", fileName: targetLabel.getSourceFile().fileName, kind: ScriptElementKind.label, name: labelName, textSpan: createTextSpanFromNode(targetLabel, sourceFile), displayParts: [displayPart(labelName, SymbolDisplayPartKind.text)] }; return [{ definition, references }]; } function isValidReferencePosition(node: Node, searchSymbolName: string): boolean { // Compare the length so we filter out strict superstrings of the symbol we are looking for switch (node && node.kind) { case SyntaxKind.Identifier: return node.getWidth() === searchSymbolName.length; case SyntaxKind.StringLiteral: return (isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isNameOfExternalModuleImportOrDeclaration(node)) && // For string literals we have two additional chars for the quotes node.getWidth() === searchSymbolName.length + 2; case SyntaxKind.NumericLiteral: return isLiteralNameOfPropertyDeclarationOrIndexAccess(node) && node.getWidth() === searchSymbolName.length; default: return false; } } function getAllReferencesForKeyword(sourceFiles: SourceFile[], keywordKind: ts.SyntaxKind, cancellationToken: CancellationToken): ReferencedSymbol[] { const name = tokenToString(keywordKind); const references: ReferenceEntry[] = []; for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested(); addReferencesForKeywordInFile(sourceFile, keywordKind, name, cancellationToken, references); } if (!references.length) return undefined; const definition: ReferencedSymbolDefinitionInfo = { containerKind: "", containerName: "", fileName: references[0].fileName, kind: ScriptElementKind.keyword, name, textSpan: references[0].textSpan, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] } return [{ definition, references }]; } function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, cancellationToken: CancellationToken, references: Push): void { const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); for (const position of possiblePositions) { cancellationToken.throwIfCancellationRequested(); const referenceLocation = getTouchingPropertyName(sourceFile, position); if (referenceLocation.kind === kind) { references.push({ textSpan: createTextSpanFromNode(referenceLocation), fileName: sourceFile.fileName, isWriteAccess: false, isDefinition: false, }); } } } /** Search within node "container" for references for a search value, where the search value is defined as a * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). * searchLocation: a node where the search value */ function getReferencesInNode(container: Node, searchSymbol: Symbol, searchText: string, searchLocation: Node, searchMeaning: SemanticMeaning, findInStrings: boolean, findInComments: boolean, result: ReferencedSymbol[], symbolToIndex: number[], implementations: boolean, typeChecker: TypeChecker, cancellationToken: CancellationToken, searchSymbols: Symbol[], inheritsFromCache: Map): void { const sourceFile = container.getSourceFile(); const start = findInComments ? container.getFullStart() : container.getStart(); const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, start, container.getEnd(), cancellationToken); const parents = getParentSymbolsOfPropertyAccess(); for (const position of possiblePositions) { cancellationToken.throwIfCancellationRequested(); const referenceLocation = getTouchingPropertyName(sourceFile, position); if (!isValidReferencePosition(referenceLocation, searchText)) { // This wasn't the start of a token. Check to see if it might be a // match in a comment or string if that's what the caller is asking // for. if (!implementations && ((findInStrings && isInString(sourceFile, position)) || (findInComments && isInNonReferenceComment(sourceFile, position)))) { // In the case where we're looking inside comments/strings, we don't have // an actual definition. So just use 'undefined' here. Features like // 'Rename' won't care (as they ignore the definitions), and features like // 'FindReferences' will just filter out these results. result.push({ definition: undefined, references: [{ fileName: sourceFile.fileName, textSpan: createTextSpan(position, searchText.length), isWriteAccess: false, isDefinition: false }] }); } continue; } if (!(getMeaningFromLocation(referenceLocation) & searchMeaning)) { continue; } const referenceSymbol = typeChecker.getSymbolAtLocation(referenceLocation); if (referenceSymbol) { const referenceSymbolDeclaration = referenceSymbol.valueDeclaration; const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration); const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, /*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword, parents, inheritsFromCache, typeChecker); if (relatedSymbol) { addReferenceToRelatedSymbol(referenceLocation, relatedSymbol); } /* Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment * 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) { findAdditionalConstructorReferences(referenceSymbol, referenceLocation); } } } 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) */ function getParentSymbolsOfPropertyAccess(): Symbol[] | undefined { if (implementations) { const propertyAccessExpression = getPropertyAccessExpressionFromRightHandSide(searchLocation); if (propertyAccessExpression) { const localParentType = typeChecker.getTypeAtLocation(propertyAccessExpression.expression); if (localParentType) { if (localParentType.symbol && localParentType.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) && localParentType.symbol !== searchSymbol.parent) { return [localParentType.symbol]; } else if (localParentType.flags & TypeFlags.UnionOrIntersection) { return getSymbolsForClassAndInterfaceComponents(localParentType); } } } } } /** 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)); const referenceClass = referenceLocation.parent; if (referenceSymbol === searchSymbol && isClassLike(referenceClass)) { Debug.assert(referenceClass.name === referenceLocation); // This is the class declaration containing the constructor. addReferences(findOwnConstructorCalls(searchSymbol, sourceFile)); } else { // If this class appears in `extends C`, then the extending class' "super" calls are references. const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); if (classExtending && isClassLike(classExtending) && followAliasIfNecessary(referenceSymbol, referenceLocation, typeChecker) === searchSymbol) { addReferences(superConstructorAccesses(classExtending)); } } } function addReferences(references: Node[]): void { if (references.length) { const referencedSymbol = getReferencedSymbol(searchSymbol); addRange(referencedSymbol.references, map(references, getReferenceEntryFromNode)); } } function getReferencedSymbol(symbol: Symbol): ReferencedSymbol { const symbolId = getSymbolId(symbol); let index = symbolToIndex[symbolId]; if (index === undefined) { index = result.length; symbolToIndex[symbolId] = index; result.push({ definition: getDefinition(symbol, searchLocation, typeChecker), references: [] }); } return result[index]; } function addReferenceToRelatedSymbol(node: Node, relatedSymbol: Symbol) { const references = getReferencedSymbol(relatedSymbol).references; if (implementations) { getImplementationReferenceEntryForNode(node, references, typeChecker); } else { references.push(getReferenceEntryFromNode(node)); } } } 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)) { result.push(getReferenceEntryFromNode(refNode.parent)); } else if (refNode.kind === SyntaxKind.Identifier) { if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { // Go ahead and dereference the shorthand assignment by going to its definition getReferenceEntriesForShorthandPropertyAssignment(refNode, typeChecker, result); } // Check if the node is within an extends or implements clause const containingClass = getContainingClassIfInHeritageClause(refNode); if (containingClass) { result.push(getReferenceEntryFromNode(containingClass)); return; } // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface const containingTypeReference = getContainingTypeReference(refNode); if (containingTypeReference) { const parent = containingTypeReference.parent; if (isVariableLike(parent) && parent.type === containingTypeReference && parent.initializer && isImplementationExpression(parent.initializer)) { maybeAdd(getReferenceEntryFromNode(parent.initializer)); } else if (isFunctionLike(parent) && parent.type === containingTypeReference && parent.body) { if (parent.body.kind === SyntaxKind.Block) { forEachReturnStatement(parent.body, returnStatement => { if (returnStatement.expression && isImplementationExpression(returnStatement.expression)) { maybeAdd(getReferenceEntryFromNode(returnStatement.expression)); } }); } else if (isImplementationExpression(parent.body)) { maybeAdd(getReferenceEntryFromNode(parent.body)); } } else if (isAssertionExpression(parent) && isImplementationExpression(parent.expression)) { maybeAdd(getReferenceEntryFromNode(parent.expression)); } } } // Type nodes can contain multiple references to the same type. For example: // let x: Foo & (Foo & Bar) = ... // Because we are returning the implementation locations and not the identifier locations, // duplicate entries would be returned here as each of the type references is part of // the same implementation. For that reason, check before we add a new entry function maybeAdd(a: ReferenceEntry) { if (!forEach(result, b => a.fileName === b.fileName && a.textSpan.start === b.textSpan.start && a.textSpan.length === b.textSpan.length)) { result.push(a); } } } function getSymbolsForClassAndInterfaceComponents(type: UnionOrIntersectionType, result: Symbol[] = []): Symbol[] { for (const componentType of type.types) { if (componentType.symbol && componentType.symbol.getFlags() & (SymbolFlags.Class | SymbolFlags.Interface)) { result.push(componentType.symbol); } if (componentType.getFlags() & TypeFlags.UnionOrIntersection) { getSymbolsForClassAndInterfaceComponents(componentType, result); } } return result; } function getContainingTypeReference(node: Node): Node { let topLevelTypeReference: Node = undefined; while (node) { if (isTypeNode(node)) { topLevelTypeReference = node; } node = node.parent; } return topLevelTypeReference; } function getContainingClassIfInHeritageClause(node: Node): ClassLikeDeclaration { if (node && node.parent) { if (node.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.kind === SyntaxKind.HeritageClause && isClassLike(node.parent.parent)) { return node.parent.parent; } else if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression) { return getContainingClassIfInHeritageClause(node.parent); } } return undefined; } /** * Returns true if this is an expression that can be considered an implementation */ function isImplementationExpression(node: Expression): boolean { switch (node.kind) { case SyntaxKind.ParenthesizedExpression: return isImplementationExpression((node).expression); case SyntaxKind.ArrowFunction: case SyntaxKind.FunctionExpression: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.ClassExpression: case SyntaxKind.ArrayLiteralExpression: return true; default: return false; } } /** * Determines if the parent symbol occurs somewhere in the child's ancestry. If the parent symbol * is an interface, determines if some ancestor of the child symbol extends or inherits from it. * Also takes in a cache of previous results which makes this slightly more efficient and is * necessary to avoid potential loops like so: * class A extends B { } * class B extends A { } * * We traverse the AST rather than using the type checker because users are typically only interested * in explicit implementations of an interface/class when calling "Go to Implementation". Sibling * implementations of types that share a common ancestor with the type whose implementation we are * searching for need to be filtered out of the results. The type checker doesn't let us make the * distinction between structurally compatible implementations and explicit implementations, so we * must use the AST. * * @param child A class or interface Symbol * @param parent Another class or interface Symbol * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results */ function explicitlyInheritsFrom(child: Symbol, parent: Symbol, cachedResults: Map, typeChecker: TypeChecker): boolean { const parentIsInterface = parent.getFlags() & SymbolFlags.Interface; return searchHierarchy(child); function searchHierarchy(symbol: Symbol): boolean { if (symbol === parent) { return true; } const key = getSymbolId(symbol) + "," + getSymbolId(parent); const cached = cachedResults.get(key); if (cached !== undefined) { return cached; } // Set the key so that we don't infinitely recurse cachedResults.set(key, false); const inherits = forEach(symbol.getDeclarations(), declaration => { if (isClassLike(declaration)) { if (parentIsInterface) { const interfaceReferences = getClassImplementsHeritageClauseElements(declaration); if (interfaceReferences) { for (const typeReference of interfaceReferences) { if (searchTypeReference(typeReference)) { return true; } } } } return searchTypeReference(getClassExtendsHeritageClauseElement(declaration)); } else if (declaration.kind === SyntaxKind.InterfaceDeclaration) { if (parentIsInterface) { return forEach(getInterfaceBaseTypeNodes(declaration), searchTypeReference); } } return false; }); cachedResults.set(key, inherits); return inherits; } function searchTypeReference(typeReference: ExpressionWithTypeArguments): boolean { if (typeReference) { const type = typeChecker.getTypeAtLocation(typeReference); if (type && type.symbol) { return searchHierarchy(type.symbol); } } return false; } } function getReferencesForSuperKeyword(superKeyword: Node, typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); if (!searchSpaceNode) { return undefined; } // Whether 'super' occurs in a static context within a class. let staticFlag = ModifierFlags.Static; switch (searchSpaceNode.kind) { case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: staticFlag &= getModifierFlags(searchSpaceNode); searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class break; default: return undefined; } const references: ReferenceEntry[] = []; const sourceFile = searchSpaceNode.getSourceFile(); const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode.getStart(), searchSpaceNode.getEnd(), cancellationToken); for (const position of possiblePositions) { cancellationToken.throwIfCancellationRequested(); const node = getTouchingWord(sourceFile, position); if (!node || node.kind !== SyntaxKind.SuperKeyword) { continue; } const container = getSuperContainer(node, /*stopOnFunctions*/ false); // If we have a 'super' container, we must have an enclosing class. // Now make sure the owning class is the same as the search-space // and has the same static qualifier as the original 'super's owner. 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 }]; } function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[], typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); // Whether 'this' occurs in a static context within a class. let staticFlag = ModifierFlags.Static; switch (searchSpaceNode.kind) { case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: if (isObjectLiteralMethod(searchSpaceNode)) { break; } // fall through case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: staticFlag &= getModifierFlags(searchSpaceNode); searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class break; case SyntaxKind.SourceFile: if (isExternalModule(searchSpaceNode)) { return undefined; } // Fall through case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: break; // Computed properties in classes are not handled here because references to this are illegal, // so there is no point finding references to them. default: return undefined; } const references: ReferenceEntry[] = []; let possiblePositions: number[]; if (searchSpaceNode.kind === SyntaxKind.SourceFile) { forEach(sourceFiles, sourceFile => { possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); getThisReferencesInFile(sourceFile, sourceFile, possiblePositions, references); }); } else { const sourceFile = searchSpaceNode.getSourceFile(); possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", searchSpaceNode.getStart(), searchSpaceNode.getEnd(), cancellationToken); getThisReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, references); } const thisOrSuperSymbol = typeChecker.getSymbolAtLocation(thisOrSuperKeyword); const displayParts = thisOrSuperSymbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind( typeChecker, thisOrSuperSymbol, thisOrSuperKeyword.getSourceFile(), getContainerNode(thisOrSuperKeyword), thisOrSuperKeyword).displayParts; return [{ definition: { containerKind: "", containerName: "", fileName: thisOrSuperKeyword.getSourceFile().fileName, kind: ScriptElementKind.variableElement, name: "this", textSpan: createTextSpanFromNode(thisOrSuperKeyword), displayParts }, references: references }]; function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: ReferenceEntry[]): void { forEach(possiblePositions, position => { cancellationToken.throwIfCancellationRequested(); const node = getTouchingWord(sourceFile, position); if (!node || !isThis(node)) { return; } const container = getThisContainer(node, /* includeArrowFunctions */ false); switch (searchSpaceNode.kind) { case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionDeclaration: if (searchSpaceNode.symbol === container.symbol) { result.push(getReferenceEntryFromNode(node)); } break; case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: if (isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol) { result.push(getReferenceEntryFromNode(node)); } break; case SyntaxKind.ClassExpression: case SyntaxKind.ClassDeclaration: // Make sure the container belongs to the same class // and has the appropriate static modifier from the original container. if (container.parent && searchSpaceNode.symbol === container.parent.symbol && (getModifierFlags(container) & ModifierFlags.Static) === staticFlag) { result.push(getReferenceEntryFromNode(node)); } break; case SyntaxKind.SourceFile: if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { result.push(getReferenceEntryFromNode(node)); } break; } }); } } function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { const type = getStringLiteralTypeForNode(node, typeChecker); if (!type) { // nothing to do here. moving on return undefined; } const references: ReferenceEntry[] = []; for (const sourceFile of sourceFiles) { const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, type.text, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); getReferencesForStringLiteralInFile(sourceFile, type, possiblePositions, references); } return [{ definition: { containerKind: "", containerName: "", fileName: node.getSourceFile().fileName, kind: ScriptElementKind.variableElement, name: type.text, textSpan: createTextSpanFromNode(node), displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)] }, references: references }]; function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: ReferenceEntry[]): void { for (const position of possiblePositions) { cancellationToken.throwIfCancellationRequested(); const node = getTouchingWord(sourceFile, position); if (!node || node.kind !== SyntaxKind.StringLiteral) { return; } const type = getStringLiteralTypeForNode(node, typeChecker); if (type === searchType) { references.push(getReferenceEntryFromNode(node)); } } } } function populateSearchSymbolSet(symbol: Symbol, location: Node, typeChecker: TypeChecker, implementations: boolean): Symbol[] { // The search set contains at least the current symbol const result = [symbol]; // If the location is name of property symbol from object literal destructuring pattern // Search the property symbol // for ( { property: p2 } of elems) { } const containingObjectLiteralElement = getContainingObjectLiteralElement(location); if (containingObjectLiteralElement && containingObjectLiteralElement.kind !== SyntaxKind.ShorthandPropertyAssignment) { const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, typeChecker); if (propertySymbol) { result.push(propertySymbol); } } // 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 if (containingObjectLiteralElement) { forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement, typeChecker), contextualSymbol => { addRange(result, typeChecker.getRootSymbols(contextualSymbol)); }); /* Because in short-hand property assignment, location has two meaning : property name and as value of the property * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of * property name and variable declaration of the identifier. * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service * should show both 'name' in 'obj' and 'name' in variable declaration * const name = "Foo"; * const obj = { name }; * In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration * will be included correctly. */ const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(location.parent); if (shorthandValueSymbol) { result.push(shorthandValueSymbol); } } // If the symbol.valueDeclaration is a property parameter declaration, // we should include both parameter declaration symbol and property declaration symbol // Parameter Declaration symbol is only visible within function scope, so the symbol is stored in constructor.locals. // 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)) { addRange(result, typeChecker.getSymbolsOfParameterPropertyDeclaration(symbol.valueDeclaration, symbol.name)); } // If this is symbol of binding element without propertyName declaration in Object binding pattern // Include the property in the search const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, typeChecker); if (bindingElementPropertySymbol) { result.push(bindingElementPropertySymbol); } // 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 for (const rootSymbol of typeChecker.getRootSymbols(symbol)) { if (rootSymbol !== symbol) { result.push(rootSymbol); } // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions if (!implementations && rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap(), typeChecker); } } return result; } /** * Find symbol of the given property-name and add the symbol to the given result array * @param symbol a symbol to start searching for the given propertyName * @param propertyName a name of property to search for * @param result an array of symbol of found property symbols * @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol. * The value of previousIterationSymbol is undefined when the function is first called. */ function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, result: Symbol[], previousIterationSymbolsCache: SymbolTable, typeChecker: TypeChecker): void { if (!symbol) { return; } // If the current symbol is the same as the previous-iteration symbol, we can just return the symbol that has already been visited // This is particularly important for the following cases, so that we do not infinitely visit the same symbol. // For example: // interface C extends C { // /*findRef*/propName: string; // } // The first time getPropertySymbolsFromBaseTypes is called when finding-all-references at propName, // the symbol argument will be the symbol of an interface "C" and previousIterationSymbol is undefined, // the function will add any found symbol of the property-name, then its sub-routine will call // getPropertySymbolsFromBaseTypes again to walk up any base types to prevent revisiting already // visited symbol, interface "C", the sub-routine will pass the current symbol as previousIterationSymbol. if (previousIterationSymbolsCache.has(symbol.name)) { return; } if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { forEach(symbol.getDeclarations(), declaration => { if (isClassLike(declaration)) { getPropertySymbolFromTypeReference(getClassExtendsHeritageClauseElement(declaration)); forEach(getClassImplementsHeritageClauseElements(declaration), getPropertySymbolFromTypeReference); } else if (declaration.kind === SyntaxKind.InterfaceDeclaration) { forEach(getInterfaceBaseTypeNodes(declaration), getPropertySymbolFromTypeReference); } }); } return; function getPropertySymbolFromTypeReference(typeReference: ExpressionWithTypeArguments) { if (typeReference) { const type = typeChecker.getTypeAtLocation(typeReference); if (type) { const propertySymbol = typeChecker.getPropertyOfType(type, propertyName); if (propertySymbol) { result.push(...typeChecker.getRootSymbols(propertySymbol)); } // Visit the typeReference as well to see if it directly or indirectly use that property previousIterationSymbolsCache.set(symbol.name, symbol); getPropertySymbolsFromBaseTypes(type.symbol, propertyName, result, previousIterationSymbolsCache, typeChecker); } } } } 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 : undefined; } // If the reference symbol is an alias, check if what it is aliasing is one of the search // symbols but by looking up for related symbol of this alias so it can handle multiple level of indirectness. const aliasSymbol = getAliasSymbolForPropertyNameSymbol(referenceSymbol, referenceLocation, typeChecker); if (aliasSymbol) { return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, searchLocationIsConstructor, parents, cache, typeChecker); } // 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 const containingObjectLiteralElement = getContainingObjectLiteralElement(referenceLocation); if (containingObjectLiteralElement) { const contextualSymbol = forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement, typeChecker), contextualSymbol => find(typeChecker.getRootSymbols(contextualSymbol), symbol => contains(searchSymbols, symbol))); if (contextualSymbol) { return contextualSymbol; } // If the reference location is the name of property from object literal destructuring pattern // Get the property symbol from the object literal's type and look if thats the search symbol // In below eg. get 'property' from type of elems iterating type // for ( { property: p2 } of elems) { } const propertySymbol = getPropertySymbolOfDestructuringAssignment(referenceLocation, typeChecker); if (propertySymbol && contains(searchSymbols, propertySymbol)) { return propertySymbol; } } // If the reference location is the binding element and doesn't have property name // then include the binding element in the related symbols // let { a } : { a }; const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(referenceSymbol, typeChecker); if (bindingElementPropertySymbol && contains(searchSymbols, bindingElementPropertySymbol)) { return bindingElementPropertySymbol; } // Unwrap symbols to get to the root (e.g. transient symbols as a result of widening) // 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 (contains(searchSymbols, rootSymbol)) { return rootSymbol; } // Finally, try all properties with the same name in any type the containing type extended or implemented, and // see if any is in the list. If we were passed a parent symbol, only include types that are subtypes of the // parent symbol if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { // Parents will only be defined if implementations is true if (parents) { if (!forEach(parents, parent => explicitlyInheritsFrom(rootSymbol.parent, parent, cache, typeChecker))) { return undefined; } } const result: Symbol[] = []; getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap(), typeChecker); return find(result, symbol => contains(searchSymbols, symbol)); } return undefined; }); } function getNameFromObjectLiteralElement(node: ObjectLiteralElement) { if (node.name.kind === SyntaxKind.ComputedPropertyName) { const nameExpression = (node.name).expression; // treat computed property names where expression is string/numeric literal as just string/numeric literal if (isStringOrNumericLiteral(nameExpression)) { return (nameExpression).text; } return undefined; } return (node.name).text; } /** 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); if (name && contextualType) { const result: Symbol[] = []; const symbol = contextualType.getProperty(name); if (symbol) { result.push(symbol); } if (contextualType.flags & TypeFlags.Union) { forEach((contextualType).types, t => { const symbol = t.getProperty(name); if (symbol) { result.push(symbol); } }); } return result; } return undefined; } /** * 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 getIntersectingMeaningFromDeclarations(meaning: SemanticMeaning, declarations: Declaration[]): SemanticMeaning { if (declarations) { let lastIterationMeaning: SemanticMeaning; 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. // Remember the last meaning lastIterationMeaning = meaning; for (const declaration of declarations) { const declarationMeaning = getMeaningFromDeclaration(declaration); if (declarationMeaning & meaning) { meaning |= declarationMeaning; } } } while (meaning !== lastIterationMeaning); } return meaning; } function isImplementation(node: Node): boolean { if (!node) { return false; } else if (isVariableLike(node)) { if (node.initializer) { return true; } else if (node.kind === SyntaxKind.VariableDeclaration) { const parentStatement = getParentStatementOfVariableDeclaration(node); return parentStatement && hasModifier(parentStatement, ModifierFlags.Ambient); } } else if (isFunctionLike(node)) { return !!node.body || hasModifier(node, ModifierFlags.Ambient); } else { switch (node.kind) { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.EnumDeclaration: case SyntaxKind.ModuleDeclaration: return true; } } return false; } function getParentStatementOfVariableDeclaration(node: VariableDeclaration): VariableStatement { if (node.parent && node.parent.parent && node.parent.parent.kind === SyntaxKind.VariableStatement) { Debug.assert(node.parent.kind === SyntaxKind.VariableDeclarationList); return node.parent.parent; } } export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, typeChecker: TypeChecker, result: ReferenceEntry[]): void { const refSymbol = typeChecker.getSymbolAtLocation(node); const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); if (shorthandSymbol) { for (const declaration of shorthandSymbol.getDeclarations()) { if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { result.push(getReferenceEntryFromNode(declaration)); } } } } export function getReferenceEntryFromNode(node: Node): ReferenceEntry { let start = node.getStart(); let end = node.getEnd(); if (node.kind === SyntaxKind.StringLiteral) { start += 1; end -= 1; } return { fileName: node.getSourceFile().fileName, textSpan: createTextSpanFromBounds(start, end), isWriteAccess: isWriteAccess(node), isDefinition: isDeclarationName(node) || isLiteralComputedPropertyDeclarationName(node) }; } /** A node is considered 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 && isDeclarationName(node)) { return true; } const parent = node.parent; if (parent) { if (parent.kind === SyntaxKind.PostfixUnaryExpression || parent.kind === SyntaxKind.PrefixUnaryExpression) { return true; } else if (parent.kind === SyntaxKind.BinaryExpression && (parent).left === node) { const operator = (parent).operatorToken.kind; return SyntaxKind.FirstAssignment <= operator && operator <= SyntaxKind.LastAssignment; } } return false; } function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void) { forEachChild(node, child => { if (child.kind === kind) { action(child); } forEachDescendantOfKind(child, kind, action); }); } /** * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 } */ function getContainingObjectLiteralElement(node: Node): ObjectLiteralElement { switch (node.kind) { case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: if (node.parent.kind === SyntaxKind.ComputedPropertyName) { return isObjectLiteralPropertyDeclaration(node.parent.parent) ? node.parent.parent : undefined; } // intential fall through case SyntaxKind.Identifier: return isObjectLiteralPropertyDeclaration(node.parent) && node.parent.name === node ? node.parent : undefined; } return undefined; } function isObjectLiteralPropertyDeclaration(node: Node): node is ObjectLiteralElement { switch (node.kind) { case SyntaxKind.PropertyAssignment: case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return true; } return false; } /** Get `C` given `N` if `N` is in the position `class C extends N` or `class C extends foo.N` where `N` is an identifier. */ function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent); } function isNameOfExternalModuleImportOrDeclaration(node: Node): boolean { if (node.kind === SyntaxKind.StringLiteral) { return isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node); } return false; } function isImportDefaultSymbol(symbol: Symbol): boolean { return symbol.declarations[0].kind === SyntaxKind.ImportClause; } }