internal/ls/findallreferences.go (1,783 lines of code) (raw):

package ls import ( "cmp" "context" "fmt" "slices" "strings" "sync" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/astnav" "github.com/microsoft/typescript-go/internal/binder" "github.com/microsoft/typescript-go/internal/checker" "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/debug" "github.com/microsoft/typescript-go/internal/ls/lsconv" "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/scanner" "github.com/microsoft/typescript-go/internal/stringutil" "github.com/microsoft/typescript-go/internal/tspath" ) // === types for settings === type referenceUse int const ( referenceUseNone referenceUse = 0 referenceUseOther referenceUse = 1 referenceUseReferences referenceUse = 2 referenceUseRename referenceUse = 3 ) type refOptions struct { findInStrings bool findInComments bool use referenceUse // other, references, rename implementations bool useAliasesForRename bool // renamed from providePrefixAndSuffixTextForRename. default: true } // === types for results === type refInfo struct { file *ast.SourceFile fileName string reference *ast.FileReference unverified bool } type SymbolAndEntries struct { definition *Definition references []*ReferenceEntry } func NewSymbolAndEntries(kind DefinitionKind, node *ast.Node, symbol *ast.Symbol, references []*ReferenceEntry) *SymbolAndEntries { return &SymbolAndEntries{ &Definition{ Kind: kind, node: node, symbol: symbol, }, references, } } type DefinitionKind int const ( definitionKindSymbol DefinitionKind = 0 definitionKindLabel DefinitionKind = 1 definitionKindKeyword DefinitionKind = 2 definitionKindThis DefinitionKind = 3 definitionKindString DefinitionKind = 4 definitionKindTripleSlashReference DefinitionKind = 5 ) type Definition struct { Kind DefinitionKind symbol *ast.Symbol node *ast.Node tripleSlashFileRef *tripleSlashDefinition } type tripleSlashDefinition struct { reference *ast.FileReference file *ast.SourceFile } type entryKind int const ( entryKindNone entryKind = 0 entryKindRange entryKind = 1 entryKindNode entryKind = 2 entryKindStringLiteral entryKind = 3 entryKindSearchedLocalFoundProperty entryKind = 4 entryKindSearchedPropertyFoundLocal entryKind = 5 ) type ReferenceEntry struct { kind entryKind node *ast.Node context *ast.Node // !!! ContextWithStartAndEndNode, optional fileName string textRange *core.TextRange lspRange *lsproto.Location } func (entry *SymbolAndEntries) canUseDefinitionSymbol() bool { if entry.definition == nil { return false } switch entry.definition.Kind { case definitionKindSymbol, definitionKindThis: return entry.definition.symbol != nil case definitionKindTripleSlashReference: // !!! TODO : need to find file reference instead? // May need to return true to indicate this to be file search instead and might need to do for import stuff as well // For now return false default: return false } } func (l *LanguageService) getRangeOfEntry(entry *ReferenceEntry) *lsproto.Range { return &l.resolveEntry(entry).lspRange.Range } func (l *LanguageService) getFileNameOfEntry(entry *ReferenceEntry) lsproto.DocumentUri { return l.resolveEntry(entry).lspRange.Uri } func (l *LanguageService) getLocationOfEntry(entry *ReferenceEntry) *lsproto.Location { return l.resolveEntry(entry).lspRange } func (l *LanguageService) resolveEntry(entry *ReferenceEntry) *ReferenceEntry { if entry.textRange == nil { sourceFile := ast.GetSourceFileOfNode(entry.node) textRange := getRangeOfNode(entry.node, sourceFile, nil /*endNode*/) entry.textRange = &textRange entry.fileName = sourceFile.FileName() } if entry.lspRange == nil { location := l.getMappedLocation(entry.fileName, *entry.textRange) entry.lspRange = &location } return entry } func newNodeEntryWithKind(node *ast.Node, kind entryKind) *ReferenceEntry { e := newNodeEntry(node) e.kind = kind return e } func newNodeEntry(node *ast.Node) *ReferenceEntry { // creates nodeEntry with `kind == entryKindNode` return &ReferenceEntry{ kind: entryKindNode, node: core.OrElse(node.Name(), node), context: getContextNodeForNodeEntry(node), } } func getContextNodeForNodeEntry(node *ast.Node) *ast.Node { if ast.IsDeclaration(node) { return getContextNode(node) } if node.Parent == nil { return nil } if !ast.IsDeclaration(node.Parent) && node.Parent.Kind != ast.KindExportAssignment && node.Parent.Kind != ast.KindJSExportAssignment { // Special property assignment in javascript if ast.IsInJSFile(node) { // !!! jsdoc: check if branch still needed binaryExpression := core.IfElse(node.Parent.Kind == ast.KindBinaryExpression, node.Parent, core.IfElse(ast.IsAccessExpression(node.Parent) && node.Parent.Parent.Kind == ast.KindBinaryExpression && node.Parent.Parent.AsBinaryExpression().Left == node.Parent, node.Parent.Parent, nil)) if binaryExpression != nil && ast.GetAssignmentDeclarationKind(binaryExpression.AsBinaryExpression()) != ast.JSDeclarationKindNone { return getContextNode(binaryExpression) } } // Jsx Tags switch node.Parent.Kind { case ast.KindJsxOpeningElement, ast.KindJsxClosingElement: return node.Parent.Parent case ast.KindJsxSelfClosingElement, ast.KindLabeledStatement, ast.KindBreakStatement, ast.KindContinueStatement: return node.Parent case ast.KindStringLiteral, ast.KindNoSubstitutionTemplateLiteral: if validImport := tryGetImportFromModuleSpecifier(node); validImport != nil { declOrStatement := ast.FindAncestor(validImport, func(*ast.Node) bool { return ast.IsDeclaration(node) || ast.IsStatement(node) || ast.IsJSDocTag(node) }) if ast.IsDeclaration(declOrStatement) { return getContextNode(declOrStatement) } return declOrStatement } } // Handle computed property name propertyName := ast.FindAncestor(node, ast.IsComputedPropertyName) if propertyName != nil { return getContextNode(propertyName.Parent) } return nil } if node.Parent.Name() == node || // node is name of declaration, use parent node.Parent.Kind == ast.KindConstructor || node.Parent.Kind == ast.KindExportAssignment || node.Parent.Kind == ast.KindJSExportAssignment || // Property name of the import export specifier or binding pattern, use parent ((ast.IsImportOrExportSpecifier(node.Parent) || node.Parent.Kind == ast.KindBindingElement) && node.Parent.PropertyName() == node) || // Is default export (node.Kind == ast.KindDefaultKeyword && ast.HasSyntacticModifier(node.Parent, ast.ModifierFlagsExportDefault)) { return getContextNode(node.Parent) } return nil } func getContextNode(node *ast.Node) *ast.Node { if node == nil { return nil } switch node.Kind { case ast.KindVariableDeclaration: if !ast.IsVariableDeclarationList(node.Parent) || len(node.Parent.AsVariableDeclarationList().Declarations.Nodes) != 1 { return node } else if ast.IsVariableStatement(node.Parent.Parent) { return node.Parent.Parent } else if ast.IsForInOrOfStatement(node.Parent.Parent) { return getContextNode(node.Parent.Parent) } return node.Parent case ast.KindBindingElement: return getContextNode(node.Parent.Parent) case ast.KindImportSpecifier: return node.Parent.Parent.Parent case ast.KindExportSpecifier, ast.KindNamespaceImport: return node.Parent.Parent case ast.KindImportClause, ast.KindNamespaceExport: return node.Parent case ast.KindBinaryExpression: return core.IfElse(node.Parent.Kind == ast.KindExpressionStatement, node.Parent, node) case ast.KindForOfStatement, ast.KindForInStatement: // !!! not implemented return nil case ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment: if ast.IsArrayLiteralOrObjectLiteralDestructuringPattern(node.Parent) { return getContextNode(ast.FindAncestor(node.Parent, func(node *ast.Node) bool { return node.Kind == ast.KindBinaryExpression || ast.IsForInOrOfStatement(node) })) } return node case ast.KindSwitchStatement: // !!! not implemented return nil default: return node } } // utils func (l *LanguageService) getLspRangeOfNode(node *ast.Node, sourceFile *ast.SourceFile, endNode *ast.Node) *lsproto.Range { if sourceFile == nil { sourceFile = ast.GetSourceFileOfNode(node) } textRange := getRangeOfNode(node, sourceFile, endNode) return l.createLspRangeFromBounds(textRange.Pos(), textRange.End(), sourceFile) } func getRangeOfNode(node *ast.Node, sourceFile *ast.SourceFile, endNode *ast.Node) core.TextRange { if sourceFile == nil { sourceFile = ast.GetSourceFileOfNode(node) } start := scanner.GetTokenPosOfNode(node, sourceFile, false /*includeJsDoc*/) end := core.IfElse(endNode != nil, endNode, node).End() if ast.IsStringLiteralLike(node) && (end-start) > 2 { if endNode != nil { panic("endNode is not nil for stringLiteralLike") } start += 1 end -= 1 } if endNode != nil && endNode.Kind == ast.KindCaseBlock { end = endNode.Pos() } return core.NewTextRange(start, end) } func isValidReferencePosition(node *ast.Node, searchSymbolName string) bool { switch node.Kind { case ast.KindPrivateIdentifier: // !!! // if (isJSDocMemberName(node.Parent)) { // return true; // } return len(node.Text()) == len(searchSymbolName) case ast.KindIdentifier: return len(node.Text()) == len(searchSymbolName) case ast.KindNoSubstitutionTemplateLiteral, ast.KindStringLiteral: return len(node.Text()) == len(searchSymbolName) && (isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || // !!! object.defineProperty // (ast.IsCallExpression(node.Parent) && ast.IsBindableObjectDefinePropertyCall(node.Parent) && node.Parent.Arguments()[1] == node) || ast.IsImportOrExportSpecifier(node.Parent)) case ast.KindNumericLiteral: return isLiteralNameOfPropertyDeclarationOrIndexAccess(node) && len(node.Text()) == len(searchSymbolName) case ast.KindDefaultKeyword: return len("default") == len(searchSymbolName) } return false } func isForRenameWithPrefixAndSuffixText(options refOptions) bool { return options.use == referenceUseRename && options.useAliasesForRename } func skipPastExportOrImportSpecifierOrUnion(symbol *ast.Symbol, node *ast.Node, checker *checker.Checker, useLocalSymbolForExportSpecifier bool) *ast.Symbol { if node == nil { return nil } parent := node.Parent if parent.Kind == ast.KindExportSpecifier && useLocalSymbolForExportSpecifier { return getLocalSymbolForExportSpecifier(node.AsIdentifier(), symbol, parent.AsExportSpecifier(), checker) } // If the symbol is declared as part of a declaration like `{ type: "a" } | { type: "b" }`, use the property on the union type to get more references. return core.FirstNonNil(symbol.Declarations, func(decl *ast.Node) *ast.Symbol { if decl.Parent == nil { // Ignore UMD module and global merge if symbol.Flags&ast.SymbolFlagsTransient != 0 { return nil } // Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here. panic(fmt.Sprintf("Unexpected symbol at %s: %s", node.Kind.String(), symbol.Name)) } if decl.Parent.Kind == ast.KindTypeLiteral && decl.Parent.Parent.Kind == ast.KindUnionType { return checker.GetPropertyOfType(checker.GetTypeFromTypeNode(decl.Parent.Parent), symbol.Name) } return nil }) } func getSymbolScope(symbol *ast.Symbol) *ast.Node { // If this is the symbol of a named function expression or named class expression, // then named references are limited to its own scope. valueDeclaration := symbol.ValueDeclaration if valueDeclaration != nil && (valueDeclaration.Kind == ast.KindFunctionExpression || valueDeclaration.Kind == ast.KindClassExpression) { return valueDeclaration } if len(symbol.Declarations) == 0 { return nil } declarations := symbol.Declarations // If this is private property or method, the scope is the containing class if symbol.Flags&(ast.SymbolFlagsProperty|ast.SymbolFlagsMethod) != 0 { privateDeclaration := core.Find(declarations, func(d *ast.Node) bool { return ast.HasModifier(d, ast.ModifierFlagsPrivate) || ast.IsPrivateIdentifierClassElementDeclaration(d) }) if privateDeclaration != nil { return ast.FindAncestorKind(privateDeclaration, ast.KindClassDeclaration) } // Else this is a public property and could be accessed from anywhere. return nil } // 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 core.Some(declarations, isObjectBindingElementWithoutPropertyName) { return nil } /* If the symbol has a parent, it's globally visible unless: - It's a private property (handled above). - It's a type parameter. - The parent is an external module: then we should only search in the module (and recurse on the export later). - But if the parent has `export as namespace`, the symbol is globally visible through that namespace. */ exposedByParent := symbol.Parent != nil && symbol.Flags&ast.SymbolFlagsTypeParameter == 0 if exposedByParent && !(checker.IsExternalModuleSymbol(symbol.Parent) && symbol.Parent.GlobalExports == nil) { return nil } var scope *ast.Node for _, declaration := range declarations { container := getContainerNode(declaration) if scope != nil && scope != container { // Different declarations have different containers, bail out return nil } if container == nil || (container.Kind == ast.KindSourceFile && !ast.IsExternalOrCommonJSModule(container.AsSourceFile())) { // This is a global variable and not an external module, any declaration defined // within this scope is visible outside the file return nil } scope = container } // If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.) // For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.: // declare module "a" { export type T = number; } // declare module "b" { import { T } from "a"; export const x: T; } // So we must search the whole source file. (Because we will mark the source file as seen, we we won't return to it when searching for imports.) if exposedByParent { return ast.GetSourceFileOfNode(scope).AsNode() } return scope // TODO: GH#18217 } // === functions on (*ls) === type position struct { uri lsproto.DocumentUri pos lsproto.Position } var _ lsproto.HasTextDocumentPosition = (*position)(nil) func (nld *position) TextDocumentURI() lsproto.DocumentUri { return nld.uri } func (nld *position) TextDocumentPosition() lsproto.Position { return nld.pos } type NonLocalDefinition struct { position GetSourcePosition func() lsproto.HasTextDocumentPosition GetGeneratedPosition func() lsproto.HasTextDocumentPosition } func getFileAndStartPosFromDeclaration(declaration *ast.Node) (*ast.SourceFile, core.TextPos) { file := ast.GetSourceFileOfNode(declaration) name := core.OrElse(ast.GetNameOfDeclaration(declaration), declaration) textRange := getRangeOfNode(name, file, nil /*endNode*/) return file, core.TextPos(textRange.Pos()) } func (l *LanguageService) GetNonLocalDefinition(ctx context.Context, entry *SymbolAndEntries) *NonLocalDefinition { if !entry.canUseDefinitionSymbol() { return nil } program := l.GetProgram() checker, done := program.GetTypeChecker(ctx) defer done() emitResolver := checker.GetEmitResolver() for _, d := range entry.definition.symbol.Declarations { if isDefinitionVisible(emitResolver, d) { file, startPos := getFileAndStartPosFromDeclaration(d) fileName := file.FileName() return &NonLocalDefinition{ position: position{ uri: lsconv.FileNameToDocumentURI(fileName), pos: l.converters.PositionToLineAndCharacter(file, startPos), }, GetSourcePosition: sync.OnceValue(func() lsproto.HasTextDocumentPosition { mapped := l.tryGetSourcePosition(fileName, startPos) if mapped != nil { return &position{ uri: lsconv.FileNameToDocumentURI(mapped.FileName), pos: l.converters.PositionToLineAndCharacter(l.getScript(mapped.FileName), core.TextPos(mapped.Pos)), } } return nil }), GetGeneratedPosition: sync.OnceValue(func() lsproto.HasTextDocumentPosition { mapped := l.tryGetGeneratedPosition(fileName, startPos) if mapped != nil { return &position{ uri: lsconv.FileNameToDocumentURI(mapped.FileName), pos: l.converters.PositionToLineAndCharacter(l.getScript(mapped.FileName), core.TextPos(mapped.Pos)), } } return nil }), } } } return nil } // This is special handling to determine if we should load up more projects and find location in other projects // By default arrows (and such other ast kinds) are not visible as declaration emitter doesnt need them // But we want to handle them specially so that they are visible if their parent is visible func isDefinitionVisible(emitResolver *checker.EmitResolver, declaration *ast.Node) bool { if emitResolver.IsDeclarationVisible(declaration) { return true } if declaration.Parent == nil { return false } // Variable initializers are visible if variable is visible if ast.HasInitializer(declaration.Parent) && declaration.Parent.Initializer() == declaration { return isDefinitionVisible(emitResolver, declaration.Parent) } // Handle some exceptions here like arrow function, members of class and object literal expression which are technically not visible but we want the definition to be determined by its parent switch declaration.Kind { case ast.KindPropertyDeclaration, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindMethodDeclaration: // Private/protected properties/methods are not visible if ast.HasModifier(declaration, ast.ModifierFlagsPrivate) || ast.IsPrivateIdentifier(declaration.Name()) { return false } // Public properties/methods are visible if its parents are visible, so: // falls through fallthrough case ast.KindConstructor, ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment, ast.KindObjectLiteralExpression, ast.KindClassExpression, ast.KindArrowFunction, ast.KindFunctionExpression: return isDefinitionVisible(emitResolver, declaration.Parent) default: return false } } func (l *LanguageService) ForEachOriginalDefinitionLocation( ctx context.Context, entry *SymbolAndEntries, cb func(lsproto.DocumentUri, lsproto.Position), ) { if !entry.canUseDefinitionSymbol() { return } program := l.GetProgram() for _, d := range entry.definition.symbol.Declarations { file, startPos := getFileAndStartPosFromDeclaration(d) fileName := file.FileName() if tspath.IsDeclarationFileName(fileName) { // Map to ts position mapped := l.tryGetSourcePosition(file.FileName(), startPos) if mapped != nil { cb( lsconv.FileNameToDocumentURI(mapped.FileName), l.converters.PositionToLineAndCharacter(l.getScript(mapped.FileName), core.TextPos(mapped.Pos)), ) } } else if program.IsSourceFromProjectReference(l.toPath(fileName)) { cb( lsconv.FileNameToDocumentURI(fileName), l.converters.PositionToLineAndCharacter(file, startPos), ) } } } func (l *LanguageService) ProvideSymbolsAndEntries(ctx context.Context, uri lsproto.DocumentUri, documentPosition lsproto.Position, isRename bool) (*ast.Node, []*SymbolAndEntries, bool) { // `findReferencedSymbols` except only computes the information needed to return reference locations program, sourceFile := l.getProgramAndFile(uri) position := int(l.converters.LineAndCharacterToPosition(sourceFile, documentPosition)) node := astnav.GetTouchingPropertyName(sourceFile, position) if isRename && node.Kind != ast.KindIdentifier { return node, nil, false } var options refOptions if !isRename { options.use = referenceUseReferences } else { options.use = referenceUseRename options.useAliasesForRename = true } return node, l.getReferencedSymbolsForNode(ctx, position, node, program, program.GetSourceFiles(), options, nil), true } func (l *LanguageService) ProvideReferencesFromSymbolAndEntries(ctx context.Context, params *lsproto.ReferenceParams, originalNode *ast.Node, symbolsAndEntries []*SymbolAndEntries) (lsproto.ReferencesResponse, error) { // `findReferencedSymbols` except only computes the information needed to return reference locations locations := core.FlatMap(symbolsAndEntries, func(s *SymbolAndEntries) []lsproto.Location { return l.convertSymbolAndEntriesToLocations(s, params.Context.IncludeDeclaration) }) return lsproto.LocationsOrNull{Locations: &locations}, nil } func (l *LanguageService) ProvideImplementations(ctx context.Context, params *lsproto.ImplementationParams) (lsproto.ImplementationResponse, error) { return l.provideImplementationsEx(ctx, params, provideImplementationsOpts{}) } type provideImplementationsOpts struct { // Force the result to be Location objects. requireLocationsResult bool // Omit node(s) containing the original position. dropOriginNodes bool } func (l *LanguageService) provideImplementationsEx(ctx context.Context, params *lsproto.ImplementationParams, opts provideImplementationsOpts) (lsproto.ImplementationResponse, error) { program, sourceFile := l.getProgramAndFile(params.TextDocument.Uri) position := int(l.converters.LineAndCharacterToPosition(sourceFile, params.Position)) node := astnav.GetTouchingPropertyName(sourceFile, position) var seenNodes collections.Set[*ast.Node] var entries []*ReferenceEntry queue := l.getImplementationReferenceEntries(ctx, program, node, position) for len(queue) != 0 { if ctx.Err() != nil { return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{}, ctx.Err() } entry := queue[0] queue = queue[1:] if !seenNodes.Has(entry.node) { seenNodes.Add(entry.node) if !(opts.dropOriginNodes && entry.node.Loc.ContainsInclusive(position)) { entries = append(entries, entry) } queue = append(queue, l.getImplementationReferenceEntries(ctx, program, entry.node, entry.node.Pos())...) } } if !opts.requireLocationsResult && lsproto.GetClientCapabilities(ctx).TextDocument.Implementation.LinkSupport { links := l.convertEntriesToLocationLinks(entries) return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{DefinitionLinks: &links}, nil } locations := l.convertEntriesToLocations(entries) return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{Locations: &locations}, nil } func (l *LanguageService) getImplementationReferenceEntries(ctx context.Context, program *compiler.Program, node *ast.Node, position int) []*ReferenceEntry { options := refOptions{use: referenceUseReferences, implementations: true} symbolsAndEntries := l.getReferencedSymbolsForNode(ctx, position, node, program, program.GetSourceFiles(), options, nil) return core.FlatMap(symbolsAndEntries, func(s *SymbolAndEntries) []*ReferenceEntry { return s.references }) } func (l *LanguageService) ProvideRenameFromSymbolAndEntries(ctx context.Context, params *lsproto.RenameParams, originalNode *ast.Node, symbolsAndEntries []*SymbolAndEntries) (lsproto.WorkspaceEditOrNull, error) { if originalNode.Kind != ast.KindIdentifier { return lsproto.WorkspaceEditOrNull{}, nil } program := l.GetProgram() entries := core.FlatMap(symbolsAndEntries, func(s *SymbolAndEntries) []*ReferenceEntry { return s.references }) changes := make(map[lsproto.DocumentUri][]*lsproto.TextEdit) checker, done := program.GetTypeChecker(ctx) defer done() for _, entry := range entries { uri := l.getFileNameOfEntry(entry) textEdit := &lsproto.TextEdit{ Range: *l.getRangeOfEntry(entry), NewText: l.getTextForRename(originalNode, entry, params.NewName, checker), } changes[uri] = append(changes[uri], textEdit) } return lsproto.WorkspaceEditOrNull{ WorkspaceEdit: &lsproto.WorkspaceEdit{ Changes: &changes, }, }, nil } func (l *LanguageService) getTextForRename(originalNode *ast.Node, entry *ReferenceEntry, newText string, checker *checker.Checker) string { if entry.kind != entryKindRange && (ast.IsIdentifier(originalNode) || ast.IsStringLiteralLike(originalNode)) { node := entry.node kind := entry.kind parent := node.Parent name := originalNode.Text() isShorthandAssignment := ast.IsShorthandPropertyAssignment(parent) switch { case isShorthandAssignment || (isObjectBindingElementWithoutPropertyName(parent) && parent.Name() == node && parent.AsBindingElement().DotDotDotToken == nil): if kind == entryKindSearchedLocalFoundProperty { return name + ": " + newText } if kind == entryKindSearchedPropertyFoundLocal { return newText + ": " + name } // In `const o = { x }; o.x`, symbolAtLocation at `x` in `{ x }` is the property symbol. // For a binding element `const { x } = o;`, symbolAtLocation at `x` is the property symbol. if isShorthandAssignment { grandParent := parent.Parent if ast.IsObjectLiteralExpression(grandParent) && ast.IsBinaryExpression(grandParent.Parent) && ast.IsModuleExportsAccessExpression(grandParent.Parent.AsBinaryExpression().Left) { return name + ": " + newText } return newText + ": " + name } return name + ": " + newText case ast.IsImportSpecifier(parent) && parent.PropertyName() == nil: // If the original symbol was using this alias, just rename the alias. var originalSymbol *ast.Symbol if ast.IsExportSpecifier(originalNode.Parent) { originalSymbol = checker.GetExportSpecifierLocalTargetSymbol(originalNode.Parent) } else { originalSymbol = checker.GetSymbolAtLocation(originalNode) } if slices.Contains(originalSymbol.Declarations, parent) { return name + " as " + newText } return newText case ast.IsExportSpecifier(parent) && parent.PropertyName() == nil: // If the symbol for the node is same as declared node symbol use prefix text if originalNode == entry.node || checker.GetSymbolAtLocation(originalNode) == checker.GetSymbolAtLocation(entry.node) { return name + " as " + newText } return newText + " as " + name } } return newText } // == functions for conversions == func (l *LanguageService) convertSymbolAndEntriesToLocations(s *SymbolAndEntries, includeDeclarations bool) []lsproto.Location { references := s.references // !!! includeDeclarations if !includeDeclarations && s.definition != nil { references = core.Filter(references, func(entry *ReferenceEntry) bool { return !isDeclarationOfSymbol(entry.node, s.definition.symbol) }) } return l.convertEntriesToLocations(references) } func isDeclarationOfSymbol(node *ast.Node, target *ast.Symbol) bool { if target == nil { return false } var source *ast.Node if decl := ast.GetDeclarationFromName(node); decl != nil { source = decl } else if node.Kind == ast.KindDefaultKeyword { source = node.Parent } else if ast.IsLiteralComputedPropertyDeclarationName(node) { source = node.Parent.Parent } else if node.Kind == ast.KindConstructorKeyword && ast.IsConstructorDeclaration(node.Parent) { source = node.Parent.Parent } // !!! // const commonjsSource = source && isBinaryExpression(source) ? source.left as unknown as Declaration : undefined; return source != nil && core.Some(target.Declarations, func(decl *ast.Node) bool { return decl == source }) } func (l *LanguageService) convertEntriesToLocations(entries []*ReferenceEntry) []lsproto.Location { locations := make([]lsproto.Location, len(entries)) for i, entry := range entries { locations[i] = *l.getLocationOfEntry(entry) } return locations } func (l *LanguageService) convertEntriesToLocationLinks(entries []*ReferenceEntry) []*lsproto.LocationLink { links := make([]*lsproto.LocationLink, len(entries)) for i, entry := range entries { // Get the selection range (the actual reference) targetSelectionRange := &l.getLocationOfEntry(entry).Range targetRange := targetSelectionRange // For entries with nodes, compute ranges directly from the node if entry.node != nil { // Get the context range (broader scope including declaration context) contextTextRange := toContextRange(entry.textRange, l.program.GetSourceFile(entry.fileName), entry.context) if contextTextRange != nil { contextLocation := l.getMappedLocation(entry.fileName, *contextTextRange) targetRange = &contextLocation.Range } } links[i] = &lsproto.LocationLink{ TargetUri: lsconv.FileNameToDocumentURI(entry.fileName), TargetRange: *targetRange, TargetSelectionRange: *targetSelectionRange, } } return links } func (l *LanguageService) mergeReferences(program *compiler.Program, referencesToMerge ...[]*SymbolAndEntries) []*SymbolAndEntries { result := []*SymbolAndEntries{} getSourceFileIndexOfEntry := func(program *compiler.Program, entry *ReferenceEntry) int { var sourceFile *ast.SourceFile if entry.kind == entryKindRange { sourceFile = program.GetSourceFile(entry.fileName) } else { sourceFile = ast.GetSourceFileOfNode(entry.node) } return slices.Index(program.SourceFiles(), sourceFile) } for _, references := range referencesToMerge { if len(references) == 0 { continue } if len(result) == 0 { result = references continue } for _, entry := range references { if entry.definition == nil || entry.definition.Kind != definitionKindSymbol { result = append(result, entry) continue } symbol := entry.definition.symbol refIndex := core.FindIndex(result, func(ref *SymbolAndEntries) bool { return ref.definition != nil && ref.definition.Kind == definitionKindSymbol && ref.definition.symbol == symbol }) if refIndex == -1 { result = append(result, entry) continue } reference := result[refIndex] sortedRefs := append(reference.references, entry.references...) slices.SortStableFunc(sortedRefs, func(entry1, entry2 *ReferenceEntry) int { entry1File := getSourceFileIndexOfEntry(program, entry1) entry2File := getSourceFileIndexOfEntry(program, entry2) if entry1File != entry2File { return cmp.Compare(entry1File, entry2File) } return lsproto.CompareRanges(l.getRangeOfEntry(entry1), l.getRangeOfEntry(entry2)) }) result[refIndex] = &SymbolAndEntries{ definition: reference.definition, references: sortedRefs, } } } return result } // === functions for find all ref implementation === func (l *LanguageService) getReferencedSymbolsForNode(ctx context.Context, position int, node *ast.Node, program *compiler.Program, sourceFiles []*ast.SourceFile, options refOptions, sourceFilesSet *collections.Set[string]) []*SymbolAndEntries { // !!! cancellationToken if sourceFilesSet == nil || sourceFilesSet.Len() == 0 { sourceFilesSet = collections.NewSetWithSizeHint[string](len(sourceFiles)) for _, file := range sourceFiles { sourceFilesSet.Add(file.FileName()) } } if options.use == referenceUseReferences || options.use == referenceUseRename { node = getAdjustedLocation(node, options.use == referenceUseRename, ast.GetSourceFileOfNode(node)) } checker, done := program.GetTypeChecker(ctx) defer done() if node.Kind == ast.KindSourceFile { resolvedRef := getReferenceAtPosition(node.AsSourceFile(), position, program) if resolvedRef == nil || resolvedRef.file == nil { return nil } if moduleSymbol := checker.GetMergedSymbol(resolvedRef.file.Symbol); moduleSymbol != nil { return l.getReferencedSymbolsForModule(ctx, program, moduleSymbol /*excludeImportTypeOfExportEquals*/, false, sourceFiles, sourceFilesSet) } // !!! not implemented // fileIncludeReasons := program.getFileIncludeReasons(); // if (!fileIncludeReasons) { // return nil // } return []*SymbolAndEntries{{ definition: &Definition{Kind: definitionKindTripleSlashReference, tripleSlashFileRef: &tripleSlashDefinition{reference: resolvedRef.reference}}, references: getReferencesForNonModule(resolvedRef.file, program /*fileIncludeReasons,*/), }} } if !options.implementations { // !!! cancellationToken if special := getReferencedSymbolsSpecial(node, sourceFiles); special != nil { return special } } // constructors should use the class symbol, detected by name, if present symbol := checker.GetSymbolAtLocation(core.IfElse(node.Kind == ast.KindConstructor && node.Parent.Name() != nil, node.Parent.Name(), node)) // Could not find a symbol e.g. unknown identifier if symbol == nil { // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. if !options.implementations && ast.IsStringLiteralLike(node) { if isModuleSpecifierLike(node) { // !!! not implemented // fileIncludeReasons := program.GetFileIncludeReasons() // if referencedFile := program.GetResolvedModuleFromModuleSpecifier(node, nil /*sourceFile*/); referencedFile != nil { // return []*SymbolAndEntries{{ // definition: &Definition{Kind: definitionKindString, node: node}, // references: getReferencesForNonModule(referencedFile, program /*fileIncludeReasons,*/), // }} // } // Fall through to string literal references. This is not very likely to return // anything useful, but I guess it's better than nothing, and there's an existing // test that expects this to happen (fourslash/cases/untypedModuleImport.ts). } // !!! not implemented // return getReferencesForStringLiteral(node, sourceFiles, checker) // !!! cancellationToken return nil } return nil } if symbol.Name == ast.InternalSymbolNameExportEquals { return l.getReferencedSymbolsForModule(ctx, program, symbol.Parent, false /*excludeImportTypeOfExportEquals*/, sourceFiles, sourceFilesSet) } moduleReferences := l.getReferencedSymbolsForModuleIfDeclaredBySourceFile(ctx, symbol, program, sourceFiles, checker, options, sourceFilesSet) // !!! cancellationToken if moduleReferences != nil && symbol.Flags&ast.SymbolFlagsTransient == 0 { return moduleReferences } aliasedSymbol := getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker) moduleReferencesOfExportTarget := l.getReferencedSymbolsForModuleIfDeclaredBySourceFile(ctx, aliasedSymbol, program, sourceFiles, checker, options, sourceFilesSet) // !!! cancellationToken references := getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, options) // !!! cancellationToken return l.mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget) } func (l *LanguageService) getReferencedSymbolsForModuleIfDeclaredBySourceFile(ctx context.Context, symbol *ast.Symbol, program *compiler.Program, sourceFiles []*ast.SourceFile, checker *checker.Checker, options refOptions, sourceFilesSet *collections.Set[string]) []*SymbolAndEntries { moduleSourceFileName := "" if symbol == nil || !((symbol.Flags&ast.SymbolFlagsModule != 0) && len(symbol.Declarations) != 0) { return nil } if moduleSourceFile := core.Find(symbol.Declarations, ast.IsSourceFile); moduleSourceFile != nil { moduleSourceFileName = moduleSourceFile.AsSourceFile().FileName() } else { return nil } exportEquals := symbol.Exports[ast.InternalSymbolNameExportEquals] // If exportEquals != nil, we're about to add references to `import("mod")` anyway, so don't double-count them. moduleReferences := l.getReferencedSymbolsForModule(ctx, program, symbol, exportEquals != nil, sourceFiles, sourceFilesSet) if exportEquals == nil || exportEquals.Flags&ast.SymbolFlagsAlias == 0 || !sourceFilesSet.Has(moduleSourceFileName) { return moduleReferences } symbol, _ = checker.ResolveAlias(exportEquals) return l.mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol /*node*/, nil, sourceFiles, sourceFilesSet, checker /*, cancellationToken*/, options)) } func getReferencedSymbolsSpecial(node *ast.Node, sourceFiles []*ast.SourceFile) []*SymbolAndEntries { if isTypeKeyword(node.Kind) { // A void expression (i.e., `void foo()`) is not special, but the `void` type is. if node.Kind == ast.KindVoidKeyword && node.Parent.Kind == ast.KindVoidExpression { return nil } // A modifier readonly (like on a property declaration) is not special; // a readonly type keyword (like `readonly string[]`) is. if node.Kind == ast.KindReadonlyKeyword && !isReadonlyTypeOperator(node) { return nil } // Likewise, when we *are* looking for a special keyword, make sure we // *don't* include readonly member modifiers. return getAllReferencesForKeyword( sourceFiles, node.Kind, // cancellationToken, node.Kind == ast.KindReadonlyKeyword, ) } if ast.IsImportMeta(node.Parent) && node.Parent.Name() == node { return getAllReferencesForImportMeta(sourceFiles) } if node.Kind == ast.KindStaticKeyword && node.Parent.Kind == ast.KindClassStaticBlockDeclaration { return []*SymbolAndEntries{{definition: &Definition{Kind: definitionKindKeyword, node: node}, references: []*ReferenceEntry{newNodeEntry(node)}}} } // Labels if isJumpStatementTarget(node) { // if we have a label definition, look within its statement for references, if not, then // the label is undefined and we have no results.. if labelDefinition := getTargetLabel(node.Parent, node.Text()); labelDefinition != nil { return getLabelReferencesInNode(labelDefinition.Parent, labelDefinition) } return nil } if isLabelOfLabeledStatement(node) { // it is a label definition and not a target, search within the parent labeledStatement return getLabelReferencesInNode(node.Parent, node) } if isThis(node) { return getReferencesForThisKeyword(node, sourceFiles /*, cancellationToken*/) } if node.Kind == ast.KindSuperKeyword { return getReferencesForSuperKeyword(node) } return nil } func getLabelReferencesInNode(container *ast.Node, targetLabel *ast.Node) []*SymbolAndEntries { sourceFile := ast.GetSourceFileOfNode(container) labelName := targetLabel.Text() references := core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, labelName, container), func(node *ast.Node) *ReferenceEntry { // Only pick labels that are either the target label, or have a target that is the target label if node == targetLabel.AsNode() || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) == targetLabel) { return newNodeEntry(node) } return nil }) return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindLabel, targetLabel, nil, references)} } func getReferencesForThisKeyword(thisOrSuperKeyword *ast.Node, sourceFiles []*ast.SourceFile) []*SymbolAndEntries { searchSpaceNode := ast.GetThisContainer(thisOrSuperKeyword, false /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/) // Whether 'this' occurs in a static context within a class. staticFlag := ast.ModifierFlagsStatic isParameterName := func(node *ast.Node) bool { return node.Kind == ast.KindIdentifier && node.Parent.Kind == ast.KindParameter && node.Parent.Name() == node } switch searchSpaceNode.Kind { case ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor: if (searchSpaceNode.Kind == ast.KindMethodDeclaration || searchSpaceNode.Kind == ast.KindMethodSignature) && ast.IsObjectLiteralMethod(searchSpaceNode) { staticFlag &= searchSpaceNode.ModifierFlags() searchSpaceNode = searchSpaceNode.Parent // re-assign to be the owning object literals break } staticFlag &= searchSpaceNode.ModifierFlags() searchSpaceNode = searchSpaceNode.Parent // re-assign to be the owning class case ast.KindSourceFile: if ast.IsExternalModule(searchSpaceNode.AsSourceFile()) || isParameterName(thisOrSuperKeyword) { return nil } case ast.KindFunctionDeclaration, ast.KindFunctionExpression: // 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 nil } filesToSearch := sourceFiles if searchSpaceNode.Kind == ast.KindSourceFile { filesToSearch = []*ast.SourceFile{searchSpaceNode.AsSourceFile()} } references := core.Map( core.FlatMap(filesToSearch, func(sourceFile *ast.SourceFile) []*ast.Node { // cancellationToken.throwIfCancellationRequested(); return core.Filter( getPossibleSymbolReferenceNodes(sourceFile, "this", core.IfElse(searchSpaceNode.Kind == ast.KindSourceFile, sourceFile.AsNode(), searchSpaceNode)), func(node *ast.Node) bool { if !isThis(node) { return false } container := ast.GetThisContainer(node /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/, false) if !ast.CanHaveSymbol(container) { return false } switch searchSpaceNode.Kind { case ast.KindFunctionExpression, ast.KindFunctionDeclaration: return searchSpaceNode.Symbol() == container.Symbol() case ast.KindMethodDeclaration, ast.KindMethodSignature: return ast.IsObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.Symbol() == container.Symbol() case ast.KindClassExpression, ast.KindClassDeclaration, ast.KindObjectLiteralExpression: // Make sure the container belongs to the same class/object literals // and has the appropriate static modifier from the original container. return container.Parent != nil && ast.CanHaveSymbol(container.Parent) && searchSpaceNode.Symbol() == container.Parent.Symbol() && ast.IsStatic(container) == (staticFlag != ast.ModifierFlagsNone) case ast.KindSourceFile: return container.Kind == ast.KindSourceFile && !ast.IsExternalModule(container.AsSourceFile()) && !isParameterName(node) } return false }) }), func(n *ast.Node) *ReferenceEntry { return newNodeEntry(n) }, ) thisParameter := core.FirstNonNil(references, func(ref *ReferenceEntry) *ast.Node { if ref.node.Parent.Kind == ast.KindParameter { return ref.node } return nil }) if thisParameter == nil { thisParameter = thisOrSuperKeyword } return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindThis, thisParameter, searchSpaceNode.Symbol(), references)} } func getReferencesForSuperKeyword(superKeyword *ast.Node) []*SymbolAndEntries { searchSpaceNode := ast.GetSuperContainer(superKeyword, false /*stopOnFunctions*/) if searchSpaceNode == nil { return nil } // Whether 'super' occurs in a static context within a class. staticFlag := ast.ModifierFlagsStatic switch searchSpaceNode.Kind { case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor: staticFlag &= searchSpaceNode.ModifierFlags() searchSpaceNode = searchSpaceNode.Parent // re-assign to be the owning class default: return nil } sourceFile := ast.GetSourceFileOfNode(searchSpaceNode) references := core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), func(node *ast.Node) *ReferenceEntry { if node.Kind != ast.KindSuperKeyword { return nil } container := ast.GetSuperContainer(node, false /*stopOnFunctions*/) // 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 != nil && ast.IsStatic(container) == (staticFlag != ast.ModifierFlagsNone) && container.Parent.Symbol() == searchSpaceNode.Symbol() { return newNodeEntry(node) } return nil }) return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindSymbol, nil, searchSpaceNode.Symbol(), references)} } func getAllReferencesForImportMeta(sourceFiles []*ast.SourceFile) []*SymbolAndEntries { references := core.FlatMap(sourceFiles, func(sourceFile *ast.SourceFile) []*ReferenceEntry { return core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, "meta", sourceFile.AsNode()), func(node *ast.Node) *ReferenceEntry { parent := node.Parent if ast.IsImportMeta(parent) { return newNodeEntry(parent) } return nil }) }) if len(references) == 0 { return nil } return []*SymbolAndEntries{{definition: &Definition{Kind: definitionKindKeyword, node: references[0].node}, references: references}} } func getAllReferencesForKeyword(sourceFiles []*ast.SourceFile, keywordKind ast.Kind, filterReadOnlyTypeOperator bool) []*SymbolAndEntries { // references is a list of NodeEntry references := core.FlatMap(sourceFiles, func(sourceFile *ast.SourceFile) []*ReferenceEntry { // cancellationToken.throwIfCancellationRequested(); return core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, scanner.TokenToString(keywordKind), sourceFile.AsNode()), func(referenceLocation *ast.Node) *ReferenceEntry { if referenceLocation.Kind == keywordKind && (!filterReadOnlyTypeOperator || isReadonlyTypeOperator(referenceLocation)) { return newNodeEntry(referenceLocation) } return nil }) }) if len(references) == 0 { return nil } return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindKeyword, references[0].node, nil, references)} } func getPossibleSymbolReferenceNodes(sourceFile *ast.SourceFile, symbolName string, container *ast.Node) []*ast.Node { return core.MapNonNil(getPossibleSymbolReferencePositions(sourceFile, symbolName, container), func(pos int) *ast.Node { if referenceLocation := astnav.GetTouchingPropertyName(sourceFile, pos); referenceLocation != sourceFile.AsNode() { return referenceLocation } return nil }) } func getPossibleSymbolReferencePositions(sourceFile *ast.SourceFile, symbolName string, container *ast.Node) []int { positions := []int{} /// 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 == "" { return positions } text := sourceFile.Text() sourceLength := len(text) symbolNameLength := len(symbolName) if container == nil { container = sourceFile.AsNode() } position := strings.Index(text[container.Pos():], symbolName) endPos := container.End() for position >= 0 && position < endPos { // 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). endPosition := position + symbolNameLength if (position == 0 || !scanner.IsIdentifierPart(rune(text[position-1]))) && (endPosition == sourceLength || !scanner.IsIdentifierPart(rune(text[endPosition]))) { // Found a real match. Keep searching. positions = append(positions, position) } startIndex := position + symbolNameLength + 1 if startIndex > len(text) { break } if foundIndex := strings.Index(text[startIndex:], symbolName); foundIndex != -1 { position = startIndex + foundIndex } else { break } } return positions } // findFirstJsxNode recursively searches for the first JSX element, self-closing element, or fragment func findFirstJsxNode(root *ast.Node) *ast.Node { var visit func(*ast.Node) *ast.Node visit = func(node *ast.Node) *ast.Node { // Check if this is a JSX node we're looking for switch node.Kind { case ast.KindJsxElement, ast.KindJsxSelfClosingElement, ast.KindJsxFragment: return node } // Skip subtree if it doesn't contain JSX if node.SubtreeFacts()&ast.SubtreeContainsJsx == 0 { return nil } // Traverse children to find JSX node var result *ast.Node node.ForEachChild(func(child *ast.Node) bool { result = visit(child) return result != nil // Stop if found }) return result } return visit(root) } func getReferencesForNonModule(referencedFile *ast.SourceFile, program *compiler.Program) []*ReferenceEntry { // !!! not implemented return []*ReferenceEntry{} } func getMergedAliasedSymbolOfNamespaceExportDeclaration(node *ast.Node, symbol *ast.Symbol, checker *checker.Checker) *ast.Symbol { if node.Parent != nil && node.Parent.Kind == ast.KindNamespaceExportDeclaration { if aliasedSymbol, ok := checker.ResolveAlias(symbol); ok { targetSymbol := checker.GetMergedSymbol(aliasedSymbol) if aliasedSymbol != targetSymbol { return targetSymbol } } } return nil } func (l *LanguageService) getReferencedSymbolsForModule(ctx context.Context, program *compiler.Program, symbol *ast.Symbol, excludeImportTypeOfExportEquals bool, sourceFiles []*ast.SourceFile, sourceFilesSet *collections.Set[string]) []*SymbolAndEntries { debug.Assert(symbol.ValueDeclaration != nil) checker, done := program.GetTypeChecker(ctx) defer done() moduleRefs := findModuleReferences(program, sourceFiles, symbol, checker) references := core.MapNonNil(moduleRefs, func(reference ModuleReference) *ReferenceEntry { switch reference.kind { case ModuleReferenceKindImport: parent := reference.literal.Parent if ast.IsLiteralTypeNode(parent) { importType := parent.Parent if ast.IsImportTypeNode(importType) { importTypeNode := importType.AsImportTypeNode() if excludeImportTypeOfExportEquals && importTypeNode.Qualifier == nil { return nil } } } // import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway. return newNodeEntry(reference.literal) case ModuleReferenceKindImplicit: // For implicit references (e.g., JSX runtime imports), return the first JSX node, // the first statement, or the whole file var rangeNode *ast.Node // Skip the JSX search for tslib imports if reference.literal.Text() != "tslib" { rangeNode = findFirstJsxNode(reference.referencingFile.AsNode()) } if rangeNode == nil { if reference.referencingFile.Statements != nil && len(reference.referencingFile.Statements.Nodes) > 0 { rangeNode = reference.referencingFile.Statements.Nodes[0] } else { rangeNode = reference.referencingFile.AsNode() } } return newNodeEntry(rangeNode) case ModuleReferenceKindReference: return &ReferenceEntry{ kind: entryKindRange, fileName: reference.referencingFile.FileName(), textRange: &reference.ref.TextRange, } } return nil }) // Add references to the module declarations themselves if len(symbol.Declarations) > 0 { for _, decl := range symbol.Declarations { switch decl.Kind { case ast.KindSourceFile: // Don't include the source file itself. (This may not be ideal behavior, but awkward to include an entire file as a reference.) continue case ast.KindModuleDeclaration: if sourceFilesSet.Has(ast.GetSourceFileOfNode(decl).FileName()) { references = append(references, newNodeEntry(decl.AsModuleDeclaration().Name())) } default: // This may be merged with something. debug.Assert(symbol.Flags&ast.SymbolFlagsTransient != 0, "Expected a module symbol to be declared by a SourceFile or ModuleDeclaration.") } } } // Handle export equals declarations exported := symbol.Exports[ast.InternalSymbolNameExportEquals] if exported != nil && len(exported.Declarations) > 0 { for _, decl := range exported.Declarations { sourceFile := ast.GetSourceFileOfNode(decl) if sourceFilesSet.Has(sourceFile.FileName()) { var node *ast.Node // At `module.exports = ...`, reference node is `module` if ast.IsBinaryExpression(decl) && ast.IsPropertyAccessExpression(decl.AsBinaryExpression().Left) { node = decl.AsBinaryExpression().Left.Expression() } else if ast.IsExportAssignment(decl) { // Find the export keyword node = astnav.FindChildOfKind(decl, ast.KindExportKeyword, sourceFile) debug.Assert(node != nil, "Expected to find export keyword") } else { node = ast.GetNameOfDeclaration(decl) if node == nil { node = decl } } references = append(references, newNodeEntry(node)) } } } if len(references) > 0 { return []*SymbolAndEntries{{ definition: &Definition{Kind: definitionKindSymbol, symbol: symbol}, references: references, }} } return []*SymbolAndEntries{} } func getReferenceAtPosition(sourceFile *ast.SourceFile, position int, program *compiler.Program) *refInfo { if referencePath := findReferenceInPosition(sourceFile.ReferencedFiles, position); referencePath != nil { if file := program.GetSourceFileFromReference(sourceFile, referencePath); file != nil { return &refInfo{reference: referencePath, fileName: file.FileName(), file: file, unverified: false} } return nil } if typeReferenceDirective := findReferenceInPosition(sourceFile.TypeReferenceDirectives, position); typeReferenceDirective != nil { if reference := program.GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(typeReferenceDirective, sourceFile); reference != nil { if file := program.GetSourceFile(reference.ResolvedFileName); file != nil { return &refInfo{reference: typeReferenceDirective, fileName: file.FileName(), file: file, unverified: false} } } return nil } if libReferenceDirective := findReferenceInPosition(sourceFile.LibReferenceDirectives, position); libReferenceDirective != nil { if file := program.GetLibFileFromReference(libReferenceDirective); file != nil { return &refInfo{reference: libReferenceDirective, fileName: file.FileName(), file: file, unverified: false} } return nil } if len(sourceFile.Imports()) == 0 && len(sourceFile.ModuleAugmentations) == 0 { return nil } node := astnav.GetTouchingToken(sourceFile, position) if !isModuleSpecifierLike(node) || !tspath.IsExternalModuleNameRelative(node.Text()) { return nil } if resolution := program.GetResolvedModuleFromModuleSpecifier(sourceFile, node); resolution != nil { verifiedFileName := resolution.ResolvedFileName fileName := resolution.ResolvedFileName if fileName == "" { fileName = tspath.ResolvePath(tspath.GetDirectoryPath(sourceFile.FileName()), node.Text()) } return &refInfo{ file: program.GetSourceFile(fileName), fileName: fileName, reference: nil, unverified: verifiedFileName != "", } } return nil } // -- Core algorithm for find all references -- func getSpecialSearchKind(node *ast.Node) string { if node == nil { return "none" } switch node.Kind { case ast.KindConstructor, ast.KindConstructorKeyword: return "constructor" case ast.KindIdentifier: if ast.IsClassLike(node.Parent) { debug.Assert(node.Parent.Name() == node) return "class" } fallthrough default: return "none" } } func getReferencedSymbolsForSymbol(originalSymbol *ast.Symbol, node *ast.Node, sourceFiles []*ast.SourceFile, sourceFilesSet *collections.Set[string], checker *checker.Checker, options refOptions) []*SymbolAndEntries { // Core find-all-references algorithm for a normal symbol. symbol := core.Coalesce(skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker /*useLocalSymbolForExportSpecifier*/, !isForRenameWithPrefixAndSuffixText(options)), originalSymbol) // Compute the meaning from the location and the symbol it references searchMeaning := ast.SemanticMeaningAll if options.use != referenceUseRename { searchMeaning = getIntersectingMeaningFromDeclarations(node, symbol, ast.SemanticMeaningAll) } state := newState(sourceFiles, sourceFilesSet, node, checker /*, cancellationToken*/, searchMeaning, options) var exportSpecifier *ast.Node if !isForRenameWithPrefixAndSuffixText(options) || len(symbol.Declarations) == 0 { exportSpecifier = core.Find(symbol.Declarations, ast.IsExportSpecifier) } if exportSpecifier != nil { // !!! not implemented // When renaming at an export specifier, rename the export and not the thing being exported. // state.getReferencesAtExportSpecifier(exportSpecifier.Name(), symbol, exportSpecifier.AsExportSpecifier(), state.createSearch(node, originalSymbol, comingFromUnknown /*comingFrom*/, "", nil), true /*addReferencesHere*/, true /*alwaysGetReferences*/) } else if node != nil && node.Kind == ast.KindDefaultKeyword && symbol.Name == ast.InternalSymbolNameDefault && symbol.Parent != nil { state.addReference(node, symbol, entryKindNode) state.searchForImportsOfExport(node, symbol, &ExportInfo{exportingModuleSymbol: symbol.Parent, exportKind: ExportKindDefault}) } else { search := state.createSearch(node, symbol, ImpExpKindUnknown /*comingFrom*/, "", state.populateSearchSymbolSet(symbol, node, options.use == referenceUseRename, options.useAliasesForRename, options.implementations)) state.getReferencesInContainerOrFiles(symbol, search) } return state.result } // Symbol that is currently being searched for. // This will be replaced if we find an alias for the symbol. type refSearch struct { // If coming from an export, we will not recursively search for the imported symbol (since that's where we came from). comingFrom ImpExpKind // import, export symbol *ast.Symbol text string escapedText string // Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access. parents []*ast.Symbol allSearchSymbols []*ast.Symbol // Whether a symbol is in the search set. // Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`. includes func(symbol *ast.Symbol) bool } type inheritKey struct { symbol *ast.Symbol parent *ast.Symbol } type refState struct { sourceFiles []*ast.SourceFile sourceFilesSet *collections.Set[string] specialSearchKind string // "none", "constructor", or "class" checker *checker.Checker // cancellationToken CancellationToken searchMeaning ast.SemanticMeaning options refOptions result []*SymbolAndEntries inheritsFromCache map[inheritKey]bool seenContainingTypeReferences collections.Set[*ast.Node] // node seen tracker // seenReExportRHS *collections.Set[*ast.Node] // node seen tracker importTracker ImportTracker symbolToReferences map[*ast.Symbol]*SymbolAndEntries sourceFileToSeenSymbols map[*ast.SourceFile]*collections.Set[*ast.Symbol] } func newState(sourceFiles []*ast.SourceFile, sourceFilesSet *collections.Set[string], node *ast.Node, checker *checker.Checker, searchMeaning ast.SemanticMeaning, options refOptions) *refState { return &refState{ sourceFiles: sourceFiles, sourceFilesSet: sourceFilesSet, specialSearchKind: getSpecialSearchKind(node), checker: checker, searchMeaning: searchMeaning, options: options, inheritsFromCache: map[inheritKey]bool{}, // seenReExportRHS: &collections.Set[*ast.Node]{}, symbolToReferences: map[*ast.Symbol]*SymbolAndEntries{}, sourceFileToSeenSymbols: map[*ast.SourceFile]*collections.Set[*ast.Symbol]{}, } } func (state *refState) includesSourceFile(sourceFile *ast.SourceFile) bool { return state.sourceFilesSet.Has(sourceFile.FileName()) } func (state *refState) getImportSearches(exportSymbol *ast.Symbol, exportInfo *ExportInfo) *ImportsResult { if state.importTracker == nil { state.importTracker = createImportTracker(state.sourceFiles, state.sourceFilesSet, state.checker) } return state.importTracker(exportSymbol, exportInfo, state.options.use == referenceUseRename) } // @param allSearchSymbols set of additional symbols for use by `includes` func (state *refState) createSearch(location *ast.Node, symbol *ast.Symbol, comingFrom ImpExpKind, text string, allSearchSymbols []*ast.Symbol) *refSearch { // Note: if this is an external module symbol, the name doesn't include quotes. // Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`. // The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form // here appears to be intentional). if text == "" { s := binder.GetLocalSymbolForExportDefault(symbol) if s == nil { s = getNonModuleSymbolOfMergedModuleSymbol(symbol) if s == nil { s = symbol } } text = stringutil.StripQuotes(ast.SymbolName(s)) } if len(allSearchSymbols) == 0 { allSearchSymbols = []*ast.Symbol{symbol} } search := &refSearch{ symbol: symbol, comingFrom: comingFrom, text: text, escapedText: text, allSearchSymbols: allSearchSymbols, includes: func(sym *ast.Symbol) bool { return slices.Contains(allSearchSymbols, sym) }, } if state.options.implementations && location != nil { search.parents = getParentSymbolsOfPropertyAccess(location, symbol, state.checker) } return search } func (state *refState) referenceAdder(searchSymbol *ast.Symbol) func(*ast.Node, entryKind) { symbolAndEntries := state.symbolToReferences[searchSymbol] if symbolAndEntries == nil { symbolAndEntries = NewSymbolAndEntries(definitionKindSymbol, nil, searchSymbol, nil) state.symbolToReferences[searchSymbol] = symbolAndEntries state.result = append(state.result, symbolAndEntries) } return func(node *ast.Node, kind entryKind) { symbolAndEntries.references = append(symbolAndEntries.references, newNodeEntryWithKind(node, kind)) } } func (state *refState) addReference(referenceLocation *ast.Node, symbol *ast.Symbol, kind entryKind) { // if rename symbol from default export anonymous function, for example `export default function() {}`, we do not need to add reference if state.options.use == referenceUseRename && referenceLocation.Kind == ast.KindDefaultKeyword { return } addRef := state.referenceAdder(symbol) if state.options.implementations { state.addImplementationReferences(referenceLocation, func(n *ast.Node) { addRef(n, kind) }) } else { addRef(referenceLocation, kind) } } func getReferenceEntriesForShorthandPropertyAssignment(node *ast.Node, checker *checker.Checker, addReference func(*ast.Node)) { refSymbol := checker.GetSymbolAtLocation(node) if refSymbol == nil || refSymbol.ValueDeclaration == nil { return } shorthandSymbol := checker.GetShorthandAssignmentValueSymbol(refSymbol.ValueDeclaration) if shorthandSymbol != nil && len(shorthandSymbol.Declarations) > 0 { for _, declaration := range shorthandSymbol.Declarations { if ast.GetMeaningFromDeclaration(declaration)&ast.SemanticMeaningValue != 0 { addReference(declaration) } } } } func isMethodOrAccessor(node *ast.Node) bool { return node.Kind == ast.KindMethodDeclaration || node.Kind == ast.KindGetAccessor || node.Kind == ast.KindSetAccessor } func tryGetClassByExtendingIdentifier(node *ast.Node) *ast.ClassLikeDeclaration { return ast.TryGetClassExtendingExpressionWithTypeArguments(ast.ClimbPastPropertyAccess(node).Parent) } func getClassConstructorSymbol(classSymbol *ast.Symbol) *ast.Symbol { if classSymbol.Members == nil { return nil } return classSymbol.Members[ast.InternalSymbolNameConstructor] } func hasOwnConstructor(classDeclaration *ast.ClassLikeDeclaration) bool { return getClassConstructorSymbol(classDeclaration.Symbol()) != nil } func findOwnConstructorReferences(classSymbol *ast.Symbol, sourceFile *ast.SourceFile, addNode func(*ast.Node)) { constructorSymbol := getClassConstructorSymbol(classSymbol) if constructorSymbol != nil && len(constructorSymbol.Declarations) > 0 { for _, decl := range constructorSymbol.Declarations { if decl.Kind == ast.KindConstructor { if ctrKeyword := astnav.FindChildOfKind(decl, ast.KindConstructorKeyword, sourceFile); ctrKeyword != nil { addNode(ctrKeyword) } } } } if classSymbol.Exports != nil { for _, member := range classSymbol.Exports { decl := member.ValueDeclaration if decl != nil && decl.Kind == ast.KindMethodDeclaration { body := decl.Body() if body != nil { forEachDescendantOfKind(body, ast.KindThisKeyword, func(thisKeyword *ast.Node) { if ast.IsNewExpressionTarget(thisKeyword, false, false) { addNode(thisKeyword) } }) } } } } } func findSuperConstructorAccesses(classDeclaration *ast.ClassLikeDeclaration, addNode func(*ast.Node)) { constructorSymbol := getClassConstructorSymbol(classDeclaration.Symbol()) if constructorSymbol == nil || len(constructorSymbol.Declarations) == 0 { return } for _, decl := range constructorSymbol.Declarations { if decl.Kind == ast.KindConstructor { body := decl.Body() if body != nil { forEachDescendantOfKind(body, ast.KindSuperKeyword, func(node *ast.Node) { if ast.IsCallExpressionTarget(node, false, false) { addNode(node) } }) } } } } func forEachDescendantOfKind(node *ast.Node, kind ast.Kind, action func(*ast.Node)) { node.ForEachChild(func(child *ast.Node) bool { if child.Kind == kind { action(child) } forEachDescendantOfKind(child, kind, action) return false }) } func (state *refState) addImplementationReferences(refNode *ast.Node, addRef func(*ast.Node)) { // Check if we found a function/propertyAssignment/method with an implementation or initializer if ast.IsDeclarationName(refNode) && isImplementation(refNode.Parent) { addRef(refNode) return } if refNode.Kind != ast.KindIdentifier { return } if refNode.Parent.Kind == ast.KindShorthandPropertyAssignment { // Go ahead and dereference the shorthand assignment by going to its definition getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addRef) } // Check if the node is within an extends or implements clause if containingNode := getContainingNodeIfInHeritageClause(refNode); containingNode != nil { addRef(containingNode) return } // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface // Find the first node whose parent isn't a type node -- i.e., the highest type node. typeNode := ast.FindAncestor(refNode, func(a *ast.Node) bool { return !ast.IsQualifiedName(a.Parent) && !ast.IsTypeNode(a.Parent) && !ast.IsTypeElement(a.Parent) }) if typeNode == nil || typeNode.Parent.Type() == nil { return } typeHavingNode := typeNode.Parent if typeHavingNode.Type() == typeNode && !state.seenContainingTypeReferences.AddIfAbsent(typeHavingNode) { addIfImplementation := func(e *ast.Expression) { if isImplementationExpression(e) { addRef(e) } } if ast.HasInitializer(typeHavingNode) { addIfImplementation(typeHavingNode.Initializer()) } else if ast.IsFunctionLike(typeHavingNode) && typeHavingNode.Body() != nil { body := typeHavingNode.Body() if body.Kind == ast.KindBlock { ast.ForEachReturnStatement(body, func(returnStatement *ast.Node) bool { if expr := returnStatement.Expression(); expr != nil { addIfImplementation(expr) } return false }) } else { addIfImplementation(body) } } else if ast.IsAssertionExpression(typeHavingNode) || ast.IsSatisfiesExpression(typeHavingNode) { addIfImplementation(typeHavingNode.Expression()) } } } func (state *refState) getReferencesInContainerOrFiles(symbol *ast.Symbol, search *refSearch) { // 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). if scope := getSymbolScope(symbol); scope != nil { addReferencesHere := scope.Kind != ast.KindSourceFile || slices.Contains(state.sourceFiles, scope.AsSourceFile()) state.getReferencesInContainer(scope, ast.GetSourceFileOfNode(scope), search, addReferencesHere) } else { // Global search for _, sourceFile := range state.sourceFiles { // state.cancellationToken.throwIfCancellationRequested(); state.searchForName(sourceFile, search) } } } func (state *refState) getReferencesInSourceFile(sourceFile *ast.SourceFile, search *refSearch, addReferencesHere bool) { // state.cancellationToken.throwIfCancellationRequested(); state.getReferencesInContainer(sourceFile.AsNode(), sourceFile, search, addReferencesHere) } func (state *refState) getReferencesInContainer(container *ast.Node, sourceFile *ast.SourceFile, search *refSearch, addReferencesHere bool) { // 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 if !state.markSearchedSymbols(sourceFile, search.allSearchSymbols) { return } for _, position := range getPossibleSymbolReferencePositions(sourceFile, search.text, container) { state.getReferencesAtLocation(sourceFile, position, search, addReferencesHere) } } func (state *refState) markSearchedSymbols(sourceFile *ast.SourceFile, symbols []*ast.Symbol) bool { seenSymbols := state.sourceFileToSeenSymbols[sourceFile] if seenSymbols == nil { seenSymbols = &collections.Set[*ast.Symbol]{} state.sourceFileToSeenSymbols[sourceFile] = seenSymbols } anyNewSymbols := false for _, sym := range symbols { if seenSymbols.AddIfAbsent(sym) { anyNewSymbols = true } } return anyNewSymbols } func (state *refState) getReferencesAtLocation(sourceFile *ast.SourceFile, position int, search *refSearch, addReferencesHere bool) { referenceLocation := astnav.GetTouchingPropertyName(sourceFile, position) if !isValidReferencePosition(referenceLocation, search.text) { // 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. // !!! not implemented // if (!state.options.implementations && (state.options.findInStrings && isInString(sourceFile, position) || state.options.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. // state.addStringOrCommentReference(sourceFile.FileName, createTextSpan(position, search.text.length)); // } return } if getMeaningFromLocation(referenceLocation)&state.searchMeaning == 0 { return } referenceSymbol := state.checker.GetSymbolAtLocation(referenceLocation) if referenceSymbol == nil { return } parent := referenceLocation.Parent if parent.Kind == ast.KindImportSpecifier && parent.PropertyName() == referenceLocation { // This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again. return } if parent.Kind == ast.KindExportSpecifier { // !!! not implemented // debug.Assert(referenceLocation.Kind == ast.KindIdentifier || referenceLocation.Kind == ast.KindStringLiteral) // state.getReferencesAtExportSpecifier(referenceLocation /* Identifier | StringLiteral*/, referenceSymbol, parent.AsExportSpecifier(), search, addReferencesHere, false /*alwaysGetReferences*/) return } relatedSymbol, relatedSymbolKind := state.getRelatedSymbol(search, referenceSymbol, referenceLocation) if relatedSymbol == nil { state.getReferenceForShorthandProperty(referenceSymbol, search) return } switch state.specialSearchKind { case "none": if addReferencesHere { state.addReference(referenceLocation, relatedSymbol, relatedSymbolKind) } case "constructor": state.addConstructorReferences(referenceLocation, relatedSymbol, search, addReferencesHere) case "class": state.addClassStaticThisReferences(referenceLocation, relatedSymbol, search, addReferencesHere) } // Use the parent symbol if the location is commonjs require syntax on javascript files only. if ast.IsInJSFile(referenceLocation) && referenceLocation.Parent.Kind == ast.KindBindingElement && ast.IsVariableDeclarationInitializedToRequire(referenceLocation.Parent.Parent.Parent) { referenceSymbol = referenceLocation.Parent.Symbol() // The parent will not have a symbol if it's an ObjectBindingPattern (when destructuring is used). In // this case, just skip it, since the bound identifiers are not an alias of the import. if referenceSymbol == nil { return } } state.getImportOrExportReferences(referenceLocation, referenceSymbol, search) } func (state *refState) addConstructorReferences(referenceLocation *ast.Node, symbol *ast.Symbol, search *refSearch, addReferencesHere bool) { if ast.IsNewExpressionTarget(referenceLocation, false, false) && addReferencesHere { state.addReference(referenceLocation, symbol, entryKindNode) } pusher := func() func(*ast.Node, entryKind) { return state.referenceAdder(search.symbol) } if ast.IsClassLike(referenceLocation.Parent) { // This is the class declaration containing the constructor. sourceFile := ast.GetSourceFileOfNode(referenceLocation) findOwnConstructorReferences(search.symbol, sourceFile, func(n *ast.Node) { pusher()(n, entryKindNode) }) } else { // If this class appears in `extends C`, then the extending class' "super" calls are references. if classExtending := tryGetClassByExtendingIdentifier(referenceLocation); classExtending != nil { findSuperConstructorAccesses(classExtending, func(n *ast.Node) { pusher()(n, entryKindNode) }) state.findInheritedConstructorReferences(classExtending) } } } func (state *refState) addClassStaticThisReferences(referenceLocation *ast.Node, symbol *ast.Symbol, search *refSearch, addReferencesHere bool) { if addReferencesHere { state.addReference(referenceLocation, symbol, entryKindNode) } classLike := referenceLocation.Parent if state.options.use == referenceUseRename || !ast.IsClassLike(classLike) { return } addRef := state.referenceAdder(search.symbol) members := classLike.Members() if members == nil { return } for _, member := range members { if !(isMethodOrAccessor(member) && ast.HasStaticModifier(member)) { continue } body := member.Body() if body != nil { var cb func(*ast.Node) cb = func(node *ast.Node) { if node.Kind == ast.KindThisKeyword { addRef(node, entryKindNode) } else if !ast.IsFunctionLike(node) && !ast.IsClassLike(node) { node.ForEachChild(func(child *ast.Node) bool { cb(child) return false }) } } cb(body) } } } func (state *refState) findInheritedConstructorReferences(classDeclaration *ast.ClassLikeDeclaration) { if hasOwnConstructor(classDeclaration) { return } classSymbol := classDeclaration.Symbol() search := state.createSearch(nil, classSymbol, ImpExpKindUnknown, "", nil) state.getReferencesInContainerOrFiles(classSymbol, search) } func (state *refState) getImportOrExportReferences(referenceLocation *ast.Node, referenceSymbol *ast.Symbol, search *refSearch) { importOrExport := getImportOrExportSymbol(referenceLocation, referenceSymbol, state.checker, search.comingFrom == ImpExpKindExport) if importOrExport == nil { return } if importOrExport.kind == ImpExpKindImport { if !isForRenameWithPrefixAndSuffixText(state.options) { state.searchForImportedSymbol(importOrExport.symbol) } } else { state.searchForImportsOfExport(referenceLocation, importOrExport.symbol, importOrExport.exportInfo) } } // Go to the symbol we imported from and find references for it. func (state *refState) searchForImportedSymbol(symbol *ast.Symbol) { for _, declaration := range symbol.Declarations { exportingFile := ast.GetSourceFileOfNode(declaration) // Need to search in the file even if it's not in the search-file set, because it might export the symbol. state.getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImpExpKindImport, "", nil), state.includesSourceFile(exportingFile)) } } // Search for all imports of a given exported symbol using `State.getImportSearches`. */ func (state *refState) searchForImportsOfExport(exportLocation *ast.Node, exportSymbol *ast.Symbol, exportInfo *ExportInfo) { r := state.getImportSearches(exportSymbol, exportInfo) // For `import { foo as bar }` just add the reference to `foo`, and don't otherwise search in the file. if len(r.singleReferences) != 0 { addRef := state.referenceAdder(exportSymbol) for _, singleRef := range r.singleReferences { if state.shouldAddSingleReference(singleRef) { addRef(singleRef, entryKindNode) } } } // For each import, find all references to that import in its source file. for _, i := range r.importSearches { state.getReferencesInSourceFile(ast.GetSourceFileOfNode(i.importLocation), state.createSearch(i.importLocation, i.importSymbol, ImpExpKindExport, "", nil), true /*addReferencesHere*/) } if len(r.indirectUsers) != 0 { var indirectSearch *refSearch switch exportInfo.exportKind { case ExportKindNamed: indirectSearch = state.createSearch(exportLocation, exportSymbol, ImpExpKindExport, "", nil) case ExportKindDefault: // Search for a property access to '.default'. This can't be renamed. if state.options.use != referenceUseRename { indirectSearch = state.createSearch(exportLocation, exportSymbol, ImpExpKindExport, "default", nil) } } if indirectSearch != nil { for _, indirectUser := range r.indirectUsers { state.searchForName(indirectUser, indirectSearch) } } } } func (state *refState) shouldAddSingleReference(singleRef *ast.Node) bool { if !state.hasMatchingMeaning(singleRef) { return false } if state.options.use != referenceUseRename { return true } // Don't rename an import type `import("./module-name")` when renaming `name` in `export = name;` if !ast.IsIdentifier(singleRef) && !ast.IsImportOrExportSpecifier(singleRef.Parent) { return false } // At `default` in `import { default as x }` or `export { default as x }`, do add a reference, but do not rename. return !(ast.IsImportOrExportSpecifier(singleRef.Parent) && ast.ModuleExportNameIsDefault(singleRef)) } func (state *refState) hasMatchingMeaning(referenceLocation *ast.Node) bool { return getMeaningFromLocation(referenceLocation)&state.searchMeaning != 0 } func (state *refState) getReferenceForShorthandProperty(referenceSymbol *ast.Symbol, search *refSearch) { if referenceSymbol.Flags&ast.SymbolFlagsTransient != 0 || referenceSymbol.ValueDeclaration == nil { return } shorthandValueSymbol := state.checker.GetShorthandAssignmentValueSymbol(referenceSymbol.ValueDeclaration) name := ast.GetNameOfDeclaration(referenceSymbol.ValueDeclaration) // 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. if name != nil && search.includes(shorthandValueSymbol) { state.addReference(name, shorthandValueSymbol, entryKindNode) } } // === search === func (state *refState) populateSearchSymbolSet(symbol *ast.Symbol, location *ast.Node, isForRename, providePrefixAndSuffixText, implementations bool) []*ast.Symbol { if location == nil { return []*ast.Symbol{symbol} } result := []*ast.Symbol{} state.forEachRelatedSymbol( symbol, location, isForRename, !(isForRename && providePrefixAndSuffixText), func(sym *ast.Symbol, root *ast.Symbol, base *ast.Symbol) *ast.Symbol { // static method/property and instance method/property might have the same name. Only include static or only include instance. if base != nil { if isStaticSymbol(symbol) != isStaticSymbol(base) { base = nil } } result = append(result, core.OrElse(base, core.OrElse(root, sym))) return nil }, // when try to find implementation, implementations is true, and not allowed to find base class /*allowBaseTypes*/ func(_ *ast.Symbol) bool { return !implementations }, ) return result } func (state *refState) getRelatedSymbol(search *refSearch, referenceSymbol *ast.Symbol, referenceLocation *ast.Node) (*ast.Symbol, entryKind) { return state.forEachRelatedSymbol( referenceSymbol, referenceLocation, false, /*isForRenamePopulateSearchSymbolSet*/ state.options.use != referenceUseRename || state.options.useAliasesForRename, /*onlyIncludeBindingElementAtReferenceLocation*/ func(sym *ast.Symbol, rootSymbol *ast.Symbol, baseSymbol *ast.Symbol) *ast.Symbol { // check whether the symbol used to search itself is just the searched one. if baseSymbol != nil { // static method/property and instance method/property might have the same name. Only check static or only check instance. if isStaticSymbol(referenceSymbol) != isStaticSymbol(baseSymbol) { baseSymbol = nil } } searchSym := core.Coalesce(baseSymbol, core.Coalesce(rootSymbol, sym)) if searchSym != nil && search.includes(searchSym) { if rootSymbol != nil && sym.CheckFlags&ast.CheckFlagsSynthetic == 0 { return rootSymbol } return sym } // For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol. return nil }, func(rootSymbol *ast.Symbol) bool { return !(len(search.parents) != 0 && !core.Some(search.parents, func(parent *ast.Symbol) bool { return state.explicitlyInheritsFrom(rootSymbol.Parent, parent) })) }, ) } func (state *refState) forEachRelatedSymbol( symbol *ast.Symbol, location *ast.Node, isForRenamePopulateSearchSymbolSet, onlyIncludeBindingElementAtReferenceLocation bool, cbSymbol func(*ast.Symbol, *ast.Symbol, *ast.Symbol) *ast.Symbol, allowBaseTypes func(*ast.Symbol) bool, ) (*ast.Symbol, entryKind) { fromRoot := func(sym *ast.Symbol) *ast.Symbol { // If this is a union property: // - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types. // - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search. // If the symbol is an instantiation from a another symbol (e.g. widened symbol): // - In populateSearchSymbolsSet, add the root the list // - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.) for _, rootSymbol := range state.checker.GetRootSymbols(sym) { if result := cbSymbol(sym, rootSymbol, nil /*baseSymbol*/); result != nil { return result } // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions if rootSymbol.Parent != nil && rootSymbol.Parent.Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0 && allowBaseTypes(rootSymbol) { result := getPropertySymbolsFromBaseTypes(rootSymbol.Parent, rootSymbol.Name, state.checker, func(base *ast.Symbol) *ast.Symbol { return cbSymbol(sym, rootSymbol, base) }) if result != nil { return result } } } return nil } if containingObjectLiteralElement := getContainingObjectLiteralElement(location); containingObjectLiteralElement != nil { /* 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. */ shorthandValueSymbol := state.checker.GetShorthandAssignmentValueSymbol(location.Parent) // gets the local symbol if shorthandValueSymbol != nil && isForRenamePopulateSearchSymbolSet { // When renaming 'x' in `const o = { x }`, just rename the local variable, not the property. return cbSymbol(shorthandValueSymbol, nil /*rootSymbol*/, nil /*baseSymbol*/), entryKindSearchedLocalFoundProperty } // 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 contextualType := state.checker.GetContextualType(containingObjectLiteralElement.Parent, checker.ContextFlagsNone); contextualType != nil { symbols := state.checker.GetPropertySymbolsFromContextualType(containingObjectLiteralElement, contextualType, true /*unionSymbolOk*/) for _, sym := range symbols { if res := fromRoot(sym); res != nil { return res, entryKindSearchedPropertyFoundLocal } } } // If the location is name of property symbol from object literal destructuring pattern // Search the property symbol // for ( { property: p2 } of elems) { } if propertySymbol := state.checker.GetPropertySymbolOfDestructuringAssignment(location); propertySymbol != nil { if res := cbSymbol(propertySymbol, nil /*rootSymbol*/, nil /*baseSymbol*/); res != nil { return res, entryKindSearchedPropertyFoundLocal } } if shorthandValueSymbol != nil { if res := cbSymbol(shorthandValueSymbol, nil /*rootSymbol*/, nil /*baseSymbol*/); res != nil { return res, entryKindSearchedLocalFoundProperty } } } if aliasedSymbol := getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, state.checker); aliasedSymbol != nil { // In case of UMD module and global merging, search for global as well if res := cbSymbol(aliasedSymbol, nil /*rootSymbol*/, nil /*baseSymbol*/); res != nil { return res, entryKindNode } } if res := fromRoot(symbol); res != nil { return res, entryKindNode } if symbol.ValueDeclaration != nil && ast.IsParameterPropertyDeclaration(symbol.ValueDeclaration, symbol.ValueDeclaration.Parent) { // For a parameter property, now try on the other symbol (property if this was a parameter, parameter if this was a property). if symbol.ValueDeclaration == nil || symbol.ValueDeclaration.Kind != ast.KindParameter { panic("expected symbol.ValueDeclaration to be a parameter") } paramProp1, paramProp2 := state.checker.GetSymbolsOfParameterPropertyDeclaration(symbol.ValueDeclaration, symbol.Name) debug.Assert((paramProp1.Flags&ast.SymbolFlagsFunctionScopedVariable != 0) && (paramProp2.Flags&ast.SymbolFlagsProperty != 0)) // is [parameter, property] if !(paramProp1.Flags&ast.SymbolFlagsFunctionScopedVariable != 0 && paramProp2.Flags&ast.SymbolFlagsProperty != 0) { panic("Expected a parameter and a property") } return fromRoot(core.IfElse(symbol.Flags&ast.SymbolFlagsFunctionScopedVariable != 0, paramProp2, paramProp1)), entryKindNode } if exportSpecifier := ast.GetDeclarationOfKind(symbol, ast.KindExportSpecifier); exportSpecifier != nil && (!isForRenamePopulateSearchSymbolSet || exportSpecifier.PropertyName() == nil) { if localSymbol := state.checker.GetExportSpecifierLocalTargetSymbol(exportSpecifier); localSymbol != nil { if res := cbSymbol(localSymbol, nil /*rootSymbol*/, nil /*baseSymbol*/); res != nil { return res, entryKindNode } } } // symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property. // Don't do this when populating search set for a rename when prefix and suffix text will be provided -- just rename the local. if !isForRenamePopulateSearchSymbolSet { var bindingElementPropertySymbol *ast.Symbol if onlyIncludeBindingElementAtReferenceLocation { if !isObjectBindingElementWithoutPropertyName(location.Parent) { return nil, entryKindNone } bindingElementPropertySymbol = getPropertySymbolFromBindingElement(state.checker, location.Parent) } else { bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, state.checker) } if bindingElementPropertySymbol == nil { return nil, entryKindNone } return fromRoot(bindingElementPropertySymbol), entryKindSearchedPropertyFoundLocal } debug.Assert(isForRenamePopulateSearchSymbolSet) // due to the above assert and the arguments at the uses of this function, // (onlyIncludeBindingElementAtReferenceLocation <=> !providePrefixAndSuffixTextForRename) holds includeOriginalSymbolOfBindingElement := onlyIncludeBindingElementAtReferenceLocation if includeOriginalSymbolOfBindingElement { if bindingElementPropertySymbol := getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, state.checker); bindingElementPropertySymbol != nil { return fromRoot(bindingElementPropertySymbol), entryKindSearchedPropertyFoundLocal } } return nil, entryKindNone } // Search for all occurrences of an identifier in a source file (and filter out the ones that match). func (state *refState) searchForName(sourceFile *ast.SourceFile, search *refSearch) { if _, ok := getNameTable(sourceFile)[search.escapedText]; ok { state.getReferencesInSourceFile(sourceFile, search, true /*addReferencesHere*/) } } func (state *refState) explicitlyInheritsFrom(symbol *ast.Symbol, parent *ast.Symbol) bool { if symbol == parent { return true } // Check cache first key := inheritKey{symbol: symbol, parent: parent} if cached, ok := state.inheritsFromCache[key]; ok { return cached } // Set to false initially to prevent infinite recursion state.inheritsFromCache[key] = false if symbol.Declarations == nil { return false } inherits := core.Some(symbol.Declarations, func(declaration *ast.Node) bool { superTypeNodes := getAllSuperTypeNodes(declaration) return core.Some(superTypeNodes, func(typeReference *ast.TypeNode) bool { typ := state.checker.GetTypeAtLocation(typeReference.AsNode()) return typ != nil && typ.Symbol() != nil && state.explicitlyInheritsFrom(typ.Symbol(), parent) }) }) // Update cache with the actual result state.inheritsFromCache[key] = inherits return inherits }