vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

View File

@@ -0,0 +1,707 @@
package ls
import (
"context"
"math"
"slices"
"strings"
"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/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/module"
"github.com/microsoft/typescript-go/internal/modulespecifiers"
"github.com/microsoft/typescript-go/internal/parser"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs"
)
func (l *LanguageService) ProvideSourceDefinition(
ctx context.Context,
documentURI lsproto.DocumentUri,
position lsproto.Position,
) (lsproto.DefinitionResponse, error) {
caps := lsproto.GetClientCapabilities(ctx)
clientSupportsLink := caps.TextDocument.Definition.LinkSupport
program, file := l.getProgramAndFile(documentURI)
pos := int(l.converters.LineAndCharacterToPosition(file, position))
resolver := l.newSourceDefResolver(program, file.FileName())
node := astnav.GetTouchingPropertyName(file, pos)
if node.Kind == ast.KindSourceFile {
// Triple-slash directives are comments, not AST nodes, so
// GetTouchingPropertyName returns the SourceFile node.
if declarations, ref := resolver.resolveTripleSlashReference(file, pos, program); len(declarations) != 0 {
originSelectionRange := l.createLspRangeFromBounds(ref.Pos(), ref.End(), file)
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, declarations, nil /*reference*/), nil
}
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{}, nil
}
originSelectionRange := l.createLspRangeFromNode(node, file)
// If the cursor is directly on a module specifier string, resolve to the
// implementation file's entry point.
containingModuleSpecifier := findContainingModuleSpecifier(node)
if node == containingModuleSpecifier {
specifierMode := program.GetModeForUsageLocation(file, containingModuleSpecifier)
if implementationFile := resolver.resolveImplementation(containingModuleSpecifier.Text(), specifierMode); implementationFile != "" {
if sourceFile := resolver.getOrParseSourceFile(implementationFile); sourceFile != nil {
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, getSourceDefinitionEntryDeclarations(sourceFile), nil), nil
}
}
return l.provideDefinitionWorker(ctx, documentURI, position)
}
// Phase 1: Syntactic fast path — when the cursor is inside an
// import/require/export, forward-resolve the module specifier to an
// implementation file and search it directly. This avoids acquiring
// the type checker entirely when the fast path succeeds.
var resolvedImplFile string
if containingModuleSpecifier != nil {
specifierMode := program.GetModeForUsageLocation(file, containingModuleSpecifier)
resolvedImplFile = resolver.resolveImplementation(containingModuleSpecifier.Text(), specifierMode)
}
if resolvedImplFile != "" {
names := getCandidateSourceDeclarationNames(node, nil)
moduleResults := resolver.searchImplementationFile(node, resolvedImplFile, names)
if len(moduleResults) != 0 {
if !ast.IsPartOfTypeNode(node) && !ast.IsPartOfTypeOnlyImportOrExportDeclaration(node) || hasConcreteSourceDeclarations(moduleResults) {
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, uniqueDeclarationNodes(moduleResults), nil), nil
}
}
}
// Phase 2: Type checker path — acquire the checker for the original file
// and use its declarations and module specifier to map to source
// implementations. This is the only point where the checker is used;
// after this, only the NoDts module resolver and file parsing are needed.
checkerDeclarations, moduleSpecifier := getSourceDefCheckerInfo(ctx, program, file, node)
// Phase 3: Map checker results to source definitions.
declarations := resolver.resolveFromCheckerInfo(node, resolvedImplFile, checkerDeclarations, moduleSpecifier)
if len(declarations) == 0 {
// If we resolved an implementation file from an import/export but
// couldn't find specific declarations, fall back to the file entry
// point rather than the standard definition provider — unless the
// checker found declarations that are all type-only (e.g. interfaces),
// in which case the .d.ts definition is more appropriate.
if containingModuleSpecifier != nil && resolvedImplFile != "" && !hasConcreteSourceDeclarations(checkerDeclarations) {
if sourceFile := resolver.getOrParseSourceFile(resolvedImplFile); sourceFile != nil {
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, getSourceDefinitionEntryDeclarations(sourceFile), nil), nil
}
}
return l.provideDefinitionWorker(ctx, documentURI, position)
}
return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, declarations, nil /*reference*/), nil
}
// sourceDefResolver resolves source definitions by mapping .d.ts declarations
// to their implementation files (.js/.ts). It uses the NoDts module resolver
// and file parsing for resolution, but never acquires the type checker or
// the original program; all checker-dependent work is done before results
// are passed in.
type sourceDefResolver struct {
ls *LanguageService
fs vfs.FS
options *core.CompilerOptions
getSourceFile func(string) *ast.SourceFile
resolveFrom string
resolver *module.Resolver
parsedFiles map[string]*ast.SourceFile
}
func (l *LanguageService) newSourceDefResolver(
program *compiler.Program,
resolveFrom string,
) *sourceDefResolver {
options := program.Options()
noDtsOptions := options.Clone()
noDtsOptions.NoDtsResolution = core.TSTrue
return &sourceDefResolver{
ls: l,
fs: program.Host().FS(),
options: options,
getSourceFile: program.GetSourceFile,
resolveFrom: resolveFrom,
resolver: module.NewResolver(program.Host(), noDtsOptions, program.GetGlobalTypingsCacheLocation(), ""),
}
}
// resolveFromCheckerInfo maps type-checker declarations to source
// implementations. It uses only the NoDts module resolver and file parsing;
// the type checker and original request file are not needed.
func (r *sourceDefResolver) resolveFromCheckerInfo(
node *ast.Node,
resolvedImplFile string,
checkerDeclarations []*ast.Node,
moduleSpecifier string,
) []*ast.Node {
// If we don't yet have a forward-resolved implementation file, try to
// recover a module specifier from the checker (e.g. from the import that
// brought the symbol into scope, or from the root of an access expression).
if resolvedImplFile == "" && moduleSpecifier != "" {
resolvedImplFile = r.resolveImplementation(moduleSpecifier, r.inferImpliedNodeFormat(r.resolveFrom))
}
// For property access where the checker found no declarations (e.g.
// mapped types), search the implementation file for the property name.
if len(checkerDeclarations) == 0 && resolvedImplFile != "" {
names := getCandidateSourceDeclarationNames(node, nil)
if results := r.searchImplementationFile(node, resolvedImplFile, names); results != nil {
return uniqueDeclarationNodes(results)
}
}
var declarations []*ast.Node
for _, declaration := range checkerDeclarations {
declarations = append(declarations, r.mapDeclarationToSource(node, declaration, resolvedImplFile)...)
}
declarations = uniqueDeclarationNodes(declarations)
if hasConcreteSourceDeclarations(declarations) {
return declarations
}
return nil
}
// getSourceDefCheckerInfo acquires the type checker for the given file and
// returns the definition declarations for node along with the module specifier
// of the import that brought the symbol into scope (empty if not applicable).
func getSourceDefCheckerInfo(
ctx context.Context,
program *compiler.Program,
file *ast.SourceFile,
node *ast.Node,
) ([]*ast.Node, string) {
c, done := program.GetTypeCheckerForFile(ctx, file)
defer done()
declarations := getDeclarationsFromLocation(c, node)
isPropertyName := node.Parent != nil && ast.IsAccessExpression(node.Parent) && node.Parent.Name() == node
if len(declarations) == 0 && isPropertyName {
if left := node.Parent.Expression(); left != nil {
if prop := c.GetPropertyOfType(c.GetTypeAtLocation(left), node.Text()); prop != nil {
declarations = prop.Declarations
}
}
}
if calledDeclaration := tryGetSignatureDeclaration(c, node); calledDeclaration != nil {
nonFunctionDeclarations := core.Filter(declarations, func(node *ast.Node) bool { return !ast.IsFunctionLike(node) })
declarations = append(nonFunctionDeclarations, calledDeclaration)
}
// Extract module specifier from the import that brought this symbol into
// scope. For property access (obj.prop), walk up the access chain to the
// root expression's symbol.
var moduleSpecifier string
resolveNode := node
if isPropertyName {
expr := node.Parent.Expression()
for expr != nil && ast.IsAccessExpression(expr) {
expr = expr.Expression()
}
if expr != nil {
resolveNode = expr
}
}
if sym := c.GetSymbolAtLocation(resolveNode); sym != nil {
for _, d := range sym.Declarations {
if !ast.IsImportSpecifier(d) && !ast.IsImportClause(d) && !ast.IsNamespaceImport(d) && !ast.IsImportEqualsDeclaration(d) {
continue
}
if spec := checker.TryGetModuleSpecifierFromDeclaration(d); spec != nil {
moduleSpecifier = spec.Text()
break
}
}
}
return declarations, moduleSpecifier
}
// resolveTripleSlashReference handles /// <reference path/types="..."/> directives.
// For path references to .js files, it returns the entry declarations directly.
// For path references to .d.ts files or type references, it uses the NoDts
// resolver to find the corresponding implementation file.
func (r *sourceDefResolver) resolveTripleSlashReference(file *ast.SourceFile, pos int, program *compiler.Program) ([]*ast.Node, *ast.FileReference) {
ref := getReferenceAtPosition(file, pos, program)
if ref == nil || ref.file == nil {
return nil, nil
}
// If the referenced file is already an implementation file, return it directly.
if !ref.file.IsDeclarationFile {
return getSourceDefinitionEntryDeclarations(ref.file), ref.reference
}
// The referenced file is a .d.ts. Try to find the implementation file
// using the NoDts module resolver via findImplementationFileFromDtsFileName.
dtsFileName := ref.file.FileName()
preferredMode := r.inferImpliedNodeFormat(dtsFileName)
implementationFile := r.findImplementationFileFromDtsFileName(dtsFileName, preferredMode)
if implementationFile == "" {
return nil, nil
}
sourceFile := r.getOrParseSourceFile(implementationFile)
if sourceFile == nil {
return nil, nil
}
return getSourceDefinitionEntryDeclarations(sourceFile), ref.reference
}
// searchImplementationFile searches an implementation file for declarations
// matching the given names. Returns nil when no declarations matched; callers
// fall through to the checker path or to the standard definition provider.
func (r *sourceDefResolver) searchImplementationFile(
originalNode *ast.Node,
implementationFile string,
names []string,
) []*ast.Node {
if implementationFile == "" {
return nil
}
sourceFile := r.getOrParseSourceFile(implementationFile)
if sourceFile == nil {
return nil
}
if isDefaultImportName(originalNode) {
// For default imports, only search for "default" declarations to avoid
// matching unrelated declarations with the same identifier name.
defaultDeclarations := r.findDeclarationsInFile(implementationFile, []string{"default"}, &collections.Set[string]{})
if len(defaultDeclarations) != 0 {
return filterPreferredSourceDeclarations(originalNode, defaultDeclarations)
}
return getSourceDefinitionEntryDeclarations(sourceFile)
}
declarations := r.findDeclarationsInFile(implementationFile, names, &collections.Set[string]{})
if len(declarations) != 0 {
return filterPreferredSourceDeclarations(originalNode, declarations)
}
return nil
}
func isDefaultImportName(node *ast.Node) bool {
if node == nil || node.Parent == nil || !ast.IsImportClause(node.Parent) || node.Parent.Name() != node || node.Parent.Parent == nil {
return false
}
return ast.IsDefaultImport(node.Parent.Parent)
}
func getSourceDefinitionEntryNode(sourceFile *ast.SourceFile) *ast.Node {
if len(sourceFile.Statements.Nodes) != 0 {
return sourceFile.Statements.Nodes[0].AsNode()
}
return sourceFile.AsNode()
}
func getSourceDefinitionEntryDeclarations(sourceFile *ast.SourceFile) []*ast.Node {
return []*ast.Node{getSourceDefinitionEntryNode(sourceFile)}
}
func (r *sourceDefResolver) mapDeclarationToSource(
originalNode *ast.Node,
declaration *ast.Node,
resolvedImplFile string,
) []*ast.Node {
file, startPos := getFileAndStartPosFromDeclaration(declaration)
fileName := file.FileName()
if mapped := r.ls.tryGetSourcePosition(fileName, startPos); mapped != nil {
if sourceFile := r.getOrParseSourceFile(mapped.FileName); sourceFile != nil {
return []*ast.Node{findClosestDeclarationNode(sourceFile, mapped.Pos)}
}
}
if !tspath.IsDeclarationFileName(fileName) {
return []*ast.Node{declaration}
}
implementationFile := resolvedImplFile
if implementationFile == "" {
// Reverse-resolve .d.ts path to implementation file. This path is only
// reached for declarations with no associated module specifier (e.g.
// globals, ambient declarations, or when forward resolution failed).
dtsFileName := ast.GetSourceFileOfNode(declaration).FileName()
preferredMode := r.inferImpliedNodeFormat(dtsFileName)
implementationFile = r.findImplementationFileFromDtsFileName(dtsFileName, preferredMode)
}
return r.searchImplementationFile(originalNode, implementationFile, getCandidateSourceDeclarationNames(originalNode, declaration))
}
func (r *sourceDefResolver) findImplementationFileFromDtsFileName(
dtsFileName string,
preferredMode core.ResolutionMode,
) string {
if jsExt := module.TryGetJSExtensionForFile(dtsFileName, r.options); jsExt != "" {
candidate := tspath.ChangeExtension(dtsFileName, jsExt)
if r.fs.FileExists(candidate) {
return candidate
}
}
parts := modulespecifiers.GetNodeModulePathParts(dtsFileName)
if parts == nil {
return ""
}
// Ensure the file only contains one /node_modules/ segment. If there's more
// than one, the package name extraction may be incorrect, so bail out.
if strings.LastIndex(dtsFileName, "/node_modules/") != parts.TopLevelNodeModulesIndex {
return ""
}
packageNamePathPart := dtsFileName[parts.TopLevelPackageNameIndex+1 : parts.PackageRootIndex]
packageName := module.GetPackageNameFromTypesPackageName(module.UnmangleScopedPackageName(packageNamePathPart))
if packageName == "" {
return ""
}
pathToFileInPackage := dtsFileName[parts.PackageRootIndex+1:]
// Try resolving as a package subpath first (e.g. "pkg/dist/utils"), then
// fall back to the bare package name (e.g. "pkg"). This covers both main
// entrypoints and deep imports without needing to inspect package.json
// entrypoints.
if pathToFileInPackage != "" {
specifier := packageName + "/" + tspath.RemoveFileExtension(pathToFileInPackage)
if implementationFile := r.resolveImplementation(specifier, preferredMode); implementationFile != "" {
return implementationFile
}
}
return r.resolveImplementation(packageName, preferredMode)
}
func (r *sourceDefResolver) resolveImplementation(
moduleName string,
preferredMode core.ResolutionMode,
) string {
return r.resolveImplementationFrom(moduleName, r.resolveFrom, preferredMode)
}
func (r *sourceDefResolver) resolveImplementationFrom(
moduleName string,
resolveFromFile string,
preferredMode core.ResolutionMode,
) string {
modes := []core.ResolutionMode{preferredMode}
if preferredMode != core.ModuleKindESNext {
modes = append(modes, core.ModuleKindESNext)
}
if preferredMode != core.ModuleKindCommonJS {
modes = append(modes, core.ModuleKindCommonJS)
}
for _, mode := range modes {
resolved, _ := r.resolver.ResolveModuleName(moduleName, resolveFromFile, mode, nil)
if resolved != nil && resolved.IsResolved() && !tspath.IsDeclarationFileName(resolved.ResolvedFileName) {
return resolved.ResolvedFileName
}
}
return ""
}
func (r *sourceDefResolver) getOrParseSourceFile(fileName string) *ast.SourceFile {
if sourceFile := r.getSourceFile(fileName); sourceFile != nil {
return sourceFile
}
if sourceFile, ok := r.parsedFiles[fileName]; ok {
return sourceFile
}
var sourceFile *ast.SourceFile
if text, ok := r.ls.ReadFile(fileName); ok {
sourceFile = parser.ParseSourceFile(
ast.SourceFileParseOptions{FileName: fileName, Path: r.ls.toPath(fileName)},
text,
core.GetScriptKindFromFileName(fileName),
)
binder.BindSourceFile(sourceFile)
}
if r.parsedFiles == nil {
r.parsedFiles = map[string]*ast.SourceFile{}
}
r.parsedFiles[fileName] = sourceFile
return sourceFile
}
// inferImpliedNodeFormat determines the module format for a source file that may not be
// in the program, using the file extension and nearest package.json "type" field.
func (r *sourceDefResolver) inferImpliedNodeFormat(fileName string) core.ResolutionMode {
var packageJsonType string
if scope := r.resolver.GetPackageScopeForPath(tspath.GetDirectoryPath(fileName)); scope.Exists() {
if value, ok := scope.Contents.Type.GetValue(); ok {
packageJsonType = value
}
}
return ast.GetImpliedNodeFormatForFile(fileName, packageJsonType)
}
func findContainingModuleSpecifier(node *ast.Node) *ast.Node {
for current := node; current != nil; current = current.Parent {
if ast.IsAnyImportOrReExport(current) || ast.IsRequireCall(current, true /*requireStringLiteralLikeArgument*/) || ast.IsImportCall(current) {
if moduleSpecifier := ast.GetExternalModuleName(current); moduleSpecifier != nil && ast.IsStringLiteralLike(moduleSpecifier) {
return moduleSpecifier
}
}
}
return nil
}
func (r *sourceDefResolver) findDeclarationsInFile(
fileName string,
names []string,
seen *collections.Set[string],
) []*ast.Node {
if fileName == "" || len(names) == 0 {
return nil
}
if !seen.AddIfAbsent(fileName) {
return nil
}
sourceFile := r.getOrParseSourceFile(fileName)
if sourceFile == nil {
return nil
}
declarations := findDeclarationNodesByName(sourceFile, names)
if len(declarations) != 0 && hasConcreteSourceDeclarations(declarations) {
return declarations
}
var forwarded []*ast.Node
for _, forwardedFile := range r.getForwardedImplementationFiles(sourceFile) {
forwarded = append(forwarded, r.findDeclarationsInFile(forwardedFile, names, seen)...)
}
if len(forwarded) != 0 {
if hasConcreteSourceDeclarations(forwarded) {
return uniqueDeclarationNodes(forwarded)
}
return uniqueDeclarationNodes(append(slices.Clip(declarations), forwarded...))
}
return declarations
}
func (r *sourceDefResolver) getForwardedImplementationFiles(sourceFile *ast.SourceFile) []string {
preferredMode := r.inferImpliedNodeFormat(sourceFile.FileName())
var files []string
for _, imp := range sourceFile.Imports() {
moduleName := imp.Text()
if implementationFile := r.resolveImplementationFrom(moduleName, sourceFile.FileName(), preferredMode); implementationFile != "" {
files = append(files, implementationFile)
}
}
return core.Deduplicate(files)
}
func getCandidateSourceDeclarationNames(originalNode *ast.Node, declaration *ast.Node) []string {
var names []string
if declaration != nil {
if name := ast.GetNameOfDeclaration(declaration); name != nil {
if text := ast.GetTextOfPropertyName(name); text != "" {
names = append(names, text)
}
}
if declaration.Kind == ast.KindExportAssignment {
names = append(names, "default")
}
if (ast.IsFunctionDeclaration(declaration) || ast.IsClassDeclaration(declaration)) && declaration.ModifierFlags()&ast.ModifierFlagsExportDefault == ast.ModifierFlagsExportDefault {
names = append(names, "default")
}
if ast.IsImportSpecifier(declaration) || ast.IsExportSpecifier(declaration) {
if propName := declaration.PropertyName(); propName != nil {
names = append(names, propName.Text())
}
}
}
if originalNode != nil {
if ast.IsIdentifier(originalNode) || ast.IsPrivateIdentifier(originalNode) {
names = append(names, originalNode.Text())
}
if isDefaultImportName(originalNode) {
names = append(names, "default")
}
if originalNode.Parent != nil {
if ast.IsImportSpecifier(originalNode.Parent) || ast.IsExportSpecifier(originalNode.Parent) {
if propName := originalNode.Parent.PropertyName(); propName != nil {
names = append(names, propName.Text())
}
}
}
}
return names
}
func findDeclarationNodesByName(sourceFile *ast.SourceFile, names []string) []*ast.Node {
names = core.Deduplicate(core.Filter(names, func(name string) bool { return name != "" }))
if len(names) == 0 {
return nil
}
var wanted collections.Set[string]
wantDefault := false
for _, name := range names {
if name == "default" {
wantDefault = true
continue
}
wanted.Add(name)
}
type candidate struct {
node *ast.Node
depth int
}
var candidates []candidate
minDepth := math.MaxInt
var visit ast.Visitor
visit = func(node *ast.Node) bool {
matched := false
if name := ast.GetNameOfDeclaration(node); name != nil {
if text := ast.GetTextOfPropertyName(name); text != "" {
if wanted.Has(text) {
matched = true
}
}
}
if wantDefault && node.Kind == ast.KindExportAssignment {
matched = true
}
if wantDefault && (ast.IsFunctionDeclaration(node) || ast.IsClassDeclaration(node)) && node.ModifierFlags()&ast.ModifierFlagsExportDefault == ast.ModifierFlagsExportDefault {
matched = true
}
if matched {
depth := getContainerDepth(node)
candidates = append(candidates, candidate{node: node, depth: depth})
if depth < minDepth {
minDepth = depth
}
}
return node.ForEachChild(visit)
}
sourceFile.AsNode().ForEachChild(visit)
// Only keep declarations at the shallowest depth, like getTopMostDeclarationNamesInFile.
var declarations []*ast.Node
for _, c := range candidates {
if c.depth == minDepth {
declarations = append(declarations, c.node)
}
}
return uniqueDeclarationNodes(declarations)
}
// getContainerDepth counts the number of container nodes above a declaration,
// matching the behavior of getDepth in getTopMostDeclarationNamesInFile.
func getContainerDepth(node *ast.Node) int {
depth := 0
current := node
for current != nil {
current = getContainerNode(current)
depth++
}
return depth
}
func filterPreferredSourceDeclarations(originalNode *ast.Node, declarations []*ast.Node) []*ast.Node {
if len(declarations) <= 1 || originalNode == nil {
return declarations
}
if preferred := getPropertyLikeSourceDeclarations(originalNode, declarations); len(preferred) != 0 {
return preferred
}
if preferred := core.Filter(declarations, isConcreteSourceDeclaration); len(preferred) != 0 {
return preferred
}
return declarations
}
func getPropertyLikeSourceDeclarations(originalNode *ast.Node, declarations []*ast.Node) []*ast.Node {
if originalNode.Parent == nil || !ast.IsAccessExpression(originalNode.Parent) || originalNode.Parent.Name() != originalNode {
return nil
}
return core.Filter(declarations, func(node *ast.Node) bool {
switch node.Kind {
case ast.KindPropertyAssignment,
ast.KindShorthandPropertyAssignment,
ast.KindPropertyDeclaration,
ast.KindPropertySignature,
ast.KindMethodDeclaration,
ast.KindMethodSignature,
ast.KindGetAccessor,
ast.KindSetAccessor,
ast.KindEnumMember:
return true
default:
return false
}
})
}
func hasConcreteSourceDeclarations(declarations []*ast.Node) bool {
return slices.ContainsFunc(declarations, isConcreteSourceDeclaration)
}
func isConcreteSourceDeclaration(node *ast.Node) bool {
if !ast.IsDeclaration(node) || node.Kind == ast.KindExportAssignment {
return false
}
if (ast.IsBinaryExpression(node) || ast.IsCallExpression(node)) && ast.GetAssignmentDeclarationKind(node) != ast.JSDeclarationKindNone {
return false
}
switch node.Kind {
case ast.KindParameter,
ast.KindTypeParameter,
ast.KindBindingElement,
ast.KindImportClause,
ast.KindImportSpecifier,
ast.KindNamespaceImport,
ast.KindExportSpecifier,
ast.KindPropertyAccessExpression,
ast.KindElementAccessExpression:
return false
default:
return true
}
}
func uniqueDeclarationNodes(nodes []*ast.Node) []*ast.Node {
type declarationKey struct {
fileName string
loc core.TextRange
}
var seen collections.Set[declarationKey]
result := make([]*ast.Node, 0, len(nodes))
for _, node := range nodes {
if node == nil {
continue
}
fileName := ast.GetSourceFileOfNode(node).FileName()
key := declarationKey{fileName: fileName, loc: node.Loc}
if !seen.AddIfAbsent(key) {
continue
}
result = append(result, node)
}
return result
}
func findClosestDeclarationNode(sourceFile *ast.SourceFile, pos int) *ast.Node {
node := astnav.GetTouchingPropertyName(sourceFile, pos)
for current := node; current != nil; current = current.Parent {
if ast.IsDeclaration(current) || current.Kind == ast.KindExportAssignment {
return current
}
}
return getSourceDefinitionEntryNode(sourceFile)
}