2046 lines
81 KiB
Go
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
|
|
}
|