2025-10-15 10:12:44 +03:00

281 lines
8.7 KiB
Go

package compiler
import (
"math"
"sync"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/module"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
)
type parseTask struct {
normalizedFilePath string
path tspath.Path
file *ast.SourceFile
libFile *LibFile
redirectedParseTask *parseTask
subTasks []*parseTask
loaded bool
isForAutomaticTypeDirective bool
includeReason *fileIncludeReason
metadata ast.SourceFileMetaData
resolutionsInFile module.ModeAwareCache[*module.ResolvedModule]
resolutionsTrace []string
typeResolutionsInFile module.ModeAwareCache[*module.ResolvedTypeReferenceDirective]
typeResolutionsTrace []string
resolutionDiagnostics []*ast.Diagnostic
importHelpersImportSpecifier *ast.Node
jsxRuntimeImportSpecifier *jsxRuntimeImportSpecifier
increaseDepth bool
elideOnDepth bool
// Track if this file is from an external library (node_modules)
// This mirrors the TypeScript currentNodeModulesDepth > 0 check
fromExternalLibrary bool
loadedTask *parseTask
allIncludeReasons []*fileIncludeReason
}
func (t *parseTask) FileName() string {
return t.normalizedFilePath
}
func (t *parseTask) Path() tspath.Path {
return t.path
}
func (t *parseTask) isRoot() bool {
// Intentionally not checking t.includeReason != nil to ensure we can catch cases for missing include reason
return !t.isForAutomaticTypeDirective && (t.includeReason.kind == fileIncludeKindRootFile || t.includeReason.kind == fileIncludeKindLibFile)
}
func (t *parseTask) load(loader *fileLoader) {
t.loaded = true
t.path = loader.toPath(t.normalizedFilePath)
if t.isForAutomaticTypeDirective {
t.loadAutomaticTypeDirectives(loader)
return
}
redirect := loader.projectReferenceFileMapper.getParseFileRedirect(t)
if redirect != "" {
t.redirect(loader, redirect)
return
}
loader.totalFileCount.Add(1)
if t.libFile != nil {
loader.libFileCount.Add(1)
}
t.metadata = loader.loadSourceFileMetaData(t.normalizedFilePath)
file := loader.parseSourceFile(t)
if file == nil {
return
}
t.file = file
t.subTasks = make([]*parseTask, 0, len(file.ReferencedFiles)+len(file.Imports())+len(file.ModuleAugmentations))
for index, ref := range file.ReferencedFiles {
resolvedPath := loader.resolveTripleslashPathReference(ref.FileName, file.FileName(), index)
t.addSubTask(resolvedPath, nil)
}
compilerOptions := loader.opts.Config.CompilerOptions()
loader.resolveTypeReferenceDirectives(t)
if compilerOptions.NoLib != core.TSTrue {
for index, lib := range file.LibReferenceDirectives {
includeReason := &fileIncludeReason{
kind: fileIncludeKindLibReferenceDirective,
data: &referencedFileData{
file: t.path,
index: index,
},
}
if name, ok := tsoptions.GetLibFileName(lib.FileName); ok {
libFile := loader.pathForLibFile(name)
t.addSubTask(resolvedRef{
fileName: libFile.path,
includeReason: includeReason,
}, libFile)
} else {
loader.includeProcessor.addProcessingDiagnostic(&processingDiagnostic{
kind: processingDiagnosticKindUnknownReference,
data: includeReason,
})
}
}
}
loader.resolveImportsAndModuleAugmentations(t)
}
func (t *parseTask) redirect(loader *fileLoader, fileName string) {
t.redirectedParseTask = &parseTask{
normalizedFilePath: tspath.NormalizePath(fileName),
libFile: t.libFile,
fromExternalLibrary: t.fromExternalLibrary,
includeReason: t.includeReason,
}
// increaseDepth and elideOnDepth are not copied to redirects, otherwise their depth would be double counted.
t.subTasks = []*parseTask{t.redirectedParseTask}
}
func (t *parseTask) loadAutomaticTypeDirectives(loader *fileLoader) {
toParseTypeRefs, typeResolutionsInFile, typeResolutionsTrace := loader.resolveAutomaticTypeDirectives(t.normalizedFilePath)
t.typeResolutionsInFile = typeResolutionsInFile
t.typeResolutionsTrace = typeResolutionsTrace
for _, typeResolution := range toParseTypeRefs {
t.addSubTask(typeResolution, nil)
}
}
type resolvedRef struct {
fileName string
increaseDepth bool
elideOnDepth bool
isFromExternalLibrary bool
includeReason *fileIncludeReason
}
func (t *parseTask) addSubTask(ref resolvedRef, libFile *LibFile) {
normalizedFilePath := tspath.NormalizePath(ref.fileName)
subTask := &parseTask{
normalizedFilePath: normalizedFilePath,
libFile: libFile,
increaseDepth: ref.increaseDepth,
elideOnDepth: ref.elideOnDepth,
fromExternalLibrary: ref.isFromExternalLibrary,
includeReason: ref.includeReason,
}
t.subTasks = append(t.subTasks, subTask)
}
type filesParser struct {
wg core.WorkGroup
tasksByFileName collections.SyncMap[string, *queuedParseTask]
maxDepth int
}
type queuedParseTask struct {
task *parseTask
mu sync.Mutex
lowestDepth int
fromExternalLibrary bool
}
func (w *filesParser) parse(loader *fileLoader, tasks []*parseTask) {
w.start(loader, tasks, 0, false)
w.wg.RunAndWait()
}
func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, isFromExternalLibrary bool) {
for i, task := range tasks {
taskIsFromExternalLibrary := isFromExternalLibrary || task.fromExternalLibrary
newTask := &queuedParseTask{task: task, lowestDepth: math.MaxInt}
loadedTask, loaded := w.tasksByFileName.LoadOrStore(task.FileName(), newTask)
task = loadedTask.task
if loaded {
tasks[i].loadedTask = task
// Add in the loaded task's external-ness.
taskIsFromExternalLibrary = taskIsFromExternalLibrary || task.fromExternalLibrary
}
w.wg.Queue(func() {
loadedTask.mu.Lock()
defer loadedTask.mu.Unlock()
startSubtasks := false
currentDepth := depth
if task.increaseDepth {
currentDepth++
}
if currentDepth < loadedTask.lowestDepth {
// If we're seeing this task at a lower depth than before,
// reprocess its subtasks to ensure they are loaded.
loadedTask.lowestDepth = currentDepth
startSubtasks = true
}
if !task.isRoot() && taskIsFromExternalLibrary && !loadedTask.fromExternalLibrary {
// If we're seeing this task now as an external library,
// reprocess its subtasks to ensure they are also marked as external.
loadedTask.fromExternalLibrary = true
startSubtasks = true
}
if task.elideOnDepth && currentDepth > w.maxDepth {
return
}
if !task.loaded {
task.load(loader)
}
if startSubtasks {
w.start(loader, task.subTasks, loadedTask.lowestDepth, loadedTask.fromExternalLibrary)
}
})
}
}
func (w *filesParser) collect(loader *fileLoader, tasks []*parseTask, iterate func(*parseTask)) {
// Mark all tasks we saw as external after the fact.
w.tasksByFileName.Range(func(key string, value *queuedParseTask) bool {
if value.fromExternalLibrary {
value.task.fromExternalLibrary = true
}
return true
})
w.collectWorker(loader, tasks, iterate, collections.Set[*parseTask]{})
}
func (w *filesParser) collectWorker(loader *fileLoader, tasks []*parseTask, iterate func(*parseTask), seen collections.Set[*parseTask]) {
for _, task := range tasks {
// Exclude automatic type directive tasks from include reason processing,
// as these are internal implementation details and should not contribute
// to the reasons for including files.
if task.redirectedParseTask == nil && !task.isForAutomaticTypeDirective {
includeReason := task.includeReason
if task.loadedTask != nil {
task = task.loadedTask
}
w.addIncludeReason(loader, task, includeReason)
}
// ensure we only walk each task once
if !task.loaded || !seen.AddIfAbsent(task) {
continue
}
for _, trace := range task.typeResolutionsTrace {
loader.opts.Host.Trace(trace)
}
for _, trace := range task.resolutionsTrace {
loader.opts.Host.Trace(trace)
}
if subTasks := task.subTasks; len(subTasks) > 0 {
w.collectWorker(loader, subTasks, iterate, seen)
}
iterate(task)
}
}
func (w *filesParser) addIncludeReason(loader *fileLoader, task *parseTask, reason *fileIncludeReason) {
if task.redirectedParseTask != nil {
w.addIncludeReason(loader, task.redirectedParseTask, reason)
} else if task.loaded {
if existing, ok := loader.includeProcessor.fileIncludeReasons[task.path]; ok {
loader.includeProcessor.fileIncludeReasons[task.path] = append(existing, reason)
} else {
loader.includeProcessor.fileIncludeReasons[task.path] = []*fileIncludeReason{reason}
}
}
}