internal/compiler/filesparser.go (340 lines of code) (raw):

package compiler import ( "math" "slices" "sync" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/module" "github.com/microsoft/typescript-go/internal/tsoptions" "github.com/microsoft/typescript-go/internal/tspath" ) type parseTask struct { normalizedFilePath string path tspath.Path file *ast.SourceFile libFile *LibFile redirectedParseTask *parseTask subTasks []*parseTask loaded bool startedSubTasks bool isForAutomaticTypeDirective bool includeReason *FileIncludeReason metadata ast.SourceFileMetaData resolutionsInFile module.ModeAwareCache[*module.ResolvedModule] resolutionsTrace []module.DiagAndArgs typeResolutionsInFile module.ModeAwareCache[*module.ResolvedTypeReferenceDirective] typeResolutionsTrace []module.DiagAndArgs resolutionDiagnostics []*ast.Diagnostic importHelpersImportSpecifier *ast.Node jsxRuntimeImportSpecifier *jsxRuntimeImportSpecifier increaseDepth bool elideOnDepth 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) load(loader *fileLoader) { t.loaded = true 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, 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 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, includeReason: ref.includeReason, } t.subTasks = append(t.subTasks, subTask) } type filesParser struct { wg core.WorkGroup taskDataByPath collections.SyncMap[tspath.Path, *parseTaskData] maxDepth int } type parseTaskData struct { // map of tasks by file casing tasks map[string]*parseTask mu sync.Mutex lowestDepth int startedSubTasks bool } func (w *filesParser) parse(loader *fileLoader, tasks []*parseTask) { w.start(loader, tasks, 0) w.wg.RunAndWait() } func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int) { for i, task := range tasks { task.path = loader.toPath(task.normalizedFilePath) data, loaded := w.taskDataByPath.LoadOrStore(task.path, &parseTaskData{ tasks: map[string]*parseTask{task.normalizedFilePath: task}, lowestDepth: math.MaxInt, }) w.wg.Queue(func() { data.mu.Lock() defer data.mu.Unlock() startSubtasks := false if loaded { if existingTask, ok := data.tasks[task.normalizedFilePath]; ok { tasks[i].loadedTask = existingTask } else { data.tasks[task.normalizedFilePath] = task // This is new task for file name - so load subtasks if there was loading for any other casing startSubtasks = data.startedSubTasks } } currentDepth := core.IfElse(task.increaseDepth, depth+1, depth) if currentDepth < data.lowestDepth { // If we're seeing this task at a lower depth than before, // reprocess its subtasks to ensure they are loaded. data.lowestDepth = currentDepth startSubtasks = true data.startedSubTasks = true } if task.elideOnDepth && currentDepth > w.maxDepth { return } for _, taskByFileName := range data.tasks { loadSubTasks := startSubtasks if !taskByFileName.loaded { taskByFileName.load(loader) if taskByFileName.redirectedParseTask != nil { // Always load redirected task loadSubTasks = true data.startedSubTasks = true } } if !taskByFileName.startedSubTasks && loadSubTasks { taskByFileName.startedSubTasks = true w.start(loader, taskByFileName.subTasks, data.lowestDepth) } } }) } } func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { totalFileCount := int(loader.totalFileCount.Load()) libFileCount := int(loader.libFileCount.Load()) var missingFiles []string files := make([]*ast.SourceFile, 0, totalFileCount-libFileCount) libFiles := make([]*ast.SourceFile, 0, totalFileCount) // totalFileCount here since we append files to it later to construct the final list filesByPath := make(map[tspath.Path]*ast.SourceFile, totalFileCount) // stores 'filename -> file association' ignoring case // used to track cases when two file names differ only in casing var tasksSeenByNameIgnoreCase map[string]*parseTask if loader.comparePathsOptions.UseCaseSensitiveFileNames { tasksSeenByNameIgnoreCase = make(map[string]*parseTask, totalFileCount) } loader.includeProcessor.fileIncludeReasons = make(map[tspath.Path][]*FileIncludeReason, totalFileCount) var outputFileToProjectReferenceSource map[tspath.Path]string if !loader.opts.canUseProjectReferenceSource() { outputFileToProjectReferenceSource = make(map[tspath.Path]string, totalFileCount) } resolvedModules := make(map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule], totalFileCount+1) typeResolutionsInFile := make(map[tspath.Path]module.ModeAwareCache[*module.ResolvedTypeReferenceDirective], totalFileCount) sourceFileMetaDatas := make(map[tspath.Path]ast.SourceFileMetaData, totalFileCount) var jsxRuntimeImportSpecifiers map[tspath.Path]*jsxRuntimeImportSpecifier var importHelpersImportSpecifiers map[tspath.Path]*ast.Node var sourceFilesFoundSearchingNodeModules collections.Set[tspath.Path] libFilesMap := make(map[tspath.Path]*LibFile, libFileCount) var collectFiles func(tasks []*parseTask, seen map[*parseTaskData]string) collectFiles = func(tasks []*parseTask, seen map[*parseTaskData]string) { for _, task := range tasks { includeReason := task.includeReason // 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 { if task.loadedTask != nil { task = task.loadedTask } w.addIncludeReason(loader, task, includeReason) } data, _ := w.taskDataByPath.Load(task.path) if !task.loaded { continue } // ensure we only walk each task once if checkedName, ok := seen[data]; ok { if !loader.opts.Config.CompilerOptions().ForceConsistentCasingInFileNames.IsFalse() { // Check if it differs only in drive letters its ok to ignore that error: checkedAbsolutePath := tspath.GetNormalizedAbsolutePathWithoutRoot(checkedName, loader.comparePathsOptions.CurrentDirectory) inputAbsolutePath := tspath.GetNormalizedAbsolutePathWithoutRoot(task.normalizedFilePath, loader.comparePathsOptions.CurrentDirectory) if checkedAbsolutePath != inputAbsolutePath { loader.includeProcessor.addProcessingDiagnosticsForFileCasing(task.path, checkedName, task.normalizedFilePath, includeReason) } } continue } else { seen[data] = task.normalizedFilePath } if tasksSeenByNameIgnoreCase != nil { pathLowerCase := tspath.ToFileNameLowerCase(string(task.path)) if taskByIgnoreCase, ok := tasksSeenByNameIgnoreCase[pathLowerCase]; ok { loader.includeProcessor.addProcessingDiagnosticsForFileCasing(taskByIgnoreCase.path, taskByIgnoreCase.normalizedFilePath, task.normalizedFilePath, includeReason) } else { tasksSeenByNameIgnoreCase[pathLowerCase] = task } } for _, trace := range task.typeResolutionsTrace { loader.opts.Host.Trace(trace.Message, trace.Args...) } for _, trace := range task.resolutionsTrace { loader.opts.Host.Trace(trace.Message, trace.Args...) } if subTasks := task.subTasks; len(subTasks) > 0 { collectFiles(subTasks, seen) } // 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 { if !loader.opts.canUseProjectReferenceSource() { outputFileToProjectReferenceSource[task.redirectedParseTask.path] = task.FileName() } continue } if task.isForAutomaticTypeDirective { typeResolutionsInFile[task.path] = task.typeResolutionsInFile continue } file := task.file path := task.path if file == nil { // !!! sheetal file preprocessing diagnostic explaining getSourceFileFromReferenceWorker missingFiles = append(missingFiles, task.normalizedFilePath) continue } if task.libFile != nil { libFiles = append(libFiles, file) libFilesMap[path] = task.libFile } else { files = append(files, file) } filesByPath[path] = file resolvedModules[path] = task.resolutionsInFile typeResolutionsInFile[path] = task.typeResolutionsInFile sourceFileMetaDatas[path] = task.metadata if task.jsxRuntimeImportSpecifier != nil { if jsxRuntimeImportSpecifiers == nil { jsxRuntimeImportSpecifiers = make(map[tspath.Path]*jsxRuntimeImportSpecifier, totalFileCount) } jsxRuntimeImportSpecifiers[path] = task.jsxRuntimeImportSpecifier } if task.importHelpersImportSpecifier != nil { if importHelpersImportSpecifiers == nil { importHelpersImportSpecifiers = make(map[tspath.Path]*ast.Node, totalFileCount) } importHelpersImportSpecifiers[path] = task.importHelpersImportSpecifier } if data.lowestDepth > 0 { sourceFilesFoundSearchingNodeModules.Add(path) } } } collectFiles(loader.rootTasks, make(map[*parseTaskData]string, totalFileCount)) loader.sortLibs(libFiles) allFiles := append(libFiles, files...) keys := slices.Collect(loader.pathForLibFileResolutions.Keys()) slices.Sort(keys) for _, key := range keys { value, _ := loader.pathForLibFileResolutions.Load(key) resolvedModules[key] = module.ModeAwareCache[*module.ResolvedModule]{ module.ModeAwareCacheKey{Name: value.libraryName, Mode: core.ModuleKindCommonJS}: value.resolution, } for _, trace := range value.trace { loader.opts.Host.Trace(trace.Message, trace.Args...) } } return processedFiles{ finishedProcessing: true, resolver: loader.resolver, files: allFiles, filesByPath: filesByPath, projectReferenceFileMapper: loader.projectReferenceFileMapper, resolvedModules: resolvedModules, typeResolutionsInFile: typeResolutionsInFile, sourceFileMetaDatas: sourceFileMetaDatas, jsxRuntimeImportSpecifiers: jsxRuntimeImportSpecifiers, importHelpersImportSpecifiers: importHelpersImportSpecifiers, sourceFilesFoundSearchingNodeModules: sourceFilesFoundSearchingNodeModules, libFiles: libFilesMap, missingFiles: missingFiles, includeProcessor: loader.includeProcessor, outputFileToProjectReferenceSource: outputFileToProjectReferenceSource, } } 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} } } }