729 lines
28 KiB
Go
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
|
|
}
|