2655 lines
99 KiB
Go
2655 lines
99 KiB
Go
package ls
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/microsoft/typescript-go/internal/ast"
|
|
"github.com/microsoft/typescript-go/internal/astnav"
|
|
"github.com/microsoft/typescript-go/internal/binder"
|
|
"github.com/microsoft/typescript-go/internal/checker"
|
|
"github.com/microsoft/typescript-go/internal/collections"
|
|
"github.com/microsoft/typescript-go/internal/compiler"
|
|
"github.com/microsoft/typescript-go/internal/core"
|
|
"github.com/microsoft/typescript-go/internal/debug"
|
|
"github.com/microsoft/typescript-go/internal/ls/lsconv"
|
|
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
|
|
"github.com/microsoft/typescript-go/internal/printer"
|
|
"github.com/microsoft/typescript-go/internal/scanner"
|
|
"github.com/microsoft/typescript-go/internal/stringutil"
|
|
|
|
"github.com/microsoft/typescript-go/internal/tspath"
|
|
)
|
|
|
|
// === types for settings ===
|
|
type referenceUse int
|
|
|
|
const (
|
|
referenceUseNone referenceUse = 0
|
|
referenceUseOther referenceUse = 1
|
|
referenceUseReferences referenceUse = 2
|
|
referenceUseRename referenceUse = 3
|
|
)
|
|
|
|
type refOptions struct {
|
|
findInStrings bool
|
|
findInComments bool
|
|
use referenceUse // other, references, rename
|
|
implementations bool
|
|
useAliasesForRename bool // renamed from providePrefixAndSuffixTextForRename. default: true
|
|
}
|
|
|
|
// === types for results ===
|
|
|
|
type refInfo struct {
|
|
file *ast.SourceFile
|
|
fileName string
|
|
reference *ast.FileReference
|
|
unverified bool
|
|
}
|
|
|
|
type SymbolAndEntries struct {
|
|
definition *Definition
|
|
references []*ReferenceEntry
|
|
}
|
|
|
|
func NewSymbolAndEntries(kind DefinitionKind, node *ast.Node, symbol *ast.Symbol, references []*ReferenceEntry) *SymbolAndEntries {
|
|
return &SymbolAndEntries{
|
|
&Definition{
|
|
Kind: kind,
|
|
node: node,
|
|
symbol: symbol,
|
|
},
|
|
references,
|
|
}
|
|
}
|
|
|
|
type DefinitionKind int
|
|
|
|
const (
|
|
definitionKindSymbol DefinitionKind = 0
|
|
definitionKindLabel DefinitionKind = 1
|
|
definitionKindKeyword DefinitionKind = 2
|
|
definitionKindThis DefinitionKind = 3
|
|
definitionKindString DefinitionKind = 4
|
|
definitionKindTripleSlashReference DefinitionKind = 5
|
|
)
|
|
|
|
type Definition struct {
|
|
Kind DefinitionKind
|
|
symbol *ast.Symbol
|
|
node *ast.Node
|
|
tripleSlashFileRef *tripleSlashDefinition
|
|
}
|
|
type tripleSlashDefinition struct {
|
|
reference *ast.FileReference
|
|
file *ast.SourceFile
|
|
}
|
|
|
|
type entryKind int
|
|
|
|
const (
|
|
entryKindNone entryKind = 0
|
|
entryKindRange entryKind = 1
|
|
entryKindNode entryKind = 2
|
|
entryKindStringLiteral entryKind = 3
|
|
entryKindSearchedLocalFoundProperty entryKind = 4
|
|
entryKindSearchedPropertyFoundLocal entryKind = 5
|
|
)
|
|
|
|
type ReferenceEntry struct {
|
|
kind entryKind
|
|
node *ast.Node
|
|
context *ast.Node // !!! ContextWithStartAndEndNode, optional
|
|
fileName string
|
|
textRange *core.TextRange
|
|
lspRange *lsproto.Location
|
|
}
|
|
|
|
// Node returns the AST node for this reference entry.
|
|
func (e *ReferenceEntry) Node() *ast.Node {
|
|
return e.node
|
|
}
|
|
|
|
// IsNodeEntry returns true if this is a node-backed reference entry.
|
|
func (e *ReferenceEntry) IsNodeEntry() bool {
|
|
return e.node != nil
|
|
}
|
|
|
|
// References returns the reference entries for this symbol.
|
|
func (s *SymbolAndEntries) References() []*ReferenceEntry {
|
|
return s.references
|
|
}
|
|
|
|
// DefinitionNode returns the defining AST node for this symbol, if any.
|
|
func (s *SymbolAndEntries) DefinitionNode() *ast.Node {
|
|
if s.definition == nil {
|
|
return nil
|
|
}
|
|
if s.definition.node != nil {
|
|
return s.definition.node
|
|
}
|
|
if s.definition.symbol != nil && len(s.definition.symbol.Declarations) > 0 {
|
|
return s.definition.symbol.Declarations[0]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *SymbolAndEntries) DefinitionSymbol() *ast.Symbol {
|
|
if s.definition == nil {
|
|
return nil
|
|
}
|
|
return s.definition.symbol
|
|
}
|
|
|
|
func (entry *SymbolAndEntries) canUseDefinitionSymbol() bool {
|
|
if entry.definition == nil {
|
|
return false
|
|
}
|
|
|
|
switch entry.definition.Kind {
|
|
case definitionKindSymbol, definitionKindThis:
|
|
return entry.definition.symbol != nil
|
|
case definitionKindTripleSlashReference:
|
|
// !!! TODO : need to find file reference instead?
|
|
// May need to return true to indicate this to be file search instead and might need to do for import stuff as well
|
|
// For now
|
|
return false
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (l *LanguageService) getRangeOfEntry(entry *ReferenceEntry) lsproto.Range {
|
|
return l.resolveEntry(entry).lspRange.Range
|
|
}
|
|
|
|
func (l *LanguageService) getFileNameOfEntry(entry *ReferenceEntry) lsproto.DocumentUri {
|
|
return l.resolveEntry(entry).lspRange.Uri
|
|
}
|
|
|
|
func (l *LanguageService) getLocationOfEntry(entry *ReferenceEntry) lsproto.Location {
|
|
return *l.resolveEntry(entry).lspRange
|
|
}
|
|
|
|
func (l *LanguageService) resolveEntry(entry *ReferenceEntry) *ReferenceEntry {
|
|
if entry.textRange == nil {
|
|
sourceFile := ast.GetSourceFileOfNode(entry.node)
|
|
textRange := getRangeOfNode(entry.node, sourceFile, nil /*endNode*/)
|
|
entry.textRange = &textRange
|
|
entry.fileName = sourceFile.FileName()
|
|
}
|
|
if entry.lspRange == nil {
|
|
location := l.getMappedLocation(entry.fileName, *entry.textRange)
|
|
entry.lspRange = &location
|
|
}
|
|
return entry
|
|
}
|
|
|
|
func newNodeEntryWithKind(node *ast.Node, kind entryKind) *ReferenceEntry {
|
|
e := newNodeEntry(node)
|
|
e.kind = kind
|
|
return e
|
|
}
|
|
|
|
func newNodeEntry(node *ast.Node) *ReferenceEntry {
|
|
// creates nodeEntry with `kind == entryKindNode`
|
|
return &ReferenceEntry{
|
|
kind: entryKindNode,
|
|
node: core.OrElse(node.Name(), node),
|
|
context: getContextNodeForNodeEntry(node),
|
|
}
|
|
}
|
|
|
|
func getContextNodeForNodeEntry(node *ast.Node) *ast.Node {
|
|
if ast.IsDeclaration(node) {
|
|
return getContextNode(node)
|
|
}
|
|
|
|
if node.Parent == nil {
|
|
return nil
|
|
}
|
|
|
|
if !ast.IsDeclaration(node.Parent) && !ast.IsExportAssignment(node.Parent) {
|
|
// Special property assignment in javascript
|
|
if ast.IsInJSFile(node) {
|
|
// !!! jsdoc: check if branch still needed
|
|
var binaryExpression *ast.Node
|
|
if ast.IsBinaryExpression(node.Parent) {
|
|
binaryExpression = node.Parent
|
|
} else if ast.IsAccessExpression(node.Parent) && ast.IsBinaryExpression(node.Parent.Parent) && node.Parent.Parent.AsBinaryExpression().Left == node.Parent {
|
|
binaryExpression = node.Parent.Parent
|
|
}
|
|
if binaryExpression != nil && ast.GetAssignmentDeclarationKind(binaryExpression) != 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 := ast.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 ||
|
|
// Property name of the import export specifier or binding pattern, use parent
|
|
((ast.IsImportOrExportSpecifier(node.Parent) || node.Parent.Kind == ast.KindBindingElement) && node.Parent.PropertyName() == node) ||
|
|
// Is default export
|
|
(node.Kind == ast.KindDefaultKeyword && ast.HasSyntacticModifier(node.Parent, ast.ModifierFlagsExportDefault)) {
|
|
return getContextNode(node.Parent)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getContextNode(node *ast.Node) *ast.Node {
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
switch node.Kind {
|
|
case ast.KindVariableDeclaration:
|
|
if !ast.IsVariableDeclarationList(node.Parent) || len(node.Parent.AsVariableDeclarationList().Declarations.Nodes) != 1 {
|
|
return node
|
|
} else if ast.IsVariableStatement(node.Parent.Parent) {
|
|
return node.Parent.Parent
|
|
} else if ast.IsForInOrOfStatement(node.Parent.Parent) {
|
|
return getContextNode(node.Parent.Parent)
|
|
}
|
|
return node.Parent
|
|
|
|
case ast.KindBindingElement:
|
|
return getContextNode(node.Parent.Parent)
|
|
|
|
case ast.KindImportSpecifier:
|
|
return node.Parent.Parent.Parent
|
|
|
|
case ast.KindExportSpecifier, ast.KindNamespaceImport:
|
|
return node.Parent.Parent
|
|
|
|
case ast.KindImportClause, ast.KindNamespaceExport:
|
|
return node.Parent
|
|
|
|
case ast.KindBinaryExpression:
|
|
return core.IfElse(node.Parent.Kind == ast.KindExpressionStatement, node.Parent, node)
|
|
|
|
case ast.KindForOfStatement, ast.KindForInStatement:
|
|
// !!! not implemented
|
|
return nil
|
|
|
|
case ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment:
|
|
if ast.IsArrayLiteralOrObjectLiteralDestructuringPattern(node.Parent) {
|
|
return getContextNode(ast.FindAncestor(node.Parent, func(node *ast.Node) bool {
|
|
return node.Kind == ast.KindBinaryExpression || ast.IsForInOrOfStatement(node)
|
|
}))
|
|
}
|
|
return node
|
|
case ast.KindSwitchStatement:
|
|
// !!! not implemented
|
|
return nil
|
|
default:
|
|
return node
|
|
}
|
|
}
|
|
|
|
// utils
|
|
func (l *LanguageService) getLspRangeOfNode(node *ast.Node, sourceFile *ast.SourceFile, endNode *ast.Node) lsproto.Range {
|
|
if sourceFile == nil {
|
|
sourceFile = ast.GetSourceFileOfNode(node)
|
|
}
|
|
textRange := getRangeOfNode(node, sourceFile, endNode)
|
|
return l.createLspRangeFromBounds(textRange.Pos(), textRange.End(), sourceFile)
|
|
}
|
|
|
|
func getRangeOfNode(node *ast.Node, sourceFile *ast.SourceFile, endNode *ast.Node) core.TextRange {
|
|
if sourceFile == nil {
|
|
sourceFile = ast.GetSourceFileOfNode(node)
|
|
}
|
|
start := scanner.GetTokenPosOfNode(node, sourceFile, false /*includeJsDoc*/)
|
|
end := core.IfElse(endNode != nil, endNode, node).End()
|
|
if ast.IsStringLiteralLike(node) && (end-start) > 2 {
|
|
if endNode != nil {
|
|
panic("endNode is not nil for stringLiteralLike")
|
|
}
|
|
start += 1
|
|
end -= 1
|
|
}
|
|
if endNode != nil && endNode.Kind == ast.KindCaseBlock {
|
|
end = endNode.Pos()
|
|
}
|
|
return core.NewTextRange(start, end)
|
|
}
|
|
|
|
func isValidReferencePosition(node *ast.Node, searchSymbolName string) bool {
|
|
switch node.Kind {
|
|
case ast.KindPrivateIdentifier:
|
|
// !!!
|
|
// if (isJSDocMemberName(node.Parent)) {
|
|
// return true;
|
|
// }
|
|
return len(node.Text()) == len(searchSymbolName)
|
|
case ast.KindIdentifier:
|
|
return len(node.Text()) == len(searchSymbolName)
|
|
case ast.KindNoSubstitutionTemplateLiteral, ast.KindStringLiteral:
|
|
return len(node.Text()) == len(searchSymbolName) && (isLiteralNameOfPropertyDeclarationOrIndexAccess(node) ||
|
|
isNameOfModuleDeclaration(node) ||
|
|
isExpressionOfExternalModuleImportEqualsDeclaration(node) ||
|
|
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, 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 and CJS module end exports symbols
|
|
if symbol.Flags&(ast.SymbolFlagsTransient|ast.SymbolFlagsModuleExports) != 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) && !isSourceFileWithGlobalExports(symbol.Parent.ValueDeclaration)) {
|
|
return nil
|
|
}
|
|
|
|
var scope *ast.Node
|
|
for _, declaration := range declarations {
|
|
container := getContainerNode(declaration)
|
|
if scope != nil && scope != container {
|
|
// Different declarations have different containers, bail out
|
|
return nil
|
|
}
|
|
|
|
if container == nil || (container.Kind == ast.KindSourceFile && !ast.IsExternalOrCommonJSModule(container.AsSourceFile())) {
|
|
// This is a global variable and not an external module, any declaration defined
|
|
// within this scope is visible outside the file
|
|
return nil
|
|
}
|
|
|
|
scope = container
|
|
}
|
|
|
|
// If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.)
|
|
// For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.:
|
|
// declare module "a" { export type T = number; }
|
|
// declare module "b" { import { T } from "a"; export const x: T; }
|
|
// So we must search the whole source file. (Because we will mark the source file as seen, we we won't return to it when searching for imports.)
|
|
if exposedByParent {
|
|
return ast.GetSourceFileOfNode(scope).AsNode()
|
|
}
|
|
return scope // TODO: GH#18217
|
|
}
|
|
|
|
// === functions on (*ls) ===
|
|
|
|
type position struct {
|
|
uri lsproto.DocumentUri
|
|
pos lsproto.Position
|
|
}
|
|
|
|
var _ lsproto.HasTextDocumentPosition = (*position)(nil)
|
|
|
|
func (nld *position) TextDocumentURI() lsproto.DocumentUri { return nld.uri }
|
|
func (nld *position) TextDocumentPosition() lsproto.Position { return nld.pos }
|
|
|
|
type nonLocalDefinition struct {
|
|
position
|
|
GetSourcePosition func() lsproto.HasTextDocumentPosition
|
|
GetGeneratedPosition func() lsproto.HasTextDocumentPosition
|
|
}
|
|
|
|
func getFileAndStartPosFromDeclaration(declaration *ast.Node) (*ast.SourceFile, core.TextPos) {
|
|
file := ast.GetSourceFileOfNode(declaration)
|
|
name := core.OrElse(ast.GetNameOfDeclaration(declaration), declaration)
|
|
textRange := getRangeOfNode(name, file, nil /*endNode*/)
|
|
|
|
return file, core.TextPos(textRange.Pos())
|
|
}
|
|
|
|
func (l *LanguageService) getNonLocalDefinition(ctx context.Context, entry *SymbolAndEntries) *nonLocalDefinition {
|
|
if !entry.canUseDefinitionSymbol() {
|
|
return nil
|
|
}
|
|
|
|
program := l.GetProgram()
|
|
checker, done := program.GetTypeChecker(ctx)
|
|
defer done()
|
|
emitResolver := checker.GetEmitResolver()
|
|
for _, d := range entry.definition.symbol.Declarations {
|
|
if isDefinitionVisible(emitResolver, d) {
|
|
file, startPos := getFileAndStartPosFromDeclaration(d)
|
|
fileName := file.FileName()
|
|
return &nonLocalDefinition{
|
|
position: position{
|
|
uri: lsconv.FileNameToDocumentURI(fileName),
|
|
pos: l.converters.PositionToLineAndCharacter(file, startPos),
|
|
},
|
|
GetSourcePosition: sync.OnceValue(func() lsproto.HasTextDocumentPosition {
|
|
mapped := l.tryGetSourcePosition(fileName, startPos)
|
|
if mapped != nil {
|
|
return &position{
|
|
uri: lsconv.FileNameToDocumentURI(mapped.FileName),
|
|
pos: l.converters.PositionToLineAndCharacter(l.getScript(mapped.FileName), core.TextPos(mapped.Pos)),
|
|
}
|
|
}
|
|
return nil
|
|
}),
|
|
GetGeneratedPosition: sync.OnceValue(func() lsproto.HasTextDocumentPosition {
|
|
mapped := l.tryGetGeneratedPosition(fileName, startPos)
|
|
if mapped != nil {
|
|
return &position{
|
|
uri: lsconv.FileNameToDocumentURI(mapped.FileName),
|
|
pos: l.converters.PositionToLineAndCharacter(l.getScript(mapped.FileName), core.TextPos(mapped.Pos)),
|
|
}
|
|
}
|
|
return nil
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// This is special handling to determine if we should load up more projects and find location in other projects
|
|
// By default arrows (and such other ast kinds) are not visible as declaration emitter doesnt need them
|
|
// But we want to handle them specially so that they are visible if their parent is visible
|
|
func isDefinitionVisible(emitResolver *checker.EmitResolver, declaration *ast.Node) bool {
|
|
if emitResolver.IsDeclarationVisible(declaration) {
|
|
return true
|
|
}
|
|
if declaration.Parent == nil {
|
|
return false
|
|
}
|
|
|
|
// Variable initializers are visible if variable is visible
|
|
if ast.HasInitializer(declaration.Parent) && declaration.Parent.Initializer() == declaration {
|
|
return isDefinitionVisible(emitResolver, declaration.Parent)
|
|
}
|
|
|
|
// Handle some exceptions here like arrow function, members of class and object literal expression which are technically not visible but we want the definition to be determined by its parent
|
|
switch declaration.Kind {
|
|
case ast.KindPropertyDeclaration,
|
|
ast.KindGetAccessor,
|
|
ast.KindSetAccessor,
|
|
ast.KindMethodDeclaration:
|
|
// Private/protected properties/methods are not visible
|
|
if ast.HasModifier(declaration, ast.ModifierFlagsPrivate) || ast.IsPrivateIdentifier(declaration.Name()) {
|
|
return false
|
|
}
|
|
// Public properties/methods are visible if its parents are visible, so:
|
|
// falls through
|
|
fallthrough
|
|
case ast.KindConstructor,
|
|
ast.KindPropertyAssignment,
|
|
ast.KindShorthandPropertyAssignment,
|
|
ast.KindObjectLiteralExpression,
|
|
ast.KindClassExpression,
|
|
ast.KindArrowFunction,
|
|
ast.KindFunctionExpression:
|
|
return isDefinitionVisible(emitResolver, declaration.Parent)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (l *LanguageService) forEachOriginalDefinitionLocation(
|
|
ctx context.Context,
|
|
entry *SymbolAndEntries,
|
|
cb func(lsproto.DocumentUri, lsproto.Position),
|
|
) {
|
|
if !entry.canUseDefinitionSymbol() {
|
|
return
|
|
}
|
|
|
|
program := l.GetProgram()
|
|
for _, d := range entry.definition.symbol.Declarations {
|
|
file, startPos := getFileAndStartPosFromDeclaration(d)
|
|
fileName := file.FileName()
|
|
if tspath.IsDeclarationFileName(fileName) {
|
|
// Map to ts position
|
|
mapped := l.tryGetSourcePosition(file.FileName(), startPos)
|
|
if mapped != nil {
|
|
cb(
|
|
lsconv.FileNameToDocumentURI(mapped.FileName),
|
|
l.converters.PositionToLineAndCharacter(l.getScript(mapped.FileName), core.TextPos(mapped.Pos)),
|
|
)
|
|
}
|
|
} else if program.IsSourceFromProjectReference(l.toPath(fileName)) {
|
|
cb(
|
|
lsconv.FileNameToDocumentURI(fileName),
|
|
l.converters.PositionToLineAndCharacter(file, startPos),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
type symbolEntryTransformOptions struct {
|
|
// Force the result to be Location objects.
|
|
requireLocationsResult bool
|
|
// Omit node(s) containing the original position.
|
|
dropOriginNodes bool
|
|
}
|
|
|
|
type SymbolAndEntriesData struct {
|
|
OriginalNode *ast.Node
|
|
SymbolsAndEntries []*SymbolAndEntries
|
|
Position int
|
|
}
|
|
|
|
func (l *LanguageService) provideSymbolsAndEntries(ctx context.Context, uri lsproto.DocumentUri, documentPosition lsproto.Position, isRename bool, implementations bool) (SymbolAndEntriesData, bool) {
|
|
// `findReferencedSymbols` except only computes the information needed to return reference locations
|
|
program, sourceFile := l.getProgramAndFile(uri)
|
|
position := int(l.converters.LineAndCharacterToPosition(sourceFile, documentPosition))
|
|
|
|
node := astnav.GetTouchingPropertyName(sourceFile, position)
|
|
if isRename {
|
|
// Adjust modifier/keyword nodes to the declaration name, matching Strada's findRenameLocations.
|
|
node = getAdjustedLocation(node, true /*forRename*/, sourceFile)
|
|
}
|
|
if isRename && !nodeIsEligibleForRename(node) || implementations && ast.IsSourceFile(node) {
|
|
return SymbolAndEntriesData{OriginalNode: node, Position: position}, false
|
|
}
|
|
|
|
entries := l.getSymbolAndEntries(ctx, position, node, program, isRename, implementations)
|
|
if !implementations {
|
|
return SymbolAndEntriesData{OriginalNode: node, SymbolsAndEntries: entries, Position: position}, true
|
|
}
|
|
|
|
var implementationEntries []*SymbolAndEntries
|
|
var queue []*ReferenceEntry
|
|
var seenNodes collections.Set[*ast.Node]
|
|
addToQueue := func(symbolAndEntries []*SymbolAndEntries) {
|
|
implementationEntries = core.Concatenate(implementationEntries, symbolAndEntries)
|
|
for _, s := range symbolAndEntries {
|
|
queue = append(queue, s.references...)
|
|
}
|
|
}
|
|
|
|
addToQueue(entries)
|
|
for len(queue) != 0 {
|
|
if ctx.Err() != nil {
|
|
return SymbolAndEntriesData{}, false
|
|
}
|
|
|
|
entry := queue[0]
|
|
queue = queue[1:]
|
|
if entry.node != nil && !seenNodes.Has(entry.node) {
|
|
seenNodes.Add(entry.node)
|
|
addToQueue(l.getSymbolAndEntries(ctx, entry.node.Pos(), entry.node, program, isRename, implementations))
|
|
}
|
|
}
|
|
return SymbolAndEntriesData{OriginalNode: node, SymbolsAndEntries: implementationEntries, Position: position}, true
|
|
}
|
|
|
|
func (l *LanguageService) getSymbolAndEntries(
|
|
ctx context.Context,
|
|
position int,
|
|
node *ast.Node,
|
|
program *compiler.Program,
|
|
isRename bool,
|
|
implementations bool,
|
|
) []*SymbolAndEntries {
|
|
var options refOptions
|
|
if !isRename {
|
|
options.use = referenceUseReferences
|
|
if implementations {
|
|
options.implementations = true
|
|
}
|
|
} else {
|
|
options.use = referenceUseRename
|
|
options.useAliasesForRename = l.UserPreferences().UseAliasesForRename.IsTrueOrUnknown()
|
|
}
|
|
return l.getReferencedSymbolsForNode(ctx, position, node, program, program.GetSourceFiles(), options)
|
|
}
|
|
|
|
func (l *LanguageService) ProvideReferences(ctx context.Context, params *lsproto.ReferenceParams, orchestrator CrossProjectOrchestrator) (lsproto.ReferencesResponse, error) {
|
|
return handleCrossProject(
|
|
l,
|
|
ctx,
|
|
params,
|
|
orchestrator,
|
|
(*LanguageService).symbolAndEntriesToReferences,
|
|
combineReferences,
|
|
false, /*isRename*/
|
|
false, /*implementations*/
|
|
symbolEntryTransformOptions{},
|
|
)
|
|
}
|
|
|
|
func (l *LanguageService) ProvideVSReferences(ctx context.Context, params *lsproto.ReferenceParams, orchestrator CrossProjectOrchestrator) (lsproto.VSReferencesResponse, error) {
|
|
return handleCrossProject(
|
|
l,
|
|
ctx,
|
|
params,
|
|
orchestrator,
|
|
(*LanguageService).symbolAndEntriesToVSReferences,
|
|
combineVSReferences,
|
|
false, /*isRename*/
|
|
false, /*implementations*/
|
|
symbolEntryTransformOptions{},
|
|
)
|
|
}
|
|
|
|
func (l *LanguageService) symbolAndEntriesToReferences(ctx context.Context, params *lsproto.ReferenceParams, data SymbolAndEntriesData, options symbolEntryTransformOptions) (lsproto.ReferencesResponse, error) {
|
|
// `findReferencedSymbols` except only computes the information needed to return reference locations
|
|
locations := core.FlatMap(data.SymbolsAndEntries, func(s *SymbolAndEntries) []lsproto.Location {
|
|
return l.convertSymbolAndEntriesToLocations(s, params.Context.IncludeDeclaration)
|
|
})
|
|
return lsproto.LocationsOrNull{Locations: &locations}, nil
|
|
}
|
|
|
|
func (l *LanguageService) symbolAndEntriesToVSReferences(ctx context.Context, params *lsproto.ReferenceParams, data SymbolAndEntriesData, options symbolEntryTransformOptions) (lsproto.VSReferencesResponse, error) {
|
|
caps := lsproto.GetClientCapabilities(ctx)
|
|
vsCapability := caps.VSSupportsVisualStudioExtensions
|
|
var items []*lsproto.VSReferenceItem
|
|
id := int32(0)
|
|
projectName := string(l.projectPath)
|
|
|
|
for _, s := range data.SymbolsAndEntries {
|
|
if s.definition == nil {
|
|
continue
|
|
}
|
|
|
|
// Convert definition to info
|
|
defInfo := l.definitionToReferencedSymbolDefinitionInfo(ctx, s.definition, data.OriginalNode, vsCapability)
|
|
if defInfo == nil {
|
|
continue
|
|
}
|
|
|
|
// Create the definition item
|
|
definitionId := id
|
|
emptyStr := ""
|
|
defItem := &lsproto.VSReferenceItem{
|
|
VSId: definitionId,
|
|
VSLocation: defInfo.location,
|
|
VSDefinitionText: defInfo.displayText,
|
|
VSKind: &[]lsproto.VSReferenceKind{lsproto.VSReferenceKindUnknown},
|
|
VSProjectName: &projectName,
|
|
VSContainingType: &emptyStr,
|
|
}
|
|
items = append(items, defItem)
|
|
id++
|
|
|
|
// Create reference items grouped under the definition
|
|
for _, ref := range s.references {
|
|
// Skip the declaration itself (already represented by the definition item)
|
|
if s.definition.symbol != nil && isDeclarationOfSymbol(ref.node, s.definition.symbol) {
|
|
continue
|
|
}
|
|
|
|
refLocation := l.getLocationOfEntry(ref)
|
|
|
|
// Determine read/write kind
|
|
kind := lsproto.VSReferenceKindRead
|
|
if ref.kind != entryKindRange && ref.node != nil && ast.IsWriteAccessForReference(ref.node) {
|
|
kind = lsproto.VSReferenceKindWrite
|
|
}
|
|
|
|
refItem := &lsproto.VSReferenceItem{
|
|
VSId: id,
|
|
VSDefinitionId: &definitionId,
|
|
VSLocation: refLocation,
|
|
VSKind: &[]lsproto.VSReferenceKind{kind},
|
|
VSProjectName: &projectName,
|
|
}
|
|
items = append(items, refItem)
|
|
id++
|
|
}
|
|
}
|
|
|
|
return lsproto.VSReferencesResponse{VSReferenceItems: &items}, nil
|
|
}
|
|
|
|
// referencedSymbolDefinitionInfo holds the computed info for a definition
|
|
type referencedSymbolDefinitionInfo struct {
|
|
node *ast.Node
|
|
location lsproto.Location
|
|
displayText *lsproto.VSClassifiedTextElement
|
|
}
|
|
|
|
// definitionToReferencedSymbolDefinitionInfo converts a Definition to display info
|
|
func (l *LanguageService) definitionToReferencedSymbolDefinitionInfo(ctx context.Context, def *Definition, originalNode *ast.Node, vsCapability bool) *referencedSymbolDefinitionInfo {
|
|
switch def.Kind {
|
|
case definitionKindSymbol:
|
|
symbol := def.symbol
|
|
if symbol == nil {
|
|
return nil
|
|
}
|
|
// Get display parts
|
|
element := l.getDefinitionKindAndDisplayParts(ctx, symbol, originalNode, vsCapability)
|
|
|
|
// Get the definition node
|
|
var node *ast.Node
|
|
if len(symbol.Declarations) > 0 {
|
|
decl := symbol.Declarations[0]
|
|
node = core.OrElse(decl.Name(), decl)
|
|
} else {
|
|
node = originalNode
|
|
}
|
|
|
|
loc := l.getLocationOfEntry(&ReferenceEntry{kind: entryKindNode, node: node})
|
|
return &referencedSymbolDefinitionInfo{
|
|
node: node,
|
|
location: loc,
|
|
displayText: element,
|
|
}
|
|
|
|
case definitionKindLabel:
|
|
node := def.node
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
loc := l.getLocationOfEntry(&ReferenceEntry{kind: entryKindNode, node: node})
|
|
return &referencedSymbolDefinitionInfo{
|
|
node: node,
|
|
location: loc,
|
|
displayText: &lsproto.VSClassifiedTextElement{
|
|
Runs: []*lsproto.VSClassifiedTextRun{{Text: node.Text(), ClassificationTypeName: string(lsproto.ClassificationTypeNameText)}},
|
|
},
|
|
}
|
|
|
|
case definitionKindKeyword:
|
|
node := def.node
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
name := scanner.TokenToString(node.Kind)
|
|
loc := l.getLocationOfEntry(&ReferenceEntry{kind: entryKindNode, node: node})
|
|
return &referencedSymbolDefinitionInfo{
|
|
node: node,
|
|
location: loc,
|
|
displayText: &lsproto.VSClassifiedTextElement{
|
|
Runs: []*lsproto.VSClassifiedTextRun{{Text: name, ClassificationTypeName: string(lsproto.ClassificationTypeNameKeyword)}},
|
|
},
|
|
}
|
|
|
|
case definitionKindThis:
|
|
node := def.node
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
symbol := def.symbol
|
|
if symbol == nil {
|
|
return nil
|
|
}
|
|
element := l.getDefinitionKindAndDisplayParts(ctx, symbol, node, vsCapability)
|
|
loc := l.getLocationOfEntry(&ReferenceEntry{kind: entryKindNode, node: node})
|
|
return &referencedSymbolDefinitionInfo{
|
|
node: node,
|
|
location: loc,
|
|
displayText: element,
|
|
}
|
|
|
|
case definitionKindString:
|
|
node := def.node
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
loc := l.getLocationOfEntry(&ReferenceEntry{kind: entryKindNode, node: node})
|
|
return &referencedSymbolDefinitionInfo{
|
|
node: node,
|
|
location: loc,
|
|
displayText: &lsproto.VSClassifiedTextElement{
|
|
Runs: []*lsproto.VSClassifiedTextRun{{Text: node.Text(), ClassificationTypeName: string(lsproto.ClassificationTypeNameString)}},
|
|
},
|
|
}
|
|
|
|
case definitionKindTripleSlashReference:
|
|
if def.tripleSlashFileRef == nil || def.tripleSlashFileRef.file == nil {
|
|
return nil
|
|
}
|
|
node := def.tripleSlashFileRef.file.AsNode()
|
|
loc := l.getLocationOfEntry(&ReferenceEntry{kind: entryKindNode, node: node})
|
|
return &referencedSymbolDefinitionInfo{
|
|
node: node,
|
|
location: loc,
|
|
displayText: &lsproto.VSClassifiedTextElement{
|
|
Runs: []*lsproto.VSClassifiedTextRun{{Text: `"` + def.tripleSlashFileRef.reference.FileName + `"`, ClassificationTypeName: string(lsproto.ClassificationTypeNameString)}},
|
|
},
|
|
}
|
|
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// getDefinitionKindAndDisplayParts returns the classified display text for a symbol definition.
|
|
func (l *LanguageService) getDefinitionKindAndDisplayParts(ctx context.Context, symbol *ast.Symbol, originalNode *ast.Node, vsCapability bool) *lsproto.VSClassifiedTextElement {
|
|
program := l.GetProgram()
|
|
c, done := program.GetTypeChecker(ctx)
|
|
defer done()
|
|
|
|
meaning := getIntersectingMeaningFromDeclarations(originalNode, symbol, ast.SemanticMeaningAll)
|
|
|
|
info := getQuickInfoAndDeclarationAtLocation(c, symbol, originalNode, nil, vsCapability, meaning)
|
|
|
|
if vsCapability {
|
|
return &lsproto.VSClassifiedTextElement{Runs: info.displayParts.GetRuns()}
|
|
}
|
|
// Fallback: single unclassified run with the full text
|
|
text := info.displayParts.String()
|
|
return &lsproto.VSClassifiedTextElement{
|
|
Runs: []*lsproto.VSClassifiedTextRun{{Text: text, ClassificationTypeName: string(lsproto.ClassificationTypeNameText)}},
|
|
}
|
|
}
|
|
|
|
func (l *LanguageService) ProvideImplementations(ctx context.Context, params *lsproto.ImplementationParams, orchestrator CrossProjectOrchestrator) (lsproto.ImplementationResponse, error) {
|
|
return l.provideImplementationsEx(ctx, params, symbolEntryTransformOptions{}, orchestrator)
|
|
}
|
|
|
|
func (l *LanguageService) provideImplementationsEx(ctx context.Context, params *lsproto.ImplementationParams, options symbolEntryTransformOptions, orchestrator CrossProjectOrchestrator) (lsproto.ImplementationResponse, error) {
|
|
return handleCrossProject(
|
|
l,
|
|
ctx,
|
|
params,
|
|
orchestrator,
|
|
(*LanguageService).symbolAndEntriesToImplementations,
|
|
combineImplementations,
|
|
false, /*isRename*/
|
|
true, /*implementations*/
|
|
options,
|
|
)
|
|
}
|
|
|
|
func (l *LanguageService) symbolAndEntriesToImplementations(ctx context.Context, params *lsproto.ImplementationParams, data SymbolAndEntriesData, options symbolEntryTransformOptions) (lsproto.ImplementationResponse, error) {
|
|
var seenNodes collections.Set[*ast.Node]
|
|
var entries []*ReferenceEntry
|
|
for _, entry := range data.SymbolsAndEntries {
|
|
for _, ref := range entry.references {
|
|
if seenNodes.AddIfAbsent(ref.node) && (!options.dropOriginNodes || !ref.node.Loc.ContainsInclusive(data.Position)) {
|
|
entries = append(entries, ref)
|
|
}
|
|
}
|
|
}
|
|
|
|
if !options.requireLocationsResult && lsproto.GetClientCapabilities(ctx).TextDocument.Implementation.LinkSupport {
|
|
links := l.convertEntriesToLocationLinks(entries)
|
|
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{DefinitionLinks: &links}, nil
|
|
}
|
|
locations := l.convertEntriesToLocations(entries)
|
|
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{Locations: &locations}, nil
|
|
}
|
|
|
|
// == functions for conversions ==
|
|
func (l *LanguageService) convertSymbolAndEntriesToLocations(s *SymbolAndEntries, includeDeclarations bool) []lsproto.Location {
|
|
references := s.references
|
|
|
|
// !!! includeDeclarations
|
|
if !includeDeclarations && s.definition != nil {
|
|
references = core.Filter(references, func(entry *ReferenceEntry) bool {
|
|
return !isDeclarationOfSymbol(entry.node, s.definition.symbol)
|
|
})
|
|
}
|
|
|
|
return l.convertEntriesToLocations(references)
|
|
}
|
|
|
|
func isDeclarationOfSymbol(node *ast.Node, target *ast.Symbol) bool {
|
|
if node == nil || target == nil {
|
|
return false
|
|
}
|
|
|
|
var source *ast.Node
|
|
if decl := ast.GetDeclarationFromName(node); decl != nil {
|
|
source = decl
|
|
} else if node.Kind == ast.KindDefaultKeyword {
|
|
source = node.Parent
|
|
} else if ast.IsLiteralComputedPropertyDeclarationName(node) {
|
|
source = node.Parent.Parent
|
|
} else if node.Kind == ast.KindConstructorKeyword && ast.IsConstructorDeclaration(node.Parent) {
|
|
source = node.Parent.Parent
|
|
}
|
|
|
|
// !!!
|
|
// const commonjsSource = source && isBinaryExpression(source) ? source.left as unknown as Declaration : undefined;
|
|
|
|
return source != nil && core.Some(target.Declarations, func(decl *ast.Node) bool {
|
|
return decl == source
|
|
})
|
|
}
|
|
|
|
func (l *LanguageService) convertEntriesToLocations(entries []*ReferenceEntry) []lsproto.Location {
|
|
locations := make([]lsproto.Location, len(entries))
|
|
for i, entry := range entries {
|
|
locations[i] = l.getLocationOfEntry(entry)
|
|
}
|
|
return locations
|
|
}
|
|
|
|
func (l *LanguageService) convertEntriesToLocationLinks(entries []*ReferenceEntry) []*lsproto.LocationLink {
|
|
links := make([]*lsproto.LocationLink, len(entries))
|
|
for i, entry := range entries {
|
|
|
|
// Get the selection range (the actual reference)
|
|
loc := l.getLocationOfEntry(entry)
|
|
targetSelectionRange := loc.Range
|
|
targetRange := targetSelectionRange
|
|
|
|
// For entries with nodes, compute ranges directly from the node
|
|
if entry.node != nil {
|
|
// Get the context range (broader scope including declaration context)
|
|
contextTextRange := toContextRange(entry.textRange, l.program.GetSourceFile(entry.fileName), entry.context)
|
|
if contextTextRange != nil {
|
|
contextLocation := l.getMappedLocation(entry.fileName, *contextTextRange)
|
|
targetRange = contextLocation.Range
|
|
}
|
|
}
|
|
|
|
links[i] = &lsproto.LocationLink{
|
|
TargetUri: lsconv.FileNameToDocumentURI(entry.fileName),
|
|
TargetRange: targetRange,
|
|
TargetSelectionRange: targetSelectionRange,
|
|
}
|
|
}
|
|
return links
|
|
}
|
|
|
|
func (l *LanguageService) mergeReferences(program *compiler.Program, referencesToMerge ...[]*SymbolAndEntries) []*SymbolAndEntries {
|
|
result := []*SymbolAndEntries{}
|
|
getSourceFileIndexOfEntry := func(program *compiler.Program, entry *ReferenceEntry) int {
|
|
var sourceFile *ast.SourceFile
|
|
if entry.kind == entryKindRange {
|
|
sourceFile = program.GetSourceFile(entry.fileName)
|
|
} else {
|
|
sourceFile = ast.GetSourceFileOfNode(entry.node)
|
|
}
|
|
return slices.Index(program.SourceFiles(), sourceFile)
|
|
}
|
|
|
|
for _, references := range referencesToMerge {
|
|
if len(references) == 0 {
|
|
continue
|
|
}
|
|
if len(result) == 0 {
|
|
result = references
|
|
continue
|
|
}
|
|
for _, entry := range references {
|
|
if entry.definition == nil || entry.definition.Kind != definitionKindSymbol {
|
|
result = append(result, entry)
|
|
continue
|
|
}
|
|
symbol := entry.definition.symbol
|
|
refIndex := core.FindIndex(result, func(ref *SymbolAndEntries) bool {
|
|
return ref.definition != nil &&
|
|
ref.definition.Kind == definitionKindSymbol &&
|
|
ref.definition.symbol == symbol
|
|
})
|
|
if refIndex == -1 {
|
|
result = append(result, entry)
|
|
continue
|
|
}
|
|
|
|
reference := result[refIndex]
|
|
sortedRefs := append(reference.references, entry.references...)
|
|
slices.SortStableFunc(sortedRefs, func(entry1, entry2 *ReferenceEntry) int {
|
|
entry1File := getSourceFileIndexOfEntry(program, entry1)
|
|
entry2File := getSourceFileIndexOfEntry(program, entry2)
|
|
if entry1File != entry2File {
|
|
return cmp.Compare(entry1File, entry2File)
|
|
}
|
|
|
|
return lsproto.CompareRanges(l.getRangeOfEntry(entry1), l.getRangeOfEntry(entry2))
|
|
})
|
|
result[refIndex] = &SymbolAndEntries{
|
|
definition: reference.definition,
|
|
references: sortedRefs,
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetReferencedSymbolsForNode returns all referenced symbols and their reference entries for the given node.
|
|
// It returns all referenced symbols and their reference entries for the given node across the provided source files.
|
|
func (l *LanguageService) GetReferencedSymbolsForNode(ctx context.Context, position int, node *ast.Node, sourceFiles []*ast.SourceFile) []*SymbolAndEntries {
|
|
return l.getReferencedSymbolsForNode(ctx, position, node, l.program, sourceFiles, refOptions{
|
|
use: referenceUseReferences,
|
|
})
|
|
}
|
|
|
|
// SignatureUsage represents a single usage of a signature declaration,
|
|
// pairing the reference name node with its containing call expression (if any).
|
|
type SignatureUsage struct {
|
|
Name *ast.Node // The identifier reference node
|
|
Call *ast.Node // The containing call expression, or nil if not a call usage
|
|
}
|
|
|
|
// GetSignatureUsages returns all usages of a signature declaration as name-call pairs.
|
|
// For each reference to the signature's name, it returns the reference node and
|
|
// the call expression it appears in (nil if the reference is not in a call position).
|
|
func (l *LanguageService) GetSignatureUsages(ctx context.Context, signatureDecl *ast.Node) []SignatureUsage {
|
|
name := signatureDecl.Name()
|
|
if name == nil || !ast.IsIdentifier(name) {
|
|
return nil
|
|
}
|
|
|
|
sourceFiles := l.program.GetSourceFiles()
|
|
entries := l.GetReferencedSymbolsForNode(ctx, name.Pos(), name, sourceFiles)
|
|
|
|
// Collect all declaration name nodes for the target symbol so we can
|
|
// filter them out — the caller wants usages, not declarations.
|
|
declNames := make(map[*ast.Node]bool)
|
|
for _, entry := range entries {
|
|
if entry.definition != nil && entry.definition.symbol != nil {
|
|
for _, decl := range entry.definition.symbol.Declarations {
|
|
if n := decl.Name(); n != nil {
|
|
declNames[n] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var result []SignatureUsage
|
|
for _, entry := range entries {
|
|
for _, ref := range entry.References() {
|
|
if !ref.IsNodeEntry() {
|
|
continue
|
|
}
|
|
node := ref.Node()
|
|
if node == nil || declNames[node] {
|
|
continue
|
|
}
|
|
|
|
called := ast.ClimbPastPropertyAccess(node)
|
|
|
|
var callExpr *ast.Node
|
|
if called.Parent != nil && ast.IsCallExpression(called.Parent) && called.Parent.Expression() == called {
|
|
callExpr = called.Parent
|
|
}
|
|
|
|
result = append(result, SignatureUsage{
|
|
Name: node,
|
|
Call: callExpr,
|
|
})
|
|
}
|
|
}
|
|
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) []*SymbolAndEntries {
|
|
// !!! cancellationToken
|
|
sourceFilesSet := collections.NewSetWithSizeHint[string](len(sourceFiles))
|
|
for _, file := range sourceFiles {
|
|
sourceFilesSet.Add(file.FileName())
|
|
}
|
|
|
|
if options.use == referenceUseReferences || options.use == referenceUseRename {
|
|
node = getAdjustedLocation(node, options.use == referenceUseRename, ast.GetSourceFileOfNode(node))
|
|
}
|
|
|
|
checker, done := program.GetTypeChecker(ctx)
|
|
defer done()
|
|
|
|
if node.Kind == ast.KindSourceFile {
|
|
resolvedRef := getReferenceAtPosition(node.AsSourceFile(), position, program)
|
|
if resolvedRef == nil || resolvedRef.file == nil {
|
|
return nil
|
|
}
|
|
|
|
if moduleSymbol := checker.GetMergedSymbol(resolvedRef.file.Symbol); moduleSymbol != nil {
|
|
return l.getReferencedSymbolsForModule(ctx, program, moduleSymbol /*excludeImportTypeOfExportEquals*/, false, sourceFiles, sourceFilesSet)
|
|
}
|
|
|
|
// !!! not implemented
|
|
// fileIncludeReasons := program.getFileIncludeReasons();
|
|
// if (!fileIncludeReasons) {
|
|
// return nil
|
|
// }
|
|
return []*SymbolAndEntries{{
|
|
definition: &Definition{Kind: definitionKindTripleSlashReference, tripleSlashFileRef: &tripleSlashDefinition{reference: resolvedRef.reference}},
|
|
references: getReferencesForNonModule(resolvedRef.file, program /*fileIncludeReasons,*/),
|
|
}}
|
|
}
|
|
|
|
if !options.implementations {
|
|
// !!! cancellationToken
|
|
if special := getReferencedSymbolsSpecial(node, sourceFiles); special != nil {
|
|
return special
|
|
}
|
|
}
|
|
|
|
// constructors should use the class symbol, detected by name, if present
|
|
symbol := checker.GetSymbolAtLocation(core.IfElse(node.Kind == ast.KindConstructor && node.Parent.Name() != nil, node.Parent.Name(), node))
|
|
// Could not find a symbol e.g. unknown identifier
|
|
if symbol == nil {
|
|
// String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial.
|
|
if !options.implementations && ast.IsStringLiteralLike(node) {
|
|
if isModuleSpecifierLike(node) {
|
|
// !!! not implemented
|
|
// fileIncludeReasons := program.GetFileIncludeReasons()
|
|
// if referencedFile := program.GetResolvedModuleFromModuleSpecifier(node, nil /*sourceFile*/); referencedFile != nil {
|
|
// return []*SymbolAndEntries{{
|
|
// definition: &Definition{Kind: definitionKindString, node: node},
|
|
// references: getReferencesForNonModule(referencedFile, program /*fileIncludeReasons,*/),
|
|
// }}
|
|
// }
|
|
// Fall through to string literal references. This is not very likely to return
|
|
// anything useful, but I guess it's better than nothing, and there's an existing
|
|
// test that expects this to happen (fourslash/cases/untypedModuleImport.ts).
|
|
}
|
|
return l.getReferencesForStringLiteral(ctx, node, sourceFiles, checker)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if symbol.Name == ast.InternalSymbolNameExportEquals {
|
|
if symbol.Parent == nil {
|
|
return nil
|
|
}
|
|
return l.getReferencedSymbolsForModule(ctx, program, symbol.Parent, false /*excludeImportTypeOfExportEquals*/, sourceFiles, sourceFilesSet)
|
|
}
|
|
|
|
moduleReferences := l.getReferencedSymbolsForModuleIfDeclaredBySourceFile(ctx, symbol, program, sourceFiles, checker, options, sourceFilesSet)
|
|
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)
|
|
|
|
references := getReferencedSymbolsForSymbol(ctx, program, symbol, node, sourceFiles, sourceFilesSet, checker, options)
|
|
return l.mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget)
|
|
}
|
|
|
|
func (l *LanguageService) getReferencesForStringLiteral(
|
|
ctx context.Context,
|
|
node *ast.StringLiteralLike,
|
|
sourceFiles []*ast.SourceFile,
|
|
checker *checker.Checker,
|
|
) []*SymbolAndEntries {
|
|
t := getContextualTypeFromParentOrAncestorTypeNode(node, checker)
|
|
references := core.FlatMap(sourceFiles, func(sourceFile *ast.SourceFile) []*ReferenceEntry {
|
|
if ctx.Err() != nil {
|
|
return nil
|
|
}
|
|
var entries []*ReferenceEntry
|
|
possibleReferences := getPossibleSymbolReferenceNodes(sourceFile, node.Text(), nil /*container*/)
|
|
for _, ref := range possibleReferences {
|
|
if ast.IsStringLiteralLike(ref) && ref.Text() == node.Text() {
|
|
if t != nil {
|
|
refType := getContextualTypeFromParentOrAncestorTypeNode(ref, checker)
|
|
if t != checker.GetStringType() &&
|
|
(t == refType || isStringLiteralPropertyReference(ref, checker)) {
|
|
entries = append(entries, newNodeEntryWithKind(ref, entryKindStringLiteral))
|
|
}
|
|
} else {
|
|
if ast.IsNoSubstitutionTemplateLiteral(ref) && !printer.RangeIsOnSingleLine(ref.Loc, sourceFile) {
|
|
continue
|
|
}
|
|
entries = append(entries, newNodeEntryWithKind(ref, entryKindStringLiteral))
|
|
}
|
|
}
|
|
}
|
|
return entries
|
|
})
|
|
|
|
return []*SymbolAndEntries{
|
|
{
|
|
definition: &Definition{Kind: definitionKindString, node: node},
|
|
references: references,
|
|
},
|
|
}
|
|
}
|
|
|
|
func isStringLiteralPropertyReference(node *ast.StringLiteralLike, checker *checker.Checker) bool {
|
|
if ast.IsPropertySignatureDeclaration(node.Parent) {
|
|
return checker.GetPropertyOfType(checker.GetTypeAtLocation(node.Parent.Parent), node.Text()) != nil
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (l *LanguageService) getReferencedSymbolsForModuleIfDeclaredBySourceFile(ctx context.Context, symbol *ast.Symbol, program *compiler.Program, sourceFiles []*ast.SourceFile, checker *checker.Checker, options refOptions, sourceFilesSet *collections.Set[string]) []*SymbolAndEntries {
|
|
moduleSourceFileName := ""
|
|
if symbol == nil || !((symbol.Flags&ast.SymbolFlagsModule != 0) && len(symbol.Declarations) != 0) {
|
|
return nil
|
|
}
|
|
if moduleSourceFile := core.Find(symbol.Declarations, ast.IsSourceFile); moduleSourceFile != nil {
|
|
moduleSourceFileName = moduleSourceFile.AsSourceFile().FileName()
|
|
} else {
|
|
return nil
|
|
}
|
|
exportEquals := symbol.Exports[ast.InternalSymbolNameExportEquals]
|
|
// If exportEquals != nil, we're about to add references to `import("mod")` anyway, so don't double-count them.
|
|
moduleReferences := l.getReferencedSymbolsForModule(ctx, program, symbol, exportEquals != nil, sourceFiles, sourceFilesSet)
|
|
if exportEquals == nil || exportEquals.Flags&ast.SymbolFlagsAlias == 0 || !sourceFilesSet.Has(moduleSourceFileName) {
|
|
return moduleReferences
|
|
}
|
|
symbol, _ = checker.ResolveAlias(exportEquals)
|
|
return l.mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(ctx, program, 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{ast.GetSourceFileOfNode(searchSpaceNode)}
|
|
}
|
|
references := core.Map(
|
|
core.FlatMap(filesToSearch, func(sourceFile *ast.SourceFile) []*ast.Node {
|
|
// cancellationToken.throwIfCancellationRequested();
|
|
return core.Filter(
|
|
getPossibleSymbolReferenceNodes(sourceFile, "this", core.IfElse(searchSpaceNode.Kind == ast.KindSourceFile, sourceFile.AsNode(), searchSpaceNode)),
|
|
func(node *ast.Node) bool {
|
|
if !isThis(node) {
|
|
return false
|
|
}
|
|
container := ast.GetThisContainer(node /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/, false)
|
|
if !ast.CanHaveSymbol(container) {
|
|
return false
|
|
}
|
|
switch searchSpaceNode.Kind {
|
|
case ast.KindFunctionExpression, ast.KindFunctionDeclaration:
|
|
return searchSpaceNode.Symbol() == container.Symbol()
|
|
case ast.KindMethodDeclaration, ast.KindMethodSignature:
|
|
return ast.IsObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.Symbol() == container.Symbol()
|
|
case ast.KindClassExpression, ast.KindClassDeclaration, ast.KindObjectLiteralExpression:
|
|
// Make sure the container belongs to the same class/object literals
|
|
// and has the appropriate static modifier from the original container.
|
|
return container.Parent != nil && ast.CanHaveSymbol(container.Parent) && searchSpaceNode.Symbol() == container.Parent.Symbol() && ast.IsStatic(container) == (staticFlag != ast.ModifierFlagsNone)
|
|
case ast.KindSourceFile:
|
|
return container.Kind == ast.KindSourceFile && !ast.IsExternalModule(container.AsSourceFile()) && !isParameterName(node)
|
|
}
|
|
return false
|
|
},
|
|
)
|
|
}),
|
|
func(n *ast.Node) *ReferenceEntry { return newNodeEntry(n) },
|
|
)
|
|
|
|
thisParameter := core.FirstNonNil(references, func(ref *ReferenceEntry) *ast.Node {
|
|
if ref.node.Parent.Kind == ast.KindParameter {
|
|
return ref.node
|
|
}
|
|
return nil
|
|
})
|
|
if thisParameter == nil {
|
|
thisParameter = thisOrSuperKeyword
|
|
}
|
|
return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindThis, thisParameter, searchSpaceNode.Symbol(), references)}
|
|
}
|
|
|
|
func getReferencesForSuperKeyword(superKeyword *ast.Node) []*SymbolAndEntries {
|
|
searchSpaceNode := ast.GetSuperContainer(superKeyword, false /*stopOnFunctions*/)
|
|
if searchSpaceNode == nil {
|
|
return nil
|
|
}
|
|
// Whether 'super' occurs in a static context within a class.
|
|
staticFlag := ast.ModifierFlagsStatic
|
|
|
|
switch searchSpaceNode.Kind {
|
|
case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor:
|
|
staticFlag &= searchSpaceNode.ModifierFlags()
|
|
searchSpaceNode = searchSpaceNode.Parent // re-assign to be the owning class
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
sourceFile := ast.GetSourceFileOfNode(searchSpaceNode)
|
|
references := core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), func(node *ast.Node) *ReferenceEntry {
|
|
if node.Kind != ast.KindSuperKeyword {
|
|
return nil
|
|
}
|
|
|
|
container := ast.GetSuperContainer(node, false /*stopOnFunctions*/)
|
|
|
|
// If we have a 'super' container, we must have an enclosing class.
|
|
// Now make sure the owning class is the same as the search-space
|
|
// and has the same static qualifier as the original 'super's owner.
|
|
if container != nil && ast.IsStatic(container) == (staticFlag != ast.ModifierFlagsNone) && container.Parent.Symbol() == searchSpaceNode.Symbol() {
|
|
return newNodeEntry(node)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindSymbol, nil, searchSpaceNode.Symbol(), references)}
|
|
}
|
|
|
|
func getAllReferencesForImportMeta(sourceFiles []*ast.SourceFile) []*SymbolAndEntries {
|
|
references := core.FlatMap(sourceFiles, func(sourceFile *ast.SourceFile) []*ReferenceEntry {
|
|
return core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, "meta", sourceFile.AsNode()), func(node *ast.Node) *ReferenceEntry {
|
|
parent := node.Parent
|
|
if ast.IsImportMeta(parent) {
|
|
return newNodeEntry(parent)
|
|
}
|
|
return nil
|
|
})
|
|
})
|
|
if len(references) == 0 {
|
|
return nil
|
|
}
|
|
return []*SymbolAndEntries{{definition: &Definition{Kind: definitionKindKeyword, node: references[0].node}, references: references}}
|
|
}
|
|
|
|
func getAllReferencesForKeyword(sourceFiles []*ast.SourceFile, keywordKind ast.Kind, filterReadOnlyTypeOperator bool) []*SymbolAndEntries {
|
|
// references is a list of NodeEntry
|
|
references := core.FlatMap(sourceFiles, func(sourceFile *ast.SourceFile) []*ReferenceEntry {
|
|
// cancellationToken.throwIfCancellationRequested();
|
|
return core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, scanner.TokenToString(keywordKind), sourceFile.AsNode()), func(referenceLocation *ast.Node) *ReferenceEntry {
|
|
if referenceLocation.Kind == keywordKind && (!filterReadOnlyTypeOperator || isReadonlyTypeOperator(referenceLocation)) {
|
|
return newNodeEntry(referenceLocation)
|
|
}
|
|
return nil
|
|
})
|
|
})
|
|
if len(references) == 0 {
|
|
return nil
|
|
}
|
|
return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindKeyword, references[0].node, nil, references)}
|
|
}
|
|
|
|
func getPossibleSymbolReferenceNodes(sourceFile *ast.SourceFile, symbolName string, container *ast.Node) []*ast.Node {
|
|
return core.MapNonNil(getPossibleSymbolReferencePositions(sourceFile, symbolName, container), func(pos int) *ast.Node {
|
|
if referenceLocation := astnav.GetTouchingPropertyName(sourceFile, pos); referenceLocation != sourceFile.AsNode() {
|
|
return referenceLocation
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func getPossibleSymbolReferencePositions(sourceFile *ast.SourceFile, symbolName string, container *ast.Node) []int {
|
|
positions := []int{}
|
|
|
|
/// TODO: Cache symbol existence for files to save text search
|
|
// Also, need to make this work for unicode escapes.
|
|
|
|
// Be resilient in the face of a symbol with no name or zero length name
|
|
if symbolName == "" {
|
|
return positions
|
|
}
|
|
|
|
text := sourceFile.Text()
|
|
sourceLength := len(text)
|
|
symbolNameLength := len(symbolName)
|
|
|
|
if container == nil {
|
|
container = sourceFile.AsNode()
|
|
}
|
|
|
|
position := strings.Index(text[container.Pos():], symbolName)
|
|
endPos := container.End()
|
|
for position >= 0 && position < endPos {
|
|
// We found a match. Make sure it's not part of a larger word (i.e. the char
|
|
// before and after it have to be a non-identifier char).
|
|
endPosition := position + symbolNameLength
|
|
|
|
if (position == 0 || !scanner.IsIdentifierPart(rune(text[position-1]))) &&
|
|
(endPosition == sourceLength || !scanner.IsIdentifierPart(rune(text[endPosition]))) {
|
|
// Found a real match. Keep searching.
|
|
positions = append(positions, position)
|
|
}
|
|
startIndex := position + symbolNameLength + 1
|
|
if startIndex > len(text) {
|
|
break
|
|
}
|
|
if foundIndex := strings.Index(text[startIndex:], symbolName); foundIndex != -1 {
|
|
position = startIndex + foundIndex
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
return positions
|
|
}
|
|
|
|
// findFirstJsxNode recursively searches for the first JSX element, self-closing element, or fragment
|
|
func findFirstJsxNode(root *ast.Node) *ast.Node {
|
|
var visit func(*ast.Node) *ast.Node
|
|
visit = func(node *ast.Node) *ast.Node {
|
|
// Check if this is a JSX node we're looking for
|
|
switch node.Kind {
|
|
case ast.KindJsxElement, ast.KindJsxSelfClosingElement, ast.KindJsxFragment:
|
|
return node
|
|
}
|
|
|
|
// Skip subtree if it doesn't contain JSX
|
|
if node.SubtreeFacts()&ast.SubtreeContainsJsx == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Traverse children to find JSX node
|
|
var result *ast.Node
|
|
node.ForEachChild(func(child *ast.Node) bool {
|
|
result = visit(child)
|
|
return result != nil // Stop if found
|
|
})
|
|
return result
|
|
}
|
|
|
|
return visit(root)
|
|
}
|
|
|
|
func getReferencesForNonModule(referencedFile *ast.SourceFile, program *compiler.Program) []*ReferenceEntry {
|
|
// !!! not implemented
|
|
return []*ReferenceEntry{}
|
|
}
|
|
|
|
func getMergedAliasedSymbolOfNamespaceExportDeclaration(node *ast.Node, symbol *ast.Symbol, checker *checker.Checker) *ast.Symbol {
|
|
if node.Parent != nil && node.Parent.Kind == ast.KindNamespaceExportDeclaration {
|
|
if aliasedSymbol, ok := checker.ResolveAlias(symbol); ok {
|
|
targetSymbol := checker.GetMergedSymbol(aliasedSymbol)
|
|
if aliasedSymbol != targetSymbol {
|
|
return targetSymbol
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LanguageService) getReferencedSymbolsForModule(ctx context.Context, program *compiler.Program, symbol *ast.Symbol, excludeImportTypeOfExportEquals bool, sourceFiles []*ast.SourceFile, sourceFilesSet *collections.Set[string]) []*SymbolAndEntries {
|
|
debug.Assert(symbol.ValueDeclaration != nil)
|
|
|
|
checker, done := program.GetTypeChecker(ctx)
|
|
defer done()
|
|
|
|
moduleRefs := findModuleReferences(program, sourceFiles, symbol, checker)
|
|
references := core.MapNonNil(moduleRefs, func(reference ModuleReference) *ReferenceEntry {
|
|
switch reference.kind {
|
|
case ModuleReferenceKindImport:
|
|
parent := reference.literal.Parent
|
|
if ast.IsLiteralTypeNode(parent) {
|
|
importType := parent.Parent
|
|
if ast.IsImportTypeNode(importType) {
|
|
importTypeNode := importType.AsImportTypeNode()
|
|
if excludeImportTypeOfExportEquals && importTypeNode.Qualifier == nil {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
// import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway.
|
|
return newNodeEntry(reference.literal)
|
|
case ModuleReferenceKindImplicit:
|
|
// For implicit references (e.g., JSX runtime imports), return the first JSX node,
|
|
// the first statement, or the whole file
|
|
var rangeNode *ast.Node
|
|
|
|
// Skip the JSX search for tslib imports
|
|
if reference.literal.Text() != "tslib" {
|
|
rangeNode = findFirstJsxNode(reference.referencingFile.AsNode())
|
|
}
|
|
|
|
if rangeNode == nil {
|
|
if reference.referencingFile.Statements != nil && len(reference.referencingFile.Statements.Nodes) > 0 {
|
|
rangeNode = reference.referencingFile.Statements.Nodes[0]
|
|
} else {
|
|
rangeNode = reference.referencingFile.AsNode()
|
|
}
|
|
}
|
|
return newNodeEntry(rangeNode)
|
|
case ModuleReferenceKindReference:
|
|
return &ReferenceEntry{
|
|
kind: entryKindRange,
|
|
fileName: reference.referencingFile.FileName(),
|
|
textRange: &reference.ref.TextRange,
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Add references to the module declarations themselves
|
|
if len(symbol.Declarations) > 0 {
|
|
for _, decl := range symbol.Declarations {
|
|
switch decl.Kind {
|
|
case ast.KindSourceFile:
|
|
// Don't include the source file itself. (This may not be ideal behavior, but awkward to include an entire file as a reference.)
|
|
continue
|
|
case ast.KindModuleDeclaration:
|
|
if sourceFilesSet.Has(ast.GetSourceFileOfNode(decl).FileName()) {
|
|
references = append(references, newNodeEntry(decl.AsModuleDeclaration().Name()))
|
|
}
|
|
default:
|
|
// This may be merged with something (e.g. a class merged with a namespace).
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle export equals declarations
|
|
exported := symbol.Exports[ast.InternalSymbolNameExportEquals]
|
|
if exported != nil && len(exported.Declarations) > 0 {
|
|
for _, decl := range exported.Declarations {
|
|
sourceFile := ast.GetSourceFileOfNode(decl)
|
|
if sourceFilesSet.Has(sourceFile.FileName()) {
|
|
var node *ast.Node
|
|
// At `module.exports = ...`, reference node is `module`
|
|
if ast.IsBinaryExpression(decl) && ast.IsPropertyAccessExpression(decl.AsBinaryExpression().Left) {
|
|
node = decl.AsBinaryExpression().Left.Expression()
|
|
} else if ast.IsExportAssignment(decl) {
|
|
// Find the export keyword
|
|
node = astnav.FindChildOfKind(decl, ast.KindExportKeyword, sourceFile)
|
|
debug.Assert(node != nil, "Expected to find export keyword")
|
|
} else {
|
|
node = ast.GetNameOfDeclaration(decl)
|
|
if node == nil {
|
|
node = decl
|
|
}
|
|
}
|
|
references = append(references, newNodeEntry(node))
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(references) > 0 {
|
|
return []*SymbolAndEntries{{
|
|
definition: &Definition{Kind: definitionKindSymbol, symbol: symbol},
|
|
references: references,
|
|
}}
|
|
}
|
|
return []*SymbolAndEntries{}
|
|
}
|
|
|
|
// -- 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(ctx context.Context, program *compiler.Program, 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(ctx, program, sourceFiles, sourceFilesSet, node, checker, searchMeaning, options)
|
|
|
|
var exportSpecifier *ast.Node
|
|
if isForRenameWithPrefixAndSuffixText(options) && len(symbol.Declarations) != 0 {
|
|
exportSpecifier = core.Find(symbol.Declarations, ast.IsExportSpecifier)
|
|
}
|
|
if exportSpecifier != nil {
|
|
// 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, ImpExpKindUnknown /*comingFrom*/, "", nil), true /*addReferencesHere*/, true /*alwaysGetReferences*/)
|
|
} else if node != nil && node.Kind == ast.KindDefaultKeyword && symbol.Name == ast.InternalSymbolNameDefault && symbol.Parent != nil {
|
|
state.addReference(node, symbol, entryKindNode)
|
|
state.searchForImportsOfExport(node, symbol, &ExportInfo{exportingModuleSymbol: symbol.Parent, exportKind: ExportKindDefault})
|
|
} else {
|
|
search := state.createSearch(node, symbol, ImpExpKindUnknown /*comingFrom*/, "", state.populateSearchSymbolSet(symbol, node, options.use == referenceUseRename, options.useAliasesForRename, options.implementations))
|
|
state.getReferencesInContainerOrFiles(symbol, search)
|
|
}
|
|
|
|
return state.result
|
|
}
|
|
|
|
// Symbol that is currently being searched for.
|
|
// This will be replaced if we find an alias for the symbol.
|
|
type refSearch struct {
|
|
// If coming from an export, we will not recursively search for the imported symbol (since that's where we came from).
|
|
comingFrom ImpExpKind // import, export
|
|
|
|
symbol *ast.Symbol
|
|
text string
|
|
escapedText string
|
|
|
|
// Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access.
|
|
parents []*ast.Symbol
|
|
|
|
allSearchSymbols []*ast.Symbol
|
|
|
|
// Whether a symbol is in the search set.
|
|
// Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`.
|
|
includes func(symbol *ast.Symbol) bool
|
|
}
|
|
|
|
type inheritKey struct {
|
|
symbol *ast.Symbol
|
|
parent *ast.Symbol
|
|
}
|
|
|
|
type refState struct {
|
|
sourceFiles []*ast.SourceFile
|
|
sourceFilesSet *collections.Set[string]
|
|
specialSearchKind string // "none", "constructor", or "class"
|
|
checker *checker.Checker
|
|
ctx context.Context
|
|
program *compiler.Program
|
|
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(ctx context.Context, program *compiler.Program, 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,
|
|
ctx: ctx,
|
|
program: program,
|
|
searchMeaning: searchMeaning,
|
|
options: options,
|
|
inheritsFromCache: map[inheritKey]bool{},
|
|
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.ctx, state.program, state.sourceFiles, state.sourceFilesSet, state.checker)
|
|
}
|
|
return state.importTracker(exportSymbol, exportInfo, state.options.use == referenceUseRename)
|
|
}
|
|
|
|
// @param allSearchSymbols set of additional symbols for use by `includes`
|
|
func (state *refState) createSearch(location *ast.Node, symbol *ast.Symbol, comingFrom ImpExpKind, text string, allSearchSymbols []*ast.Symbol) *refSearch {
|
|
// Note: if this is an external module symbol, the name doesn't include quotes.
|
|
// Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`.
|
|
// The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form
|
|
// here appears to be intentional).
|
|
if text == "" {
|
|
s := binder.GetLocalSymbolForExportDefault(symbol)
|
|
if s == nil {
|
|
s = getNonModuleSymbolOfMergedModuleSymbol(symbol)
|
|
if s == nil {
|
|
s = symbol
|
|
}
|
|
}
|
|
text = stringutil.StripQuotes(ast.SymbolName(s))
|
|
}
|
|
if len(allSearchSymbols) == 0 {
|
|
allSearchSymbols = []*ast.Symbol{symbol}
|
|
}
|
|
search := &refSearch{
|
|
symbol: symbol,
|
|
comingFrom: comingFrom,
|
|
text: text,
|
|
escapedText: text,
|
|
allSearchSymbols: allSearchSymbols,
|
|
includes: func(sym *ast.Symbol) bool { return slices.Contains(allSearchSymbols, sym) },
|
|
}
|
|
if state.options.implementations && location != nil {
|
|
search.parents = getParentSymbolsOfPropertyAccess(location, symbol, state.checker)
|
|
}
|
|
return search
|
|
}
|
|
|
|
func (state *refState) referenceAdder(searchSymbol *ast.Symbol) func(*ast.Node, entryKind) {
|
|
symbolAndEntries := state.symbolToReferences[searchSymbol]
|
|
if symbolAndEntries == nil {
|
|
symbolAndEntries = NewSymbolAndEntries(definitionKindSymbol, nil, searchSymbol, nil)
|
|
state.symbolToReferences[searchSymbol] = symbolAndEntries
|
|
state.result = append(state.result, symbolAndEntries)
|
|
}
|
|
return func(node *ast.Node, kind entryKind) {
|
|
symbolAndEntries.references = append(symbolAndEntries.references, newNodeEntryWithKind(node, kind))
|
|
}
|
|
}
|
|
|
|
func (state *refState) addReference(referenceLocation *ast.Node, symbol *ast.Symbol, kind entryKind) {
|
|
// if rename symbol from default export anonymous function, for example `export default function() {}`, we do not need to add reference
|
|
if state.options.use == referenceUseRename && referenceLocation.Kind == ast.KindDefaultKeyword {
|
|
return
|
|
}
|
|
|
|
addRef := state.referenceAdder(symbol)
|
|
if state.options.implementations {
|
|
state.addImplementationReferences(referenceLocation, func(n *ast.Node) { addRef(n, kind) })
|
|
} else {
|
|
addRef(referenceLocation, kind)
|
|
}
|
|
}
|
|
|
|
func getReferenceEntriesForShorthandPropertyAssignment(node *ast.Node, checker *checker.Checker, addReference func(*ast.Node)) {
|
|
refSymbol := checker.GetSymbolAtLocation(node)
|
|
if refSymbol == nil || refSymbol.ValueDeclaration == nil {
|
|
return
|
|
}
|
|
shorthandSymbol := checker.GetShorthandAssignmentValueSymbol(refSymbol.ValueDeclaration)
|
|
if shorthandSymbol != nil && len(shorthandSymbol.Declarations) > 0 {
|
|
for _, declaration := range shorthandSymbol.Declarations {
|
|
if ast.GetMeaningFromDeclaration(declaration)&ast.SemanticMeaningValue != 0 {
|
|
addReference(declaration)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func isMethodOrAccessor(node *ast.Node) bool {
|
|
return node.Kind == ast.KindMethodDeclaration || node.Kind == ast.KindGetAccessor || node.Kind == ast.KindSetAccessor
|
|
}
|
|
|
|
func tryGetClassByExtendingIdentifier(node *ast.Node) *ast.ClassLikeDeclaration {
|
|
return ast.TryGetClassExtendingExpressionWithTypeArguments(ast.ClimbPastPropertyAccess(node).Parent)
|
|
}
|
|
|
|
func getClassConstructorSymbol(classSymbol *ast.Symbol) *ast.Symbol {
|
|
if classSymbol.Members == nil {
|
|
return nil
|
|
}
|
|
return classSymbol.Members[ast.InternalSymbolNameConstructor]
|
|
}
|
|
|
|
func hasOwnConstructor(classDeclaration *ast.ClassLikeDeclaration) bool {
|
|
return getClassConstructorSymbol(classDeclaration.Symbol()) != nil
|
|
}
|
|
|
|
func findOwnConstructorReferences(classSymbol *ast.Symbol, sourceFile *ast.SourceFile, addNode func(*ast.Node)) {
|
|
constructorSymbol := getClassConstructorSymbol(classSymbol)
|
|
if constructorSymbol != nil && len(constructorSymbol.Declarations) > 0 {
|
|
for _, decl := range constructorSymbol.Declarations {
|
|
if decl.Kind == ast.KindConstructor {
|
|
if ctrKeyword := astnav.FindChildOfKind(decl, ast.KindConstructorKeyword, sourceFile); ctrKeyword != nil {
|
|
addNode(ctrKeyword)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if classSymbol.Exports != nil {
|
|
for _, member := range classSymbol.Exports {
|
|
decl := member.ValueDeclaration
|
|
if decl != nil && decl.Kind == ast.KindMethodDeclaration {
|
|
body := decl.Body()
|
|
if body != nil {
|
|
forEachDescendantOfKind(body, ast.KindThisKeyword, func(thisKeyword *ast.Node) {
|
|
if ast.IsNewExpressionTarget(thisKeyword, false, false) {
|
|
addNode(thisKeyword)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func findSuperConstructorAccesses(classDeclaration *ast.ClassLikeDeclaration, addNode func(*ast.Node)) {
|
|
constructorSymbol := getClassConstructorSymbol(classDeclaration.Symbol())
|
|
if constructorSymbol == nil || len(constructorSymbol.Declarations) == 0 {
|
|
return
|
|
}
|
|
|
|
for _, decl := range constructorSymbol.Declarations {
|
|
if decl.Kind == ast.KindConstructor {
|
|
body := decl.Body()
|
|
if body != nil {
|
|
forEachDescendantOfKind(body, ast.KindSuperKeyword, func(node *ast.Node) {
|
|
if ast.IsCallExpressionTarget(node, false, false) {
|
|
addNode(node)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func forEachDescendantOfKind(node *ast.Node, kind ast.Kind, action func(*ast.Node)) {
|
|
node.ForEachChild(func(child *ast.Node) bool {
|
|
if child.Kind == kind {
|
|
action(child)
|
|
}
|
|
forEachDescendantOfKind(child, kind, action)
|
|
return false
|
|
})
|
|
}
|
|
|
|
func (state *refState) addImplementationReferences(refNode *ast.Node, addRef func(*ast.Node)) {
|
|
// Check if we found a function/propertyAssignment/method with an implementation or initializer
|
|
if ast.IsDeclarationName(refNode) && isImplementation(refNode.Parent) {
|
|
addRef(refNode)
|
|
return
|
|
}
|
|
|
|
if refNode.Kind != ast.KindIdentifier {
|
|
return
|
|
}
|
|
|
|
if refNode.Parent.Kind == ast.KindShorthandPropertyAssignment {
|
|
// Go ahead and dereference the shorthand assignment by going to its definition
|
|
getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addRef)
|
|
}
|
|
|
|
// Check if the node is within an extends or implements clause
|
|
|
|
if containingNode := getContainingNodeIfInHeritageClause(refNode); containingNode != nil {
|
|
addRef(containingNode)
|
|
return
|
|
}
|
|
|
|
// If we got a type reference, try and see if the reference applies to any expressions that can implement an interface
|
|
// Find the first node whose parent isn't a type node -- i.e., the highest type node.
|
|
typeNode := ast.FindAncestor(refNode, func(a *ast.Node) bool {
|
|
return !ast.IsQualifiedName(a.Parent) && !ast.IsTypeNode(a.Parent) && !ast.IsTypeElement(a.Parent)
|
|
})
|
|
|
|
if typeNode == nil || typeNode.Parent.Type() == nil {
|
|
return
|
|
}
|
|
|
|
typeHavingNode := typeNode.Parent
|
|
if typeHavingNode.Type() == typeNode && state.seenContainingTypeReferences.AddIfAbsent(typeHavingNode) {
|
|
addIfImplementation := func(e *ast.Expression) {
|
|
if isImplementationExpression(e) {
|
|
addRef(e)
|
|
}
|
|
}
|
|
if ast.HasInitializer(typeHavingNode) {
|
|
addIfImplementation(typeHavingNode.Initializer())
|
|
} else if ast.IsFunctionLike(typeHavingNode) && typeHavingNode.Body() != nil {
|
|
body := typeHavingNode.Body()
|
|
if body.Kind == ast.KindBlock {
|
|
ast.ForEachReturnStatement(body, func(returnStatement *ast.Node) bool {
|
|
if expr := returnStatement.Expression(); expr != nil {
|
|
addIfImplementation(expr)
|
|
}
|
|
return false
|
|
})
|
|
} else {
|
|
addIfImplementation(body)
|
|
}
|
|
} else if ast.IsAssertionExpression(typeHavingNode) || ast.IsSatisfiesExpression(typeHavingNode) {
|
|
addIfImplementation(typeHavingNode.Expression())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (state *refState) getReferencesInContainerOrFiles(symbol *ast.Symbol, search *refSearch) {
|
|
// Try to get the smallest valid scope that we can limit our search to;
|
|
// otherwise we'll need to search globally (i.e. include each file).
|
|
if scope := getSymbolScope(symbol); scope != nil {
|
|
addReferencesHere := scope.Kind != ast.KindSourceFile || slices.Contains(state.sourceFiles, scope.AsSourceFile())
|
|
state.getReferencesInContainer(scope, ast.GetSourceFileOfNode(scope), search, addReferencesHere)
|
|
} else {
|
|
// Global search
|
|
for _, sourceFile := range state.sourceFiles {
|
|
// state.cancellationToken.throwIfCancellationRequested();
|
|
state.searchForName(sourceFile, search)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (state *refState) getReferencesInSourceFile(sourceFile *ast.SourceFile, search *refSearch, addReferencesHere bool) {
|
|
// state.cancellationToken.throwIfCancellationRequested();
|
|
state.getReferencesInContainer(sourceFile.AsNode(), sourceFile, search, addReferencesHere)
|
|
}
|
|
|
|
func (state *refState) getReferencesInContainer(container *ast.Node, sourceFile *ast.SourceFile, search *refSearch, addReferencesHere bool) {
|
|
// Search within node "container" for references for a search value, where the search value is defined as a
|
|
// tuple of (searchSymbol, searchText, searchLocation, and searchMeaning).
|
|
// searchLocation: a node where the search value
|
|
if !state.markSearchedSymbols(sourceFile, search.allSearchSymbols) {
|
|
return
|
|
}
|
|
|
|
for _, position := range getPossibleSymbolReferencePositions(sourceFile, search.text, container) {
|
|
state.getReferencesAtLocation(sourceFile, position, search, addReferencesHere)
|
|
}
|
|
}
|
|
|
|
func (state *refState) markSearchedSymbols(sourceFile *ast.SourceFile, symbols []*ast.Symbol) bool {
|
|
seenSymbols := state.sourceFileToSeenSymbols[sourceFile]
|
|
if seenSymbols == nil {
|
|
seenSymbols = &collections.Set[*ast.Symbol]{}
|
|
state.sourceFileToSeenSymbols[sourceFile] = seenSymbols
|
|
}
|
|
anyNewSymbols := false
|
|
for _, sym := range symbols {
|
|
if seenSymbols.AddIfAbsent(sym) {
|
|
anyNewSymbols = true
|
|
}
|
|
}
|
|
return anyNewSymbols
|
|
}
|
|
|
|
func (state *refState) getReferencesAtLocation(sourceFile *ast.SourceFile, position int, search *refSearch, addReferencesHere bool) {
|
|
referenceLocation := astnav.GetTouchingPropertyName(sourceFile, position)
|
|
|
|
if !isValidReferencePosition(referenceLocation, search.text) {
|
|
// This wasn't the start of a token. Check to see if it might be a
|
|
// match in a comment or string if that's what the caller is asking
|
|
// for.
|
|
|
|
// !!! not implemented
|
|
// if (!state.options.implementations && (state.options.findInStrings && isInString(sourceFile, position) || state.options.findInComments && isInNonReferenceComment(sourceFile, position))) {
|
|
// // In the case where we're looking inside comments/strings, we don't have
|
|
// // an actual definition. So just use 'undefined' here. Features like
|
|
// // 'Rename' won't care (as they ignore the definitions), and features like
|
|
// // 'FindReferences' will just filter out these results.
|
|
// state.addStringOrCommentReference(sourceFile.FileName, createTextSpan(position, search.text.length));
|
|
// }
|
|
|
|
return
|
|
}
|
|
|
|
if getMeaningFromLocation(referenceLocation)&state.searchMeaning == 0 {
|
|
return
|
|
}
|
|
|
|
referenceSymbol := state.checker.GetSymbolAtLocation(referenceLocation)
|
|
if referenceSymbol == nil {
|
|
return
|
|
}
|
|
|
|
parent := referenceLocation.Parent
|
|
if parent.Kind == ast.KindImportSpecifier && parent.PropertyName() == referenceLocation {
|
|
// This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again.
|
|
return
|
|
}
|
|
|
|
if parent.Kind == ast.KindExportSpecifier {
|
|
state.getReferencesAtExportSpecifier(referenceLocation, 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.IsVariableDeclarationInitializedToBareOrAccessedRequire(referenceLocation.Parent.Parent.Parent) {
|
|
referenceSymbol = referenceLocation.Parent.Symbol()
|
|
// The parent will not have a symbol if it's an ObjectBindingPattern (when destructuring is used). In
|
|
// this case, just skip it, since the bound identifiers are not an alias of the import.
|
|
if referenceSymbol == nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
state.getImportOrExportReferences(referenceLocation, referenceSymbol, search)
|
|
}
|
|
|
|
func (state *refState) addConstructorReferences(referenceLocation *ast.Node, symbol *ast.Symbol, search *refSearch, addReferencesHere bool) {
|
|
if ast.IsNewExpressionTarget(referenceLocation, false, false) && addReferencesHere {
|
|
state.addReference(referenceLocation, symbol, entryKindNode)
|
|
}
|
|
|
|
pusher := func() func(*ast.Node, entryKind) {
|
|
return state.referenceAdder(search.symbol)
|
|
}
|
|
|
|
if ast.IsClassLike(referenceLocation.Parent) {
|
|
// This is the class declaration containing the constructor.
|
|
sourceFile := ast.GetSourceFileOfNode(referenceLocation)
|
|
findOwnConstructorReferences(search.symbol, sourceFile, func(n *ast.Node) {
|
|
pusher()(n, entryKindNode)
|
|
})
|
|
} else {
|
|
// If this class appears in `extends C`, then the extending class' "super" calls are references.
|
|
if classExtending := tryGetClassByExtendingIdentifier(referenceLocation); classExtending != nil {
|
|
findSuperConstructorAccesses(classExtending, func(n *ast.Node) {
|
|
pusher()(n, entryKindNode)
|
|
})
|
|
state.findInheritedConstructorReferences(classExtending)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (state *refState) addClassStaticThisReferences(referenceLocation *ast.Node, symbol *ast.Symbol, search *refSearch, addReferencesHere bool) {
|
|
if addReferencesHere {
|
|
state.addReference(referenceLocation, symbol, entryKindNode)
|
|
}
|
|
|
|
classLike := referenceLocation.Parent
|
|
if state.options.use == referenceUseRename || !ast.IsClassLike(classLike) {
|
|
return
|
|
}
|
|
|
|
addRef := state.referenceAdder(search.symbol)
|
|
members := classLike.Members()
|
|
if members == nil {
|
|
return
|
|
}
|
|
for _, member := range members {
|
|
if !(isMethodOrAccessor(member) && ast.HasStaticModifier(member)) {
|
|
continue
|
|
}
|
|
body := member.Body()
|
|
if body != nil {
|
|
var cb func(*ast.Node)
|
|
cb = func(node *ast.Node) {
|
|
if node.Kind == ast.KindThisKeyword {
|
|
addRef(node, entryKindNode)
|
|
} else if !ast.IsFunctionLike(node) && !ast.IsClassLike(node) {
|
|
node.ForEachChild(func(child *ast.Node) bool {
|
|
cb(child)
|
|
return false
|
|
})
|
|
}
|
|
}
|
|
cb(body)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (state *refState) findInheritedConstructorReferences(classDeclaration *ast.ClassLikeDeclaration) {
|
|
if hasOwnConstructor(classDeclaration) {
|
|
return
|
|
}
|
|
classSymbol := classDeclaration.Symbol()
|
|
search := state.createSearch(nil, classSymbol, ImpExpKindUnknown, "", nil)
|
|
state.getReferencesInContainerOrFiles(classSymbol, search)
|
|
}
|
|
|
|
func (state *refState) getImportOrExportReferences(referenceLocation *ast.Node, referenceSymbol *ast.Symbol, search *refSearch) {
|
|
importOrExport := getImportOrExportSymbol(referenceLocation, referenceSymbol, state.checker, search.comingFrom == ImpExpKindExport)
|
|
if importOrExport == nil {
|
|
return
|
|
}
|
|
if importOrExport.kind == ImpExpKindImport {
|
|
if !isForRenameWithPrefixAndSuffixText(state.options) {
|
|
state.searchForImportedSymbol(importOrExport.symbol)
|
|
}
|
|
} else {
|
|
state.searchForImportsOfExport(referenceLocation, importOrExport.symbol, importOrExport.exportInfo)
|
|
}
|
|
}
|
|
|
|
func (state *refState) markSeenReExportRHS(node *ast.Node) bool {
|
|
return state.seenReExportRHS.AddIfAbsent(node)
|
|
}
|
|
|
|
func (state *refState) getReferencesAtExportSpecifier(
|
|
referenceLocation *ast.Node,
|
|
referenceSymbol *ast.Symbol,
|
|
exportSpecifier *ast.ExportSpecifier,
|
|
search *refSearch,
|
|
addReferencesHere bool,
|
|
alwaysGetReferences bool,
|
|
) {
|
|
debug.Assert(!alwaysGetReferences || state.options.useAliasesForRename, "If alwaysGetReferences is true, then prefix/suffix text must be enabled")
|
|
|
|
exportDeclaration := exportSpecifier.Parent.Parent.AsExportDeclaration()
|
|
propertyName := exportSpecifier.PropertyName
|
|
name := exportSpecifier.Name()
|
|
localSymbol := getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker)
|
|
|
|
if !alwaysGetReferences && !search.includes(localSymbol) {
|
|
return
|
|
}
|
|
|
|
addRef := func() {
|
|
if addReferencesHere {
|
|
state.addReference(referenceLocation, localSymbol, entryKindNode)
|
|
}
|
|
}
|
|
|
|
if propertyName == nil {
|
|
// Don't rename at `export { default } from "m";`. (but do continue to search for imports of the re-export)
|
|
if !(state.options.use == referenceUseRename && ast.ModuleExportNameIsDefault(name)) {
|
|
addRef()
|
|
}
|
|
} else if referenceLocation == propertyName.AsNode() {
|
|
// For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export.
|
|
// For `export { foo as bar };`, where `foo` is a local, so add it now.
|
|
if exportDeclaration.ModuleSpecifier == nil {
|
|
addRef()
|
|
}
|
|
|
|
if addReferencesHere && state.options.use != referenceUseRename && state.markSeenReExportRHS(name) {
|
|
exportSymbol := exportSpecifier.AsNode().Symbol()
|
|
debug.Assert(exportSymbol != nil, "exportSpecifier.Symbol() should not be nil")
|
|
state.addReference(name, exportSymbol, entryKindNode)
|
|
}
|
|
} else {
|
|
if state.markSeenReExportRHS(referenceLocation) {
|
|
addRef()
|
|
}
|
|
}
|
|
|
|
// For `export { foo as bar }`, rename `foo`, but not `bar`.
|
|
if !isForRenameWithPrefixAndSuffixText(state.options) || alwaysGetReferences {
|
|
isDefaultExport := ast.ModuleExportNameIsDefault(referenceLocation) || ast.ModuleExportNameIsDefault(exportSpecifier.Name())
|
|
exportKind := ExportKindNamed
|
|
if isDefaultExport {
|
|
exportKind = ExportKindDefault
|
|
}
|
|
exportSymbol := exportSpecifier.AsNode().Symbol()
|
|
debug.Assert(exportSymbol != nil, "exportSpecifier.Symbol() should not be nil")
|
|
exportInfo := getExportInfo(exportSymbol, exportKind, state.checker)
|
|
if exportInfo != nil {
|
|
state.searchForImportsOfExport(referenceLocation, exportSymbol, exportInfo)
|
|
}
|
|
}
|
|
|
|
// At `export { x } from "foo"`, also search for the imported symbol `"foo".x`.
|
|
if search.comingFrom != ImpExpKindExport && exportDeclaration.ModuleSpecifier != nil && propertyName == nil && !isForRenameWithPrefixAndSuffixText(state.options) {
|
|
imported := state.checker.GetExportSpecifierLocalTargetSymbol(exportSpecifier.AsNode())
|
|
if imported != nil {
|
|
state.searchForImportedSymbol(imported)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Go to the symbol we imported from and find references for it.
|
|
func (state *refState) searchForImportedSymbol(symbol *ast.Symbol) {
|
|
for _, declaration := range symbol.Declarations {
|
|
exportingFile := ast.GetSourceFileOfNode(declaration)
|
|
// Need to search in the file even if it's not in the search-file set, because it might export the symbol.
|
|
state.getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImpExpKindImport, "", nil), state.includesSourceFile(exportingFile))
|
|
}
|
|
}
|
|
|
|
// Search for all imports of a given exported symbol using `State.getImportSearches`. */
|
|
func (state *refState) searchForImportsOfExport(exportLocation *ast.Node, exportSymbol *ast.Symbol, exportInfo *ExportInfo) {
|
|
r := state.getImportSearches(exportSymbol, exportInfo)
|
|
|
|
// For `import { foo as bar }` just add the reference to `foo`, and don't otherwise search in the file.
|
|
if len(r.singleReferences) != 0 {
|
|
addRef := state.referenceAdder(exportSymbol)
|
|
for _, singleRef := range r.singleReferences {
|
|
if state.shouldAddSingleReference(singleRef) {
|
|
addRef(singleRef, entryKindNode)
|
|
}
|
|
}
|
|
}
|
|
|
|
// For each import, find all references to that import in its source file.
|
|
for _, i := range r.importSearches {
|
|
state.getReferencesInSourceFile(ast.GetSourceFileOfNode(i.importLocation), state.createSearch(i.importLocation, i.importSymbol, ImpExpKindExport, "", nil), true /*addReferencesHere*/)
|
|
}
|
|
|
|
if len(r.indirectUsers) != 0 {
|
|
var indirectSearch *refSearch
|
|
switch exportInfo.exportKind {
|
|
case ExportKindNamed:
|
|
indirectSearch = state.createSearch(exportLocation, exportSymbol, ImpExpKindExport, "", nil)
|
|
case ExportKindDefault:
|
|
// Search for a property access to '.default'. This can't be renamed.
|
|
if state.options.use != referenceUseRename {
|
|
indirectSearch = state.createSearch(exportLocation, exportSymbol, ImpExpKindExport, "default", nil)
|
|
}
|
|
}
|
|
if indirectSearch != nil {
|
|
for _, indirectUser := range r.indirectUsers {
|
|
state.searchForName(indirectUser, indirectSearch)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (state *refState) shouldAddSingleReference(singleRef *ast.Node) bool {
|
|
if !state.hasMatchingMeaning(singleRef) {
|
|
return false
|
|
}
|
|
if state.options.use != referenceUseRename {
|
|
return true
|
|
}
|
|
// Don't rename an import type `import("./module-name")` when renaming `name` in `export = name;`
|
|
if !ast.IsIdentifier(singleRef) && !ast.IsImportOrExportSpecifier(singleRef.Parent) {
|
|
return false
|
|
}
|
|
// At `default` in `import { default as x }` or `export { default as x }`, do add a reference, but do not rename.
|
|
return !(ast.IsImportOrExportSpecifier(singleRef.Parent) && ast.ModuleExportNameIsDefault(singleRef))
|
|
}
|
|
|
|
func (state *refState) hasMatchingMeaning(referenceLocation *ast.Node) bool {
|
|
return getMeaningFromLocation(referenceLocation)&state.searchMeaning != 0
|
|
}
|
|
|
|
func (state *refState) getReferenceForShorthandProperty(referenceSymbol *ast.Symbol, search *refSearch) {
|
|
if referenceSymbol.Flags&ast.SymbolFlagsTransient != 0 || referenceSymbol.ValueDeclaration == nil {
|
|
return
|
|
}
|
|
shorthandValueSymbol := state.checker.GetShorthandAssignmentValueSymbol(referenceSymbol.ValueDeclaration)
|
|
name := ast.GetNameOfDeclaration(referenceSymbol.ValueDeclaration)
|
|
|
|
// Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment
|
|
// has two meanings: property name and property value. Therefore when we do findAllReference at the position where
|
|
// an identifier is declared, the language service should return the position of the variable declaration as well as
|
|
// the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the
|
|
// position of property accessing, the referenceEntry of such position will be handled in the first case.
|
|
if name != nil && search.includes(shorthandValueSymbol) {
|
|
state.addReference(name, shorthandValueSymbol, entryKindNode)
|
|
}
|
|
}
|
|
|
|
// === search ===
|
|
func (state *refState) populateSearchSymbolSet(symbol *ast.Symbol, location *ast.Node, isForRename, providePrefixAndSuffixText, implementations bool) []*ast.Symbol {
|
|
if location == nil {
|
|
return []*ast.Symbol{symbol}
|
|
}
|
|
result := []*ast.Symbol{}
|
|
state.forEachRelatedSymbol(
|
|
symbol,
|
|
location,
|
|
isForRename,
|
|
!(isForRename && providePrefixAndSuffixText),
|
|
func(sym *ast.Symbol, root *ast.Symbol, base *ast.Symbol) *ast.Symbol {
|
|
// static method/property and instance method/property might have the same name. Only include static or only include instance.
|
|
if base != nil {
|
|
if isStaticSymbol(symbol) != isStaticSymbol(base) {
|
|
base = nil
|
|
}
|
|
}
|
|
result = append(result, core.OrElse(base, core.OrElse(root, sym)))
|
|
return nil
|
|
}, // when try to find implementation, implementations is true, and not allowed to find base class
|
|
/*allowBaseTypes*/ func(_ *ast.Symbol) bool { return !implementations },
|
|
)
|
|
return result
|
|
}
|
|
|
|
func (state *refState) getRelatedSymbol(search *refSearch, referenceSymbol *ast.Symbol, referenceLocation *ast.Node) (*ast.Symbol, entryKind) {
|
|
return state.forEachRelatedSymbol(
|
|
referenceSymbol,
|
|
referenceLocation,
|
|
false, /*isForRenamePopulateSearchSymbolSet*/
|
|
state.options.use != referenceUseRename || state.options.useAliasesForRename, /*onlyIncludeBindingElementAtReferenceLocation*/
|
|
func(sym *ast.Symbol, rootSymbol *ast.Symbol, baseSymbol *ast.Symbol) *ast.Symbol {
|
|
// check whether the symbol used to search itself is just the searched one.
|
|
if baseSymbol != nil {
|
|
// static method/property and instance method/property might have the same name. Only check static or only check instance.
|
|
if isStaticSymbol(referenceSymbol) != isStaticSymbol(baseSymbol) {
|
|
baseSymbol = nil
|
|
}
|
|
}
|
|
searchSym := core.Coalesce(baseSymbol, core.Coalesce(rootSymbol, sym))
|
|
if searchSym != nil && search.includes(searchSym) {
|
|
if rootSymbol != nil && sym.CheckFlags&ast.CheckFlagsSynthetic == 0 {
|
|
return rootSymbol
|
|
}
|
|
return sym
|
|
}
|
|
// For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol.
|
|
return nil
|
|
},
|
|
func(rootSymbol *ast.Symbol) bool {
|
|
return !(len(search.parents) != 0 && !core.Some(search.parents, func(parent *ast.Symbol) bool {
|
|
return state.explicitlyInheritsFrom(rootSymbol.Parent, parent)
|
|
}))
|
|
},
|
|
)
|
|
}
|
|
|
|
func (state *refState) forEachRelatedSymbol(
|
|
symbol *ast.Symbol,
|
|
location *ast.Node,
|
|
isForRenamePopulateSearchSymbolSet,
|
|
onlyIncludeBindingElementAtReferenceLocation bool,
|
|
cbSymbol func(*ast.Symbol, *ast.Symbol, *ast.Symbol) *ast.Symbol,
|
|
allowBaseTypes func(*ast.Symbol) bool,
|
|
) (*ast.Symbol, entryKind) {
|
|
fromRoot := func(sym *ast.Symbol) *ast.Symbol {
|
|
// If this is a union property:
|
|
// - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types.
|
|
// - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search.
|
|
// If the symbol is an instantiation from a another symbol (e.g. widened symbol):
|
|
// - In populateSearchSymbolsSet, add the root the list
|
|
// - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.)
|
|
for _, rootSymbol := range state.checker.GetRootSymbols(sym) {
|
|
if result := cbSymbol(sym, rootSymbol, nil /*baseSymbol*/); result != nil {
|
|
return result
|
|
}
|
|
// Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions
|
|
if rootSymbol.Parent != nil && rootSymbol.Parent.Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0 && allowBaseTypes(rootSymbol) {
|
|
result := getPropertySymbolsFromBaseTypes(rootSymbol.Parent, rootSymbol.Name, state.checker, func(base *ast.Symbol) *ast.Symbol {
|
|
return cbSymbol(sym, rootSymbol, base)
|
|
})
|
|
if result != nil {
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if containingObjectLiteralElement := getContainingObjectLiteralElement(location); containingObjectLiteralElement != nil {
|
|
/* Because in short-hand property assignment, location has two meaning : property name and as value of the property
|
|
* When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of
|
|
* property name and variable declaration of the identifier.
|
|
* Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service
|
|
* should show both 'name' in 'obj' and 'name' in variable declaration
|
|
* const name = "Foo";
|
|
* const obj = { name };
|
|
* In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment
|
|
* so that when matching with potential reference symbol, both symbols from property declaration and variable declaration
|
|
* will be included correctly.
|
|
*/
|
|
shorthandValueSymbol := state.checker.GetShorthandAssignmentValueSymbol(location.Parent)
|
|
// gets the local symbol
|
|
if shorthandValueSymbol != nil && isForRenamePopulateSearchSymbolSet {
|
|
// When renaming 'x' in `const o = { x }`, just rename the local variable, not the property.
|
|
return cbSymbol(shorthandValueSymbol, nil /*rootSymbol*/, nil /*baseSymbol*/), entryKindSearchedLocalFoundProperty
|
|
}
|
|
// If the location is in a context sensitive location (i.e. in an object literal) try
|
|
// to get a contextual type for it, and add the property symbol from the contextual
|
|
// type to the search set
|
|
if contextualType := state.checker.GetContextualType(containingObjectLiteralElement.Parent, checker.ContextFlagsNone); contextualType != nil {
|
|
symbols := state.checker.GetPropertySymbolsFromContextualType(containingObjectLiteralElement, contextualType, true /*unionSymbolOk*/)
|
|
for _, sym := range symbols {
|
|
if res := fromRoot(sym); res != nil {
|
|
return res, entryKindSearchedPropertyFoundLocal
|
|
}
|
|
}
|
|
}
|
|
// If the location is name of property symbol from object literal destructuring pattern
|
|
// Search the property symbol
|
|
// for ( { property: p2 } of elems) { }
|
|
if propertySymbol := state.checker.GetPropertySymbolOfDestructuringAssignment(location); propertySymbol != nil {
|
|
if res := cbSymbol(propertySymbol, nil /*rootSymbol*/, nil /*baseSymbol*/); res != nil {
|
|
return res, entryKindSearchedPropertyFoundLocal
|
|
}
|
|
}
|
|
if shorthandValueSymbol != nil {
|
|
if res := cbSymbol(shorthandValueSymbol, nil /*rootSymbol*/, nil /*baseSymbol*/); res != nil {
|
|
return res, entryKindSearchedLocalFoundProperty
|
|
}
|
|
}
|
|
}
|
|
|
|
if aliasedSymbol := getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, state.checker); aliasedSymbol != nil {
|
|
// In case of UMD module and global merging, search for global as well
|
|
if res := cbSymbol(aliasedSymbol, nil /*rootSymbol*/, nil /*baseSymbol*/); res != nil {
|
|
return res, entryKindNode
|
|
}
|
|
}
|
|
|
|
if res := fromRoot(symbol); res != nil {
|
|
return res, entryKindNode
|
|
}
|
|
|
|
if symbol.ValueDeclaration != nil && ast.IsParameterPropertyDeclaration(symbol.ValueDeclaration, symbol.ValueDeclaration.Parent) {
|
|
paramProp1, paramProp2 := state.checker.GetSymbolsOfParameterPropertyDeclaration(symbol.ValueDeclaration, symbol.Name)
|
|
debug.Assert(
|
|
paramProp1.Flags&ast.SymbolFlagsFunctionScopedVariable != 0 && paramProp2.Flags&ast.SymbolFlagsClassMember != 0,
|
|
"GetSymbolsOfParameterPropertyDeclaration must return (parameter, member) pair",
|
|
)
|
|
return fromRoot(core.IfElse(symbol.Flags&ast.SymbolFlagsFunctionScopedVariable != 0, paramProp2, paramProp1)), entryKindNode
|
|
}
|
|
|
|
if exportSpecifier := ast.GetDeclarationOfKind(symbol, ast.KindExportSpecifier); exportSpecifier != nil && (!isForRenamePopulateSearchSymbolSet || exportSpecifier.PropertyName() == nil) {
|
|
if localSymbol := state.checker.GetExportSpecifierLocalTargetSymbol(exportSpecifier); localSymbol != nil {
|
|
if res := cbSymbol(localSymbol, nil /*rootSymbol*/, nil /*baseSymbol*/); res != nil {
|
|
return res, entryKindNode
|
|
}
|
|
}
|
|
}
|
|
|
|
// symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property.
|
|
// Don't do this when populating search set for a rename when prefix and suffix text will be provided -- just rename the local.
|
|
if !isForRenamePopulateSearchSymbolSet {
|
|
var bindingElementPropertySymbol *ast.Symbol
|
|
if onlyIncludeBindingElementAtReferenceLocation {
|
|
if !isObjectBindingElementWithoutPropertyName(location.Parent) {
|
|
return nil, entryKindNone
|
|
}
|
|
bindingElementPropertySymbol = getPropertySymbolFromBindingElement(state.checker, location.Parent)
|
|
} else {
|
|
bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, state.checker)
|
|
}
|
|
if bindingElementPropertySymbol == nil {
|
|
return nil, entryKindNone
|
|
}
|
|
return fromRoot(bindingElementPropertySymbol), entryKindSearchedPropertyFoundLocal
|
|
}
|
|
|
|
debug.Assert(isForRenamePopulateSearchSymbolSet)
|
|
|
|
// due to the above assert and the arguments at the uses of this function,
|
|
// (onlyIncludeBindingElementAtReferenceLocation <=> !providePrefixAndSuffixTextForRename) holds
|
|
includeOriginalSymbolOfBindingElement := onlyIncludeBindingElementAtReferenceLocation
|
|
|
|
if includeOriginalSymbolOfBindingElement {
|
|
if bindingElementPropertySymbol := getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, state.checker); bindingElementPropertySymbol != nil {
|
|
return fromRoot(bindingElementPropertySymbol), entryKindSearchedPropertyFoundLocal
|
|
}
|
|
}
|
|
return nil, entryKindNone
|
|
}
|
|
|
|
// Search for all occurrences of an identifier in a source file (and filter out the ones that match).
|
|
func (state *refState) searchForName(sourceFile *ast.SourceFile, search *refSearch) {
|
|
if _, ok := sourceFile.GetNameTable()[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
|
|
}
|