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

729 lines
28 KiB
Go

package ls
import (
"slices"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
)
type ImpExpKind int32
const (
ImpExpKindUnknown ImpExpKind = iota
ImpExpKindImport
ImpExpKindExport
)
type ImportExportSymbol struct {
kind ImpExpKind
symbol *ast.Symbol
exportInfo *ExportInfo
}
type ExportInfo struct {
exportingModuleSymbol *ast.Symbol
exportKind ExportKind
}
type LocationAndSymbol struct {
importLocation *ast.Node
importSymbol *ast.Symbol
}
type ImportsResult struct {
importSearches []LocationAndSymbol
singleReferences []*ast.Node
indirectUsers []*ast.SourceFile
}
type ImportTracker func(exportSymbol *ast.Symbol, exportInfo *ExportInfo, isForRename bool) *ImportsResult
type ModuleReferenceKind int32
const (
ModuleReferenceKindImport ModuleReferenceKind = iota
ModuleReferenceKindReference
ModuleReferenceKindImplicit
)
// ModuleReference represents a reference to a module, either via import, <reference>, or implicit reference
type ModuleReference struct {
kind ModuleReferenceKind
literal *ast.Node // for import and implicit kinds (StringLiteralLike)
referencingFile *ast.SourceFile
ref *ast.FileReference // for reference kind
}
// Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily.
func createImportTracker(sourceFiles []*ast.SourceFile, sourceFilesSet *collections.Set[string], checker *checker.Checker) ImportTracker {
allDirectImports := getDirectImportsMap(sourceFiles, checker)
return func(exportSymbol *ast.Symbol, exportInfo *ExportInfo, isForRename bool) *ImportsResult {
directImports, indirectUsers := getImportersForExport(sourceFiles, sourceFilesSet, allDirectImports, exportInfo, checker)
importSearches, singleReferences := getSearchesFromDirectImports(directImports, exportSymbol, exportInfo.exportKind, checker, isForRename)
return &ImportsResult{importSearches, singleReferences, indirectUsers}
}
}
// Returns a map from a module symbol to all import statements that directly reference the module
func getDirectImportsMap(sourceFiles []*ast.SourceFile, checker *checker.Checker) map[*ast.Symbol][]*ast.Node {
result := make(map[*ast.Symbol][]*ast.Node)
for _, sourceFile := range sourceFiles {
// !!! cancellation
forEachImport(sourceFile, func(importDecl *ast.Node, moduleSpecifier *ast.Node) {
if moduleSymbol := checker.GetSymbolAtLocation(moduleSpecifier); moduleSymbol != nil {
result[moduleSymbol] = append(result[moduleSymbol], importDecl)
}
})
}
return result
}
// Calls `action` for each import, re-export, or require() in a file
func forEachImport(sourceFile *ast.SourceFile, action func(importStatement *ast.Node, imported *ast.Node)) {
if sourceFile.ExternalModuleIndicator != nil || len(sourceFile.Imports()) != 0 {
for _, i := range sourceFile.Imports() {
action(importFromModuleSpecifier(i), i)
}
} else {
forEachPossibleImportOrExportStatement(sourceFile.AsNode(), func(node *ast.Node) bool {
switch node.Kind {
case ast.KindExportDeclaration, ast.KindImportDeclaration, ast.KindJSImportDeclaration:
if specifier := node.ModuleSpecifier(); specifier != nil && ast.IsStringLiteral(specifier) {
action(node, specifier)
}
case ast.KindImportEqualsDeclaration:
if isExternalModuleImportEquals(node) {
action(node, node.AsImportEqualsDeclaration().ModuleReference.Expression())
}
}
return false
})
}
}
func forEachPossibleImportOrExportStatement(sourceFileLike *ast.Node, action func(statement *ast.Node) bool) bool {
for _, statement := range getStatementsOfSourceFileLike(sourceFileLike) {
if action(statement) || isAmbientModuleDeclaration(statement) && forEachPossibleImportOrExportStatement(statement, action) {
return true
}
}
return false
}
func getSourceFileLikeForImportDeclaration(node *ast.Node) *ast.Node {
if ast.IsCallExpression(node) || ast.IsJSDocImportTag(node) {
return ast.GetSourceFileOfNode(node).AsNode()
}
parent := node.Parent
if ast.IsSourceFile(parent) {
return parent
}
debug.Assert(ast.IsModuleBlock(parent) && isAmbientModuleDeclaration(parent.Parent))
return parent.Parent
}
func isAmbientModuleDeclaration(node *ast.Node) bool {
return ast.IsModuleDeclaration(node) && ast.IsStringLiteral(node.Name())
}
func getStatementsOfSourceFileLike(node *ast.Node) []*ast.Node {
if ast.IsSourceFile(node) {
return node.Statements()
}
if body := node.Body(); body != nil {
return body.Statements()
}
return nil
}
func getImportersForExport(
sourceFiles []*ast.SourceFile,
sourceFilesSet *collections.Set[string],
allDirectImports map[*ast.Symbol][]*ast.Node,
exportInfo *ExportInfo,
checker *checker.Checker,
) ([]*ast.Node, []*ast.SourceFile) {
var directImports []*ast.Node
var indirectUserDeclarations []*ast.Node
markSeenDirectImport := nodeSeenTracker()
markSeenIndirectUser := nodeSeenTracker()
isAvailableThroughGlobal := exportInfo.exportingModuleSymbol.GlobalExports != nil
getDirectImports := func(moduleSymbol *ast.Symbol) []*ast.Node {
return allDirectImports[moduleSymbol]
}
// Adds a module and all of its transitive dependencies as possible indirect users
var addIndirectUser func(*ast.Node, bool)
addIndirectUser = func(sourceFileLike *ast.Node, addTransitiveDependencies bool) {
debug.Assert(!isAvailableThroughGlobal)
if !markSeenIndirectUser(sourceFileLike) {
return
}
indirectUserDeclarations = append(indirectUserDeclarations, sourceFileLike)
if !addTransitiveDependencies {
return
}
moduleSymbol := checker.GetMergedSymbol(sourceFileLike.Symbol())
if moduleSymbol == nil {
return
}
debug.Assert(moduleSymbol.Flags&ast.SymbolFlagsModule != 0)
for _, directImport := range getDirectImports(moduleSymbol) {
if !ast.IsImportTypeNode(directImport) {
addIndirectUser(getSourceFileLikeForImportDeclaration(directImport), true /*addTransitiveDependencies*/)
}
}
}
isExported := func(node *ast.Node, stopAtAmbientModule bool) bool {
for node != nil && !(stopAtAmbientModule && isAmbientModuleDeclaration(node)) {
if ast.HasSyntacticModifier(node, ast.ModifierFlagsExport) {
return true
}
node = node.Parent
}
return false
}
handleImportCall := func(importCall *ast.Node) {
top := ast.FindAncestor(importCall, isAmbientModuleDeclaration)
if top == nil {
top = ast.GetSourceFileOfNode(importCall).AsNode()
}
addIndirectUser(top, isExported(importCall, true /*stopAtAmbientModule*/))
}
handleNamespaceImport := func(importDeclaration *ast.Node, name *ast.Node, isReExport bool, alreadyAddedDirect bool) {
if exportInfo.exportKind == ExportKindExportEquals {
// This is a direct import, not import-as-namespace.
if !alreadyAddedDirect {
directImports = append(directImports, importDeclaration)
}
} else if !isAvailableThroughGlobal {
sourceFileLike := getSourceFileLikeForImportDeclaration(importDeclaration)
debug.Assert(ast.IsSourceFile(sourceFileLike) || ast.IsModuleDeclaration(sourceFileLike))
addIndirectUser(sourceFileLike, isReExport || findNamespaceReExports(sourceFileLike, name, checker))
}
}
var handleDirectImports func(*ast.Symbol)
handleDirectImports = func(exportingModuleSymbol *ast.Symbol) {
theseDirectImports := getDirectImports(exportingModuleSymbol)
for _, direct := range theseDirectImports {
if !markSeenDirectImport(direct) {
continue
}
// !!! cancellation
switch direct.Kind {
case ast.KindCallExpression:
if ast.IsImportCall(direct) {
handleImportCall(direct)
} else if !isAvailableThroughGlobal {
parent := direct.Parent
if exportInfo.exportKind == ExportKindExportEquals && ast.IsVariableDeclaration(parent) {
name := parent.Name()
if ast.IsIdentifier(name) {
directImports = append(directImports, name)
}
}
}
case ast.KindIdentifier:
// Nothing
case ast.KindImportEqualsDeclaration:
handleNamespaceImport(direct, direct.Name(), ast.HasSyntacticModifier(direct, ast.ModifierFlagsExport), false /*alreadyAddedDirect*/)
case ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindJSDocImportTag:
directImports = append(directImports, direct)
if importClause := direct.ImportClause(); importClause != nil {
if namedBindings := importClause.AsImportClause().NamedBindings; namedBindings != nil && ast.IsNamespaceImport(namedBindings) {
handleNamespaceImport(direct, namedBindings.Name(), false /*isReExport*/, true /*alreadyAddedDirect*/)
break
}
}
if !isAvailableThroughGlobal && ast.IsDefaultImport(direct) {
addIndirectUser(getSourceFileLikeForImportDeclaration(direct), false)
// Add a check for indirect uses to handle synthetic default imports
}
case ast.KindExportDeclaration:
exportClause := direct.AsExportDeclaration().ExportClause
if exportClause == nil {
// This is `export * from "foo"`, so imports of this module may import the export too.
handleDirectImports(getContainingModuleSymbol(direct, checker))
} else if ast.IsNamespaceExport(exportClause) {
// `export * as foo from "foo"` add to indirect uses
addIndirectUser(getSourceFileLikeForImportDeclaration(direct), true /*addTransitiveDependencies*/)
} else {
// This is `export { foo } from "foo"` and creates an alias symbol, so recursive search will get handle re-exports.
directImports = append(directImports, direct)
}
case ast.KindImportType:
// Only check for typeof import('xyz')
if !isAvailableThroughGlobal && direct.AsImportTypeNode().IsTypeOf && direct.AsImportTypeNode().Qualifier == nil && isExported(direct, false) {
addIndirectUser(ast.GetSourceFileOfNode(direct).AsNode(), true /*addTransitiveDependencies*/)
}
directImports = append(directImports, direct)
default:
debug.FailBadSyntaxKind(direct, "Unexpected import kind.")
}
}
}
getIndirectUsers := func() []*ast.SourceFile {
if isAvailableThroughGlobal {
// It has `export as namespace`, so anything could potentially use it.
return sourceFiles
}
// Module augmentations may use this module's exports without importing it.
for _, decl := range exportInfo.exportingModuleSymbol.Declarations {
if ast.IsExternalModuleAugmentation(decl) && sourceFilesSet.Has(ast.GetSourceFileOfNode(decl).FileName()) {
addIndirectUser(decl, false)
}
}
// This may return duplicates (if there are multiple module declarations in a single source file, all importing the same thing as a namespace), but `State.markSearchedSymbol` will handle that.
return core.Map(indirectUserDeclarations, ast.GetSourceFileOfNode)
}
handleDirectImports(exportInfo.exportingModuleSymbol)
return directImports, getIndirectUsers()
}
func getContainingModuleSymbol(importer *ast.Node, checker *checker.Checker) *ast.Symbol {
return checker.GetMergedSymbol(getSourceFileLikeForImportDeclaration(importer).Symbol())
}
// Returns 'true' is the namespace 'name' is re-exported from this module, and 'false' if it is only used locally
func findNamespaceReExports(sourceFileLike *ast.Node, name *ast.Node, checker *checker.Checker) bool {
namespaceImportSymbol := checker.GetSymbolAtLocation(name)
return forEachPossibleImportOrExportStatement(sourceFileLike, func(statement *ast.Node) bool {
if !ast.IsExportDeclaration(statement) {
return false
}
exportClause := statement.AsExportDeclaration().ExportClause
moduleSpecifier := statement.AsExportDeclaration().ModuleSpecifier
return moduleSpecifier == nil && exportClause != nil && ast.IsNamedExports(exportClause) && core.Some(exportClause.Elements(), func(element *ast.Node) bool {
return checker.GetExportSpecifierLocalTargetSymbol(element) == namespaceImportSymbol
})
})
}
func getSearchesFromDirectImports(
directImports []*ast.Node,
exportSymbol *ast.Symbol,
exportKind ExportKind,
checker *checker.Checker,
isForRename bool,
) ([]LocationAndSymbol, []*ast.Node) {
var importSearches []LocationAndSymbol
var singleReferences []*ast.Node
addSearch := func(location *ast.Node, symbol *ast.Symbol) {
importSearches = append(importSearches, LocationAndSymbol{location, symbol})
}
isNameMatch := func(name string) bool {
// Use name of "default" even in `export =` case because we may have allowSyntheticDefaultImports
return name == exportSymbol.Name || exportKind != ExportKindNamed && name == ast.InternalSymbolNameDefault
}
// `import x = require("./x")` or `import * as x from "./x"`.
// An `export =` may be imported by this syntax, so it may be a direct import.
// If it's not a direct import, it will be in `indirectUsers`, so we don't have to do anything here.
handleNamespaceImportLike := func(importName *ast.Node) {
// Don't rename an import that already has a different name than the export.
if exportKind == ExportKindExportEquals && (!isForRename || isNameMatch(importName.Text())) {
addSearch(importName, checker.GetSymbolAtLocation(importName))
}
}
searchForNamedImport := func(namedBindings *ast.Node) {
if namedBindings == nil {
return
}
for _, element := range namedBindings.Elements() {
name := element.Name()
propertyName := element.PropertyName()
if !isNameMatch(core.OrElse(propertyName, name).Text()) {
continue
}
if propertyName != nil {
// This is `import { foo as bar } from "./a"` or `export { foo as bar } from "./a"`. `foo` isn't a local in the file, so just add it as a single reference.
singleReferences = append(singleReferences, propertyName)
// If renaming `{ foo as bar }`, don't touch `bar`, just `foo`.
// But do rename `foo` in ` { default as foo }` if that's the original export name.
if !isForRename || name.Text() == exportSymbol.Name {
// Search locally for `bar`.
addSearch(name, checker.GetSymbolAtLocation(name))
}
} else {
var localSymbol *ast.Symbol
if ast.IsExportSpecifier(element) && element.PropertyName() != nil {
localSymbol = checker.GetExportSpecifierLocalTargetSymbol(element)
} else {
localSymbol = checker.GetSymbolAtLocation(name)
}
addSearch(name, localSymbol)
}
}
}
handleImport := func(decl *ast.Node) {
if ast.IsImportEqualsDeclaration(decl) {
if isExternalModuleImportEquals(decl) {
handleNamespaceImportLike(decl.Name())
}
return
}
if ast.IsIdentifier(decl) {
handleNamespaceImportLike(decl)
return
}
if ast.IsImportTypeNode(decl) {
if qualifier := decl.AsImportTypeNode().Qualifier; qualifier != nil {
firstIdentifier := ast.GetFirstIdentifier(qualifier)
if firstIdentifier.Text() == ast.SymbolName(exportSymbol) {
singleReferences = append(singleReferences, firstIdentifier)
}
} else if exportKind == ExportKindExportEquals {
singleReferences = append(singleReferences, decl.AsImportTypeNode().Argument.AsLiteralTypeNode().Literal)
}
return
}
// Ignore if there's a grammar error
if !ast.IsStringLiteral(decl.ModuleSpecifier()) {
return
}
if ast.IsExportDeclaration(decl) {
if exportClause := decl.AsExportDeclaration().ExportClause; exportClause != nil && ast.IsNamedExports(exportClause) {
searchForNamedImport(exportClause)
}
return
}
if importClause := decl.ImportClause(); importClause != nil {
if namedBindings := importClause.AsImportClause().NamedBindings; namedBindings != nil {
switch namedBindings.Kind {
case ast.KindNamespaceImport:
handleNamespaceImportLike(namedBindings.Name())
case ast.KindNamedImports:
// 'default' might be accessed as a named import `{ default as foo }`.
if exportKind == ExportKindNamed || exportKind == ExportKindDefault {
searchForNamedImport(namedBindings)
}
}
}
// `export =` might be imported by a default import if `--allowSyntheticDefaultImports` is on, so this handles both ExportKind.Default and ExportKind.ExportEquals.
// If a default import has the same name as the default export, allow to rename it.
// Given `import f` and `export default function f`, we will rename both, but for `import g` we will rename just that.
if name := importClause.Name(); name != nil && (exportKind == ExportKindDefault || exportKind == ExportKindExportEquals) && (!isForRename || name.Text() == symbolNameNoDefault(exportSymbol)) {
defaultImportAlias := checker.GetSymbolAtLocation(name)
addSearch(name, defaultImportAlias)
}
}
}
for _, decl := range directImports {
handleImport(decl)
}
return importSearches, singleReferences
}
func getImportOrExportSymbol(node *ast.Node, symbol *ast.Symbol, checker *checker.Checker, comingFromExport bool) *ImportExportSymbol {
exportInfo := func(symbol *ast.Symbol, kind ExportKind) *ImportExportSymbol {
if exportInfo := getExportInfo(symbol, kind, checker); exportInfo != nil {
return &ImportExportSymbol{
kind: ImpExpKindExport,
symbol: symbol,
exportInfo: exportInfo,
}
}
return nil
}
getExport := func() *ImportExportSymbol {
getExportAssignmentExport := func(ex *ast.Node) *ImportExportSymbol {
// Get the symbol for the `export =` node; its parent is the module it's the export of.
if ex.Symbol().Parent == nil {
return nil
}
exportKind := core.IfElse(ex.AsExportAssignment().IsExportEquals, ExportKindExportEquals, ExportKindDefault)
return &ImportExportSymbol{
kind: ImpExpKindExport,
symbol: symbol,
exportInfo: &ExportInfo{
exportingModuleSymbol: ex.Symbol().Parent,
exportKind: exportKind,
},
}
}
// Not meant for use with export specifiers or export assignment.
getExportKindForDeclaration := func(node *ast.Node) ExportKind {
if ast.HasSyntacticModifier(node, ast.ModifierFlagsDefault) {
return ExportKindDefault
}
return ExportKindNamed
}
getSpecialPropertyExport := func(node *ast.Node, useLhsSymbol bool) *ImportExportSymbol {
var kind ExportKind
switch ast.GetAssignmentDeclarationKind(node.AsBinaryExpression()) {
case ast.JSDeclarationKindExportsProperty:
kind = ExportKindNamed
case ast.JSDeclarationKindModuleExports:
kind = ExportKindExportEquals
default:
return nil
}
sym := symbol
if useLhsSymbol {
sym = checker.GetSymbolAtLocation(ast.GetElementOrPropertyAccessName(node.AsBinaryExpression().Left))
}
if sym == nil {
return nil
}
return exportInfo(sym, kind)
}
parent := node.Parent
grandparent := parent.Parent
if symbol.ExportSymbol != nil {
if ast.IsPropertyAccessExpression(parent) {
// When accessing an export of a JS module, there's no alias. The symbol will still be flagged as an export even though we're at the use.
// So check that we are at the declaration.
if ast.IsBinaryExpression(grandparent) && slices.Contains(symbol.Declarations, parent) {
return getSpecialPropertyExport(grandparent, false /*useLhsSymbol*/)
}
return nil
}
return exportInfo(symbol.ExportSymbol, getExportKindForDeclaration(parent))
} else {
exportNode := getExportNode(parent, node)
switch {
case exportNode != nil && ast.HasSyntacticModifier(exportNode, ast.ModifierFlagsExport):
if ast.IsImportEqualsDeclaration(exportNode) && exportNode.AsImportEqualsDeclaration().ModuleReference == node {
// We're at `Y` in `export import X = Y`. This is not the exported symbol, the left-hand-side is. So treat this as an import statement.
if comingFromExport {
return nil
}
lhsSymbol := checker.GetSymbolAtLocation(exportNode.Name())
return &ImportExportSymbol{
kind: ImpExpKindImport,
symbol: lhsSymbol,
}
}
return exportInfo(symbol, getExportKindForDeclaration(exportNode))
case ast.IsNamespaceExport(parent):
return exportInfo(symbol, ExportKindNamed)
case ast.IsExportAssignment(parent):
return getExportAssignmentExport(parent)
case ast.IsExportAssignment(grandparent):
return getExportAssignmentExport(grandparent)
case ast.IsBinaryExpression(parent):
return getSpecialPropertyExport(parent, true /*useLhsSymbol*/)
case ast.IsBinaryExpression(grandparent):
return getSpecialPropertyExport(grandparent, true /*useLhsSymbol*/)
case ast.IsJSDocTypedefTag(parent) || ast.IsJSDocCallbackTag(parent):
return exportInfo(symbol, ExportKindNamed)
}
}
return nil
}
getImport := func() *ImportExportSymbol {
if !isNodeImport(node) {
return nil
}
// A symbol being imported is always an alias. So get what that aliases to find the local symbol.
importedSymbol := checker.GetImmediateAliasedSymbol(symbol)
if importedSymbol == nil {
return nil
}
// Search on the local symbol in the exporting module, not the exported symbol.
importedSymbol = skipExportSpecifierSymbol(importedSymbol, checker)
// Similarly, skip past the symbol for 'export ='
if importedSymbol.Name == "export=" {
importedSymbol = getExportEqualsLocalSymbol(importedSymbol, checker)
if importedSymbol == nil {
return nil
}
}
// If the import has a different name than the export, do not continue searching.
// If `importedName` is undefined, do continue searching as the export is anonymous.
// (All imports returned from this function will be ignored anyway if we are in rename and this is a not a named export.)
importedName := symbolNameNoDefault(importedSymbol)
if importedName == "" || importedName == ast.InternalSymbolNameDefault || importedName == symbol.Name {
return &ImportExportSymbol{
kind: ImpExpKindImport,
symbol: importedSymbol,
}
}
return nil
}
result := getExport()
if result == nil && !comingFromExport {
result = getImport()
}
return result
}
func getExportInfo(exportSymbol *ast.Symbol, exportKind ExportKind, c *checker.Checker) *ExportInfo {
// Parent can be nil if an `export` is not at the top-level (which is a compile error).
if exportSymbol.Parent != nil {
exportingModuleSymbol := c.GetMergedSymbol(exportSymbol.Parent)
// `export` may appear in a namespace. In that case, just rely on global search.
if checker.IsExternalModuleSymbol(exportingModuleSymbol) {
return &ExportInfo{
exportingModuleSymbol: exportingModuleSymbol,
exportKind: exportKind,
}
}
}
return nil
}
// If a reference is a class expression, the exported node would be its parent.
// If a reference is a variable declaration, the exported node would be the variable statement.
func getExportNode(parent *ast.Node, node *ast.Node) *ast.Node {
var declaration *ast.Node
switch {
case ast.IsVariableDeclaration(parent):
declaration = parent
case ast.IsBindingElement(parent):
declaration = ast.WalkUpBindingElementsAndPatterns(parent)
}
if declaration != nil {
if parent.Name() == node && !ast.IsCatchClause(declaration.Parent) && ast.IsVariableStatement(declaration.Parent.Parent) {
return declaration.Parent.Parent
}
return nil
}
return parent
}
func isNodeImport(node *ast.Node) bool {
parent := node.Parent
switch parent.Kind {
case ast.KindImportEqualsDeclaration:
return parent.Name() == node && isExternalModuleImportEquals(parent)
case ast.KindImportSpecifier:
// For a rename import `{ foo as bar }`, don't search for the imported symbol. Just find local uses of `bar`.
return parent.PropertyName() == nil
case ast.KindImportClause, ast.KindNamespaceImport:
debug.Assert(parent.Name() == node)
return true
case ast.KindBindingElement:
return ast.IsInJSFile(node) && ast.IsVariableDeclarationInitializedToRequire(parent.Parent.Parent)
}
return false
}
func isExternalModuleImportEquals(node *ast.Node) bool {
moduleReference := node.AsImportEqualsDeclaration().ModuleReference
return ast.IsExternalModuleReference(moduleReference) && moduleReference.Expression().Kind == ast.KindStringLiteral
}
// If at an export specifier, go to the symbol it refers to. */
func skipExportSpecifierSymbol(symbol *ast.Symbol, checker *checker.Checker) *ast.Symbol {
// For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does.
for _, declaration := range symbol.Declarations {
switch {
case ast.IsExportSpecifier(declaration) && declaration.PropertyName() == nil && declaration.Parent.Parent.ModuleSpecifier() == nil:
return core.OrElse(checker.GetExportSpecifierLocalTargetSymbol(declaration), symbol)
case ast.IsPropertyAccessExpression(declaration) && ast.IsModuleExportsAccessExpression(declaration.Expression()) && !ast.IsPrivateIdentifier(declaration.Name()):
// Export of form 'module.exports.propName = expr';
return checker.GetSymbolAtLocation(declaration)
case ast.IsShorthandPropertyAssignment(declaration) && ast.IsBinaryExpression(declaration.Parent.Parent) && ast.GetAssignmentDeclarationKind(declaration.Parent.Parent.AsBinaryExpression()) == ast.JSDeclarationKindModuleExports:
return checker.GetExportSpecifierLocalTargetSymbol(declaration.Name())
}
}
return symbol
}
func getExportEqualsLocalSymbol(importedSymbol *ast.Symbol, checker *checker.Checker) *ast.Symbol {
if importedSymbol.Flags&ast.SymbolFlagsAlias != 0 {
return checker.GetImmediateAliasedSymbol(importedSymbol)
}
decl := debug.CheckDefined(importedSymbol.ValueDeclaration)
switch {
case ast.IsExportAssignment(decl):
return decl.Expression().Symbol()
case ast.IsBinaryExpression(decl):
return decl.AsBinaryExpression().Right.Symbol()
case ast.IsSourceFile(decl):
return decl.Symbol()
}
return nil
}
func symbolNameNoDefault(symbol *ast.Symbol) string {
if symbol.Name != ast.InternalSymbolNameDefault {
return symbol.Name
}
for _, decl := range symbol.Declarations {
name := ast.GetNameOfDeclaration(decl)
if name != nil && ast.IsIdentifier(name) {
return name.Text()
}
}
return ""
}
// findModuleReferences finds all references to a module symbol across the given source files.
// This includes import statements, <reference> directives, and implicit references (e.g., JSX runtime imports).
func findModuleReferences(program *compiler.Program, sourceFiles []*ast.SourceFile, searchModuleSymbol *ast.Symbol, checker *checker.Checker) []ModuleReference {
refs := []ModuleReference{}
for _, referencingFile := range sourceFiles {
searchSourceFile := searchModuleSymbol.ValueDeclaration
if searchSourceFile != nil && searchSourceFile.Kind == ast.KindSourceFile {
// Check <reference path> directives
for _, ref := range referencingFile.ReferencedFiles {
if program.GetSourceFileFromReference(referencingFile, ref) == searchSourceFile.AsSourceFile() {
refs = append(refs, ModuleReference{
kind: ModuleReferenceKindReference,
referencingFile: referencingFile,
ref: ref,
})
}
}
// Check <reference types> directives
for _, ref := range referencingFile.TypeReferenceDirectives {
referenced := program.GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(ref, referencingFile)
if referenced != nil && referenced.ResolvedFileName == searchSourceFile.AsSourceFile().FileName() {
refs = append(refs, ModuleReference{
kind: ModuleReferenceKindReference,
referencingFile: referencingFile,
ref: ref,
})
}
}
}
// Check all imports (including require() calls)
forEachImport(referencingFile, func(importDecl *ast.Node, moduleSpecifier *ast.Node) {
moduleSymbol := checker.GetSymbolAtLocation(moduleSpecifier)
if moduleSymbol == searchModuleSymbol {
if ast.NodeIsSynthesized(importDecl) {
refs = append(refs, ModuleReference{
kind: ModuleReferenceKindImplicit,
literal: moduleSpecifier,
referencingFile: referencingFile,
})
} else {
refs = append(refs, ModuleReference{
kind: ModuleReferenceKindImport,
literal: moduleSpecifier,
})
}
}
})
}
return refs
}