kittenipc/kitcom/internal/tsgo/ls/findallreferences.go
2025-10-15 10:12:44 +03:00

2046 lines
81 KiB
Go

package ls
import (
"cmp"
"context"
"fmt"
"slices"
"strings"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/binder"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/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 *lsproto.Range
}
func (l *LanguageService) getRangeOfEntry(entry *referenceEntry) *lsproto.Range {
return l.resolveEntry(entry).textRange
}
func (l *LanguageService) getFileNameOfEntry(entry *referenceEntry) string {
return l.resolveEntry(entry).fileName
}
func (l *LanguageService) resolveEntry(entry *referenceEntry) *referenceEntry {
if entry.textRange == nil {
sourceFile := ast.GetSourceFileOfNode(entry.node)
entry.textRange = l.getRangeOfNode(entry.node, sourceFile, nil /*endNode*/)
entry.fileName = sourceFile.FileName()
}
return entry
}
func (l *LanguageService) newRangeEntry(file *ast.SourceFile, start, end int) *referenceEntry {
// !!! used in not-yet implemented features
return &referenceEntry{
kind: entryKindRange,
fileName: file.FileName(),
textRange: l.createLspRangeFromBounds(start, end, file),
}
}
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) getRangeOfNode(node *ast.Node, sourceFile *ast.SourceFile, endNode *ast.Node) *lsproto.Range {
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 l.createLspRangeFromBounds(start, end, sourceFile)
}
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) ===
func (l *LanguageService) ProvideReferences(ctx context.Context, params *lsproto.ReferenceParams) (lsproto.ReferencesResponse, error) {
// `findReferencedSymbols` except only computes the information needed to return reference locations
program, sourceFile := l.getProgramAndFile(params.TextDocument.Uri)
position := int(l.converters.LineAndCharacterToPosition(sourceFile, params.Position))
node := astnav.GetTouchingPropertyName(sourceFile, position)
options := refOptions{use: referenceUseReferences}
symbolsAndEntries := l.getReferencedSymbolsForNode(ctx, position, node, program, program.GetSourceFiles(), options, nil)
locations := core.FlatMap(symbolsAndEntries, l.convertSymbolAndEntriesToLocations)
return lsproto.LocationsOrNull{Locations: &locations}, nil
}
func (l *LanguageService) ProvideImplementations(ctx context.Context, params *lsproto.ImplementationParams) (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)
entries = append(entries, entry)
queue = append(queue, l.getImplementationReferenceEntries(ctx, program, entry.node, entry.node.Pos())...)
}
}
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) ProvideRename(ctx context.Context, params *lsproto.RenameParams) (lsproto.WorkspaceEditOrNull, error) {
program, sourceFile := l.getProgramAndFile(params.TextDocument.Uri)
position := int(l.converters.LineAndCharacterToPosition(sourceFile, params.Position))
node := astnav.GetTouchingPropertyName(sourceFile, position)
if node.Kind != ast.KindIdentifier {
return lsproto.WorkspaceEditOrNull{}, nil
}
options := refOptions{use: referenceUseRename, useAliasesForRename: true}
symbolsAndEntries := l.getReferencedSymbolsForNode(ctx, position, node, program, program.GetSourceFiles(), options, nil)
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 := FileNameToDocumentURI(l.getFileNameOfEntry(entry))
textEdit := &lsproto.TextEdit{
Range: *l.getRangeOfEntry(entry),
NewText: l.getTextForRename(node, 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) []lsproto.Location {
return l.convertEntriesToLocations(s.references)
}
func (l *LanguageService) convertEntriesToLocations(entries []*referenceEntry) []lsproto.Location {
locations := make([]lsproto.Location, len(entries))
for i, entry := range entries {
locations[i] = lsproto.Location{
Uri: FileNameToDocumentURI(l.getFileNameOfEntry(entry)),
Range: *l.getRangeOfEntry(entry),
}
}
return locations
}
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 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())
}
}
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 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 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 := getReferencedSymbolsForModule(ctx, program, symbol, exportEquals != nil, sourceFiles, sourceFilesSet)
if exportEquals == nil || !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, nil, 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 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:
// <reference path> or <reference types>
// We can't easily create a proper range entry here without access to LanguageService,
// but we can create a node-based entry pointing to the source file which will be resolved later
return newNodeEntry(reference.referencingFile.AsNode())
}
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.AsPropertyAccessExpression().Expression
} else if ast.IsExportAssignment(decl) {
// Find the export keyword
node = 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 nil
}
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, entryKindNone)
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 getMeaningFromDeclaration(declaration)&ast.SemanticMeaningValue != 0 {
addReference(declaration)
}
}
}
}
func climbPastPropertyAccess(node *ast.Node) *ast.Node {
if isRightSideOfPropertyAccess(node) {
return node.Parent
}
return node
}
func isNewExpressionTarget(node *ast.Node) bool {
if node.Parent == nil {
return false
}
return node.Parent.Kind == ast.KindNewExpression && node.Parent.AsNewExpression().Expression == node
}
func isCallExpressionTarget(node *ast.Node) bool {
if node.Parent == nil {
return false
}
return node.Parent.Kind == ast.KindCallExpression && node.Parent.AsCallExpression().Expression == node
}
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(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 := 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 isNewExpressionTarget(thisKeyword) {
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 isCallExpressionTarget(node) {
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 {
state.getReferencesInContainer(scope, ast.GetSourceFileOfNode(scope), search /*addReferencesHere*/, !(scope.Kind == ast.KindSourceFile && !slices.Contains(state.sourceFiles, scope.AsSourceFile())))
} 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 isNewExpressionTarget(referenceLocation) && addReferencesHere {
state.addReference(referenceLocation, symbol, entryKindNone)
}
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, entryKindNone)
})
} 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, entryKindNone)
})
state.findInheritedConstructorReferences(classExtending)
}
}
}
func (state *refState) addClassStaticThisReferences(referenceLocation *ast.Node, symbol *ast.Symbol, search *refSearch, addReferencesHere bool) {
if addReferencesHere {
state.addReference(referenceLocation, symbol, entryKindNone)
}
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, entryKindNone)
} 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, entryKindNone)
}
}
// === 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, entryKindNone
}
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)), entryKindNone
}
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
}