281 lines
8.7 KiB
Go
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}
|
|
}
|
|
}
|
|
}
|