From 6159d5298647234d764fad7268d9bb47e547d36d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 25 Nov 2025 14:46:40 -0800 Subject: [PATCH 01/15] Refactor --- internal/compiler/fileloader.go | 120 +----------------------------- internal/compiler/filesparser.go | 122 +++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 118 deletions(-) diff --git a/internal/compiler/fileloader.go b/internal/compiler/fileloader.go index ef866d1921..b77c030c65 100644 --- a/internal/compiler/fileloader.go +++ b/internal/compiler/fileloader.go @@ -129,128 +129,12 @@ func processAllProgramFiles( } loader.filesParser.parse(&loader, loader.rootTasks) + // Clear out loader and host to ensure its not used post program creation loader.projectReferenceFileMapper.loader = nil loader.projectReferenceFileMapper.host = nil - 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) - loader.includeProcessor.fileIncludeReasons = make(map[tspath.Path][]*FileIncludeReason, totalFileCount) - var outputFileToProjectReferenceSource map[tspath.Path]string - if !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) - - loader.filesParser.collect(&loader, loader.rootTasks, func(task *parseTask) { - if task.redirectedParseTask != nil { - if !opts.canUseProjectReferenceSource() { - outputFileToProjectReferenceSource[task.redirectedParseTask.path] = task.FileName() - } - return - } - - if task.isForAutomaticTypeDirective { - typeResolutionsInFile[task.path] = task.typeResolutionsInFile - return - } - file := task.file - path := task.path - if file == nil { - // !!! sheetal file preprocessing diagnostic explaining getSourceFileFromReferenceWorker - missingFiles = append(missingFiles, task.normalizedFilePath) - return - } - - // !!! sheetal todo porting file case errors - // if _, ok := filesByPath[path]; ok { - // Check if it differs only in drive letters its ok to ignore that error: - // const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); - // const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); - // if (checkedAbsolutePath !== inputAbsolutePath) { - // reportFileNamesDifferOnlyInCasingError(fileName, file, reason); - // } - // } else if loader.comparePathsOptions.UseCaseSensitiveFileNames { - // pathIgnoreCase := tspath.ToPath(file.FileName(), loader.comparePathsOptions.CurrentDirectory, false) - // // for case-sensitsive file systems check if we've already seen some file with similar filename ignoring case - // if _, ok := filesByNameIgnoreCase[pathIgnoreCase]; ok { - // reportFileNamesDifferOnlyInCasingError(fileName, existingFile, reason); - // } else { - // filesByNameIgnoreCase[pathIgnoreCase] = file - // } - // } - - 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 task.fromExternalLibrary { - sourceFilesFoundSearchingNodeModules.Add(path) - } - }) - 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 { - opts.Host.Trace(trace.Message, trace.Args...) - } - } - - return processedFiles{ - 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, - } + return loader.filesParser.getProcessedFiles(&loader) } func (p *fileLoader) toPath(file string) tspath.Path { diff --git a/internal/compiler/filesparser.go b/internal/compiler/filesparser.go index 554ca47327..96976d2b42 100644 --- a/internal/compiler/filesparser.go +++ b/internal/compiler/filesparser.go @@ -2,6 +2,7 @@ package compiler import ( "math" + "slices" "sync" "github.com/microsoft/typescript-go/internal/ast" @@ -227,6 +228,127 @@ func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, i } } +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) + 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) + + loader.filesParser.collect(loader, loader.rootTasks, func(task *parseTask) { + if task.redirectedParseTask != nil { + if !loader.opts.canUseProjectReferenceSource() { + outputFileToProjectReferenceSource[task.redirectedParseTask.path] = task.FileName() + } + return + } + + if task.isForAutomaticTypeDirective { + typeResolutionsInFile[task.path] = task.typeResolutionsInFile + return + } + file := task.file + path := task.path + if file == nil { + // !!! sheetal file preprocessing diagnostic explaining getSourceFileFromReferenceWorker + missingFiles = append(missingFiles, task.normalizedFilePath) + return + } + + // !!! sheetal todo porting file case errors + // if _, ok := filesByPath[path]; ok { + // Check if it differs only in drive letters its ok to ignore that error: + // const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); + // const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); + // if (checkedAbsolutePath !== inputAbsolutePath) { + // reportFileNamesDifferOnlyInCasingError(fileName, file, reason); + // } + // } else if loader.comparePathsOptions.UseCaseSensitiveFileNames { + // pathIgnoreCase := tspath.ToPath(file.FileName(), loader.comparePathsOptions.CurrentDirectory, false) + // // for case-sensitsive file systems check if we've already seen some file with similar filename ignoring case + // if _, ok := filesByNameIgnoreCase[pathIgnoreCase]; ok { + // reportFileNamesDifferOnlyInCasingError(fileName, existingFile, reason); + // } else { + // filesByNameIgnoreCase[pathIgnoreCase] = file + // } + // } + + 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 task.fromExternalLibrary { + sourceFilesFoundSearchingNodeModules.Add(path) + } + }) + 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{ + 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) 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 { From 00d37dd96401ed6d9b9742da7f04c2f8cf6a299e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 25 Nov 2025 14:57:04 -0800 Subject: [PATCH 02/15] Refactor 2 --- internal/compiler/filesparser.go | 195 ++++++++++++++++--------------- 1 file changed, 98 insertions(+), 97 deletions(-) diff --git a/internal/compiler/filesparser.go b/internal/compiler/filesparser.go index 96976d2b42..7646222ab0 100644 --- a/internal/compiler/filesparser.go +++ b/internal/compiler/filesparser.go @@ -250,71 +250,112 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { var sourceFilesFoundSearchingNodeModules collections.Set[tspath.Path] libFilesMap := make(map[tspath.Path]*LibFile, libFileCount) - loader.filesParser.collect(loader, loader.rootTasks, func(task *parseTask) { - if task.redirectedParseTask != nil { - if !loader.opts.canUseProjectReferenceSource() { - outputFileToProjectReferenceSource[task.redirectedParseTask.path] = task.FileName() + var collectFiles func(tasks []*parseTask, seen collections.Set[*queuedParseTask]) + collectFiles = func(tasks []*parseTask, seen collections.Set[*queuedParseTask]) { + 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) + } + queued, _ := w.tasksByFileName.Load(task.normalizedFilePath) + // ensure we only walk each task once + if !task.loaded || !seen.AddIfAbsent(queued) { + continue + } + 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) } - return - } - if task.isForAutomaticTypeDirective { - typeResolutionsInFile[task.path] = task.typeResolutionsInFile - return - } - file := task.file - path := task.path - if file == nil { - // !!! sheetal file preprocessing diagnostic explaining getSourceFileFromReferenceWorker - missingFiles = append(missingFiles, task.normalizedFilePath) - return - } + // 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 + } - // !!! sheetal todo porting file case errors - // if _, ok := filesByPath[path]; ok { - // Check if it differs only in drive letters its ok to ignore that error: - // const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); - // const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); - // if (checkedAbsolutePath !== inputAbsolutePath) { - // reportFileNamesDifferOnlyInCasingError(fileName, file, reason); - // } - // } else if loader.comparePathsOptions.UseCaseSensitiveFileNames { - // pathIgnoreCase := tspath.ToPath(file.FileName(), loader.comparePathsOptions.CurrentDirectory, false) - // // for case-sensitsive file systems check if we've already seen some file with similar filename ignoring case - // if _, ok := filesByNameIgnoreCase[pathIgnoreCase]; ok { - // reportFileNamesDifferOnlyInCasingError(fileName, existingFile, reason); - // } else { - // filesByNameIgnoreCase[pathIgnoreCase] = file - // } - // } - - 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) + if task.isForAutomaticTypeDirective { + typeResolutionsInFile[task.path] = task.typeResolutionsInFile + continue } - jsxRuntimeImportSpecifiers[path] = task.jsxRuntimeImportSpecifier - } - if task.importHelpersImportSpecifier != nil { - if importHelpersImportSpecifiers == nil { - importHelpersImportSpecifiers = make(map[tspath.Path]*ast.Node, totalFileCount) + file := task.file + path := task.path + if file == nil { + // !!! sheetal file preprocessing diagnostic explaining getSourceFileFromReferenceWorker + missingFiles = append(missingFiles, task.normalizedFilePath) + continue + } + + // !!! sheetal todo porting file case errors + // if _, ok := filesByPath[path]; ok { + // Check if it differs only in drive letters its ok to ignore that error: + // const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); + // const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); + // if (checkedAbsolutePath !== inputAbsolutePath) { + // reportFileNamesDifferOnlyInCasingError(fileName, file, reason); + // } + // } else if loader.comparePathsOptions.UseCaseSensitiveFileNames { + // pathIgnoreCase := tspath.ToPath(file.FileName(), loader.comparePathsOptions.CurrentDirectory, false) + // // for case-sensitsive file systems check if we've already seen some file with similar filename ignoring case + // if _, ok := filesByNameIgnoreCase[pathIgnoreCase]; ok { + // reportFileNamesDifferOnlyInCasingError(fileName, existingFile, reason); + // } else { + // filesByNameIgnoreCase[pathIgnoreCase] = file + // } + // } + + 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 task.fromExternalLibrary { + sourceFilesFoundSearchingNodeModules.Add(path) } - importHelpersImportSpecifiers[path] = task.importHelpersImportSpecifier } - if task.fromExternalLibrary { - sourceFilesFoundSearchingNodeModules.Add(path) + } + + // 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 }) + + collectFiles(loader.rootTasks, collections.Set[*queuedParseTask]{}) loader.sortLibs(libFiles) allFiles := append(libFiles, files...) @@ -349,46 +390,6 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { } } -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.Message, trace.Args...) - } - for _, trace := range task.resolutionsTrace { - loader.opts.Host.Trace(trace.Message, trace.Args...) - } - 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) From ea8ca90c323c7905149b0dcf1b3b124a14cada73 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 25 Nov 2025 15:07:09 -0800 Subject: [PATCH 03/15] Refactor 3 --- internal/compiler/filesparser.go | 37 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/internal/compiler/filesparser.go b/internal/compiler/filesparser.go index 7646222ab0..158b8c1d5b 100644 --- a/internal/compiler/filesparser.go +++ b/internal/compiler/filesparser.go @@ -161,11 +161,11 @@ func (t *parseTask) addSubTask(ref resolvedRef, libFile *LibFile) { type filesParser struct { wg core.WorkGroup - tasksByFileName collections.SyncMap[string, *queuedParseTask] + tasksByFileName collections.SyncMap[string, *parseTaskData] maxDepth int } -type queuedParseTask struct { +type parseTaskData struct { task *parseTask mu sync.Mutex lowestDepth int @@ -180,9 +180,8 @@ func (w *filesParser) parse(loader *fileLoader, tasks []*parseTask) { 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 + data, loaded := w.tasksByFileName.LoadOrStore(task.FileName(), &parseTaskData{task: task, lowestDepth: math.MaxInt}) + task = data.task if loaded { tasks[i].loadedTask = task // Add in the loaded task's external-ness. @@ -190,8 +189,8 @@ func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, i } w.wg.Queue(func() { - loadedTask.mu.Lock() - defer loadedTask.mu.Unlock() + data.mu.Lock() + defer data.mu.Unlock() startSubtasks := false @@ -199,17 +198,17 @@ func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, i if task.increaseDepth { currentDepth++ } - if currentDepth < loadedTask.lowestDepth { + if currentDepth < data.lowestDepth { // If we're seeing this task at a lower depth than before, // reprocess its subtasks to ensure they are loaded. - loadedTask.lowestDepth = currentDepth + data.lowestDepth = currentDepth startSubtasks = true } - if !task.isRoot() && taskIsFromExternalLibrary && !loadedTask.fromExternalLibrary { + if !task.isRoot() && taskIsFromExternalLibrary && !data.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 + data.fromExternalLibrary = true startSubtasks = true } @@ -222,7 +221,7 @@ func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, i } if startSubtasks { - w.start(loader, task.subTasks, loadedTask.lowestDepth, loadedTask.fromExternalLibrary) + w.start(loader, task.subTasks, data.lowestDepth, data.fromExternalLibrary) } }) } @@ -250,8 +249,8 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { var sourceFilesFoundSearchingNodeModules collections.Set[tspath.Path] libFilesMap := make(map[tspath.Path]*LibFile, libFileCount) - var collectFiles func(tasks []*parseTask, seen collections.Set[*queuedParseTask]) - collectFiles = func(tasks []*parseTask, seen collections.Set[*queuedParseTask]) { + var collectFiles func(tasks []*parseTask, seen collections.Set[*parseTaskData]) + collectFiles = func(tasks []*parseTask, seen collections.Set[*parseTaskData]) { for _, task := range tasks { // Exclude automatic type directive tasks from include reason processing, // as these are internal implementation details and should not contribute @@ -263,9 +262,9 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { } w.addIncludeReason(loader, task, includeReason) } - queued, _ := w.tasksByFileName.Load(task.normalizedFilePath) + data, _ := w.tasksByFileName.Load(task.normalizedFilePath) // ensure we only walk each task once - if !task.loaded || !seen.AddIfAbsent(queued) { + if !task.loaded || !seen.AddIfAbsent(data) { continue } for _, trace := range task.typeResolutionsTrace { @@ -341,21 +340,21 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { } importHelpersImportSpecifiers[path] = task.importHelpersImportSpecifier } - if task.fromExternalLibrary { + if data.fromExternalLibrary { sourceFilesFoundSearchingNodeModules.Add(path) } } } // Mark all tasks we saw as external after the fact. - w.tasksByFileName.Range(func(key string, value *queuedParseTask) bool { + w.tasksByFileName.Range(func(key string, value *parseTaskData) bool { if value.fromExternalLibrary { value.task.fromExternalLibrary = true } return true }) - collectFiles(loader.rootTasks, collections.Set[*queuedParseTask]{}) + collectFiles(loader.rootTasks, collections.Set[*parseTaskData]{}) loader.sortLibs(libFiles) allFiles := append(libFiles, files...) From 16b4c46a65087ea57ceea4c63839670659c5243e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 25 Nov 2025 15:07:31 -0800 Subject: [PATCH 04/15] Remove unnecessary loop --- internal/compiler/filesparser.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/internal/compiler/filesparser.go b/internal/compiler/filesparser.go index 158b8c1d5b..35a0fa323c 100644 --- a/internal/compiler/filesparser.go +++ b/internal/compiler/filesparser.go @@ -346,14 +346,6 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { } } - // Mark all tasks we saw as external after the fact. - w.tasksByFileName.Range(func(key string, value *parseTaskData) bool { - if value.fromExternalLibrary { - value.task.fromExternalLibrary = true - } - return true - }) - collectFiles(loader.rootTasks, collections.Set[*parseTaskData]{}) loader.sortLibs(libFiles) From 55c0c988c11d78d6f1154ed89e45f12cc7edb8aa Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 25 Nov 2025 15:26:06 -0800 Subject: [PATCH 05/15] Use data instead of task for starting tasks --- internal/compiler/filesparser.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/internal/compiler/filesparser.go b/internal/compiler/filesparser.go index 35a0fa323c..ac3e9aff83 100644 --- a/internal/compiler/filesparser.go +++ b/internal/compiler/filesparser.go @@ -58,7 +58,6 @@ func (t *parseTask) isRoot() bool { func (t *parseTask) load(loader *fileLoader) { t.loaded = true - t.path = loader.toPath(t.normalizedFilePath) if t.isForAutomaticTypeDirective { t.loadAutomaticTypeDirectives(loader) return @@ -168,6 +167,7 @@ type filesParser struct { type parseTaskData struct { task *parseTask mu sync.Mutex + isRoot bool lowestDepth int fromExternalLibrary bool } @@ -179,13 +179,18 @@ func (w *filesParser) parse(loader *fileLoader, tasks []*parseTask) { func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, isFromExternalLibrary bool) { for i, task := range tasks { + task.path = loader.toPath(task.normalizedFilePath) taskIsFromExternalLibrary := isFromExternalLibrary || task.fromExternalLibrary - data, loaded := w.tasksByFileName.LoadOrStore(task.FileName(), &parseTaskData{task: task, lowestDepth: math.MaxInt}) - task = data.task + data, loaded := w.tasksByFileName.LoadOrStore(task.FileName(), &parseTaskData{ + task: task, + isRoot: task.isRoot(), + lowestDepth: math.MaxInt, + }) + // task = data.task if loaded { - tasks[i].loadedTask = task + tasks[i].loadedTask = data.task // Add in the loaded task's external-ness. - taskIsFromExternalLibrary = taskIsFromExternalLibrary || task.fromExternalLibrary + taskIsFromExternalLibrary = taskIsFromExternalLibrary || data.fromExternalLibrary } w.wg.Queue(func() { @@ -194,10 +199,7 @@ func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, i startSubtasks := false - currentDepth := depth - if task.increaseDepth { - currentDepth++ - } + 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. @@ -205,7 +207,7 @@ func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, i startSubtasks = true } - if !task.isRoot() && taskIsFromExternalLibrary && !data.fromExternalLibrary { + if !data.isRoot && taskIsFromExternalLibrary && !data.fromExternalLibrary { // If we're seeing this task now as an external library, // reprocess its subtasks to ensure they are also marked as external. data.fromExternalLibrary = true @@ -216,12 +218,12 @@ func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, i return } - if !task.loaded { - task.load(loader) + if !data.task.loaded { + data.task.load(loader) } if startSubtasks { - w.start(loader, task.subTasks, data.lowestDepth, data.fromExternalLibrary) + w.start(loader, data.task.subTasks, data.lowestDepth, data.fromExternalLibrary) } }) } From 34edd58b78ea576e04f6206d7d56c99fe478b377 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 25 Nov 2025 15:43:35 -0800 Subject: [PATCH 06/15] Remove unnecessary fileFromExternalLibrary and handle depth instead --- internal/compiler/fileloader.go | 16 ++++--- internal/compiler/filesparser.go | 71 +++++++++++--------------------- 2 files changed, 32 insertions(+), 55 deletions(-) diff --git a/internal/compiler/fileloader.go b/internal/compiler/fileloader.go index b77c030c65..c1122f44ce 100644 --- a/internal/compiler/fileloader.go +++ b/internal/compiler/fileloader.go @@ -350,11 +350,10 @@ func (p *fileLoader) resolveTypeReferenceDirectives(t *parseTask) { if resolved.IsResolved() { t.addSubTask(resolvedRef{ - fileName: resolved.ResolvedFileName, - increaseDepth: resolved.IsExternalLibraryImport, - elideOnDepth: false, - isFromExternalLibrary: resolved.IsExternalLibraryImport, - includeReason: includeReason, + fileName: resolved.ResolvedFileName, + increaseDepth: resolved.IsExternalLibraryImport, + elideOnDepth: false, + includeReason: includeReason, }, nil) } else { p.includeProcessor.addProcessingDiagnostic(&processingDiagnostic{ @@ -450,10 +449,9 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(t *parseTask) { if shouldAddFile { t.addSubTask(resolvedRef{ - fileName: resolvedFileName, - increaseDepth: resolvedModule.IsExternalLibraryImport, - elideOnDepth: isJsFileFromNodeModules, - isFromExternalLibrary: resolvedModule.IsExternalLibraryImport, + fileName: resolvedFileName, + increaseDepth: resolvedModule.IsExternalLibraryImport, + elideOnDepth: isJsFileFromNodeModules, includeReason: &FileIncludeReason{ kind: fileIncludeKindImport, data: &referencedFileData{ diff --git a/internal/compiler/filesparser.go b/internal/compiler/filesparser.go index ac3e9aff83..e364c98795 100644 --- a/internal/compiler/filesparser.go +++ b/internal/compiler/filesparser.go @@ -32,12 +32,9 @@ type parseTask struct { 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 + increaseDepth bool + elideOnDepth bool loadedTask *parseTask allIncludeReasons []*FileIncludeReason @@ -51,11 +48,6 @@ 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 if t.isForAutomaticTypeDirective { @@ -119,10 +111,9 @@ func (t *parseTask) load(loader *fileLoader) { func (t *parseTask) redirect(loader *fileLoader, fileName string) { t.redirectedParseTask = &parseTask{ - normalizedFilePath: tspath.NormalizePath(fileName), - libFile: t.libFile, - fromExternalLibrary: t.fromExternalLibrary, - includeReason: t.includeReason, + 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} @@ -138,22 +129,20 @@ func (t *parseTask) loadAutomaticTypeDirectives(loader *fileLoader) { } type resolvedRef struct { - fileName string - increaseDepth bool - elideOnDepth bool - isFromExternalLibrary bool - includeReason *FileIncludeReason + 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, - fromExternalLibrary: ref.isFromExternalLibrary, - includeReason: ref.includeReason, + normalizedFilePath: normalizedFilePath, + libFile: libFile, + increaseDepth: ref.increaseDepth, + elideOnDepth: ref.elideOnDepth, + includeReason: ref.includeReason, } t.subTasks = append(t.subTasks, subTask) } @@ -165,32 +154,25 @@ type filesParser struct { } type parseTaskData struct { - task *parseTask - mu sync.Mutex - isRoot bool - lowestDepth int - fromExternalLibrary bool + task *parseTask + mu sync.Mutex + lowestDepth int } func (w *filesParser) parse(loader *fileLoader, tasks []*parseTask) { - w.start(loader, tasks, 0, false) + w.start(loader, tasks, 0) w.wg.RunAndWait() } -func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, isFromExternalLibrary bool) { +func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int) { for i, task := range tasks { task.path = loader.toPath(task.normalizedFilePath) - taskIsFromExternalLibrary := isFromExternalLibrary || task.fromExternalLibrary data, loaded := w.tasksByFileName.LoadOrStore(task.FileName(), &parseTaskData{ task: task, - isRoot: task.isRoot(), lowestDepth: math.MaxInt, }) - // task = data.task if loaded { tasks[i].loadedTask = data.task - // Add in the loaded task's external-ness. - taskIsFromExternalLibrary = taskIsFromExternalLibrary || data.fromExternalLibrary } w.wg.Queue(func() { @@ -207,23 +189,20 @@ func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int, i startSubtasks = true } - if !data.isRoot && taskIsFromExternalLibrary && !data.fromExternalLibrary { - // If we're seeing this task now as an external library, - // reprocess its subtasks to ensure they are also marked as external. - data.fromExternalLibrary = true - startSubtasks = true - } - if task.elideOnDepth && currentDepth > w.maxDepth { return } if !data.task.loaded { data.task.load(loader) + if data.task.redirectedParseTask != nil { + // Always load redirected task + startSubtasks = true + } } if startSubtasks { - w.start(loader, data.task.subTasks, data.lowestDepth, data.fromExternalLibrary) + w.start(loader, data.task.subTasks, data.lowestDepth) } }) } @@ -342,7 +321,7 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { } importHelpersImportSpecifiers[path] = task.importHelpersImportSpecifier } - if data.fromExternalLibrary { + if data.lowestDepth > 0 { sourceFilesFoundSearchingNodeModules.Add(path) } } From 30b8730dd5fa1ad24aa5a16c277001c5d92e1459 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 25 Nov 2025 15:52:43 -0800 Subject: [PATCH 07/15] Make stub for casing checks --- internal/compiler/filesparser.go | 62 ++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/internal/compiler/filesparser.go b/internal/compiler/filesparser.go index e364c98795..17bc2d6bab 100644 --- a/internal/compiler/filesparser.go +++ b/internal/compiler/filesparser.go @@ -148,13 +148,14 @@ func (t *parseTask) addSubTask(ref resolvedRef, libFile *LibFile) { } type filesParser struct { - wg core.WorkGroup - tasksByFileName collections.SyncMap[string, *parseTaskData] - maxDepth int + wg core.WorkGroup + taskDataByPath collections.SyncMap[tspath.Path, *parseTaskData] + maxDepth int } type parseTaskData struct { - task *parseTask + // map of tasks by file casing + tasks map[string]*parseTask mu sync.Mutex lowestDepth int } @@ -167,18 +168,23 @@ func (w *filesParser) parse(loader *fileLoader, tasks []*parseTask) { func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int) { for i, task := range tasks { task.path = loader.toPath(task.normalizedFilePath) - data, loaded := w.tasksByFileName.LoadOrStore(task.FileName(), &parseTaskData{ - task: task, + data, loaded := w.taskDataByPath.LoadOrStore(task.path, &parseTaskData{ + tasks: map[string]*parseTask{task.normalizedFilePath: task}, lowestDepth: math.MaxInt, }) - if loaded { - tasks[i].loadedTask = data.task - } w.wg.Queue(func() { data.mu.Lock() defer data.mu.Unlock() + if loaded { + if existingTask, ok := data.tasks[task.normalizedFilePath]; ok { + tasks[i].loadedTask = existingTask + } else { + data.tasks[task.normalizedFilePath] = task + } + } + startSubtasks := false currentDepth := core.IfElse(task.increaseDepth, depth+1, depth) @@ -193,16 +199,18 @@ func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int) { return } - if !data.task.loaded { - data.task.load(loader) - if data.task.redirectedParseTask != nil { - // Always load redirected task - startSubtasks = true + for _, taskByFileName := range data.tasks { + loadSubTasks := startSubtasks + if !taskByFileName.loaded { + taskByFileName.load(loader) + if taskByFileName.redirectedParseTask != nil { + // Always load redirected task + loadSubTasks = true + } + } + if loadSubTasks { + w.start(loader, taskByFileName.subTasks, data.lowestDepth) } - } - - if startSubtasks { - w.start(loader, data.task.subTasks, data.lowestDepth) } }) } @@ -230,8 +238,8 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { var sourceFilesFoundSearchingNodeModules collections.Set[tspath.Path] libFilesMap := make(map[tspath.Path]*LibFile, libFileCount) - var collectFiles func(tasks []*parseTask, seen collections.Set[*parseTaskData]) - collectFiles = func(tasks []*parseTask, seen collections.Set[*parseTaskData]) { + var collectFiles func(tasks []*parseTask, seen map[*parseTaskData]string) + collectFiles = func(tasks []*parseTask, seen map[*parseTaskData]string) { for _, task := range tasks { // Exclude automatic type directive tasks from include reason processing, // as these are internal implementation details and should not contribute @@ -243,11 +251,19 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { } w.addIncludeReason(loader, task, includeReason) } - data, _ := w.tasksByFileName.Load(task.normalizedFilePath) + data, _ := w.taskDataByPath.Load(task.path) + if !task.loaded { + continue + } + // ensure we only walk each task once - if !task.loaded || !seen.AddIfAbsent(data) { + if _, ok := seen[data]; ok { + // !!! already seen verify casing continue + } else { + seen[data] = task.normalizedFilePath } + for _, trace := range task.typeResolutionsTrace { loader.opts.Host.Trace(trace.Message, trace.Args...) } @@ -327,7 +343,7 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { } } - collectFiles(loader.rootTasks, collections.Set[*parseTaskData]{}) + collectFiles(loader.rootTasks, make(map[*parseTaskData]string, totalFileCount)) loader.sortLibs(libFiles) allFiles := append(libFiles, files...) From 50f7178e1b89345ff73cc63c98e43546af54c374 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 25 Nov 2025 16:03:54 -0800 Subject: [PATCH 08/15] Actual file casing checks --- internal/compiler/filesparser.go | 45 +++++++++++++++------------ internal/compiler/includeprocessor.go | 27 ++++++++++++++++ internal/tspath/path.go | 6 ++++ 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/internal/compiler/filesparser.go b/internal/compiler/filesparser.go index 17bc2d6bab..ad470ff934 100644 --- a/internal/compiler/filesparser.go +++ b/internal/compiler/filesparser.go @@ -225,6 +225,13 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { 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 filesByNameIgnoreCase map[string]*ast.SourceFile + if loader.comparePathsOptions.UseCaseSensitiveFileNames { + filesByNameIgnoreCase = make(map[string]*ast.SourceFile, totalFileCount) + } + loader.includeProcessor.fileIncludeReasons = make(map[tspath.Path][]*FileIncludeReason, totalFileCount) var outputFileToProjectReferenceSource map[tspath.Path]string if !loader.opts.canUseProjectReferenceSource() { @@ -241,11 +248,11 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { 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 { - includeReason := task.includeReason if task.loadedTask != nil { task = task.loadedTask } @@ -257,8 +264,15 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { } // ensure we only walk each task once - if _, ok := seen[data]; ok { - // !!! already seen verify casing + 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 @@ -296,23 +310,14 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { continue } - // !!! sheetal todo porting file case errors - // if _, ok := filesByPath[path]; ok { - // Check if it differs only in drive letters its ok to ignore that error: - // const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); - // const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); - // if (checkedAbsolutePath !== inputAbsolutePath) { - // reportFileNamesDifferOnlyInCasingError(fileName, file, reason); - // } - // } else if loader.comparePathsOptions.UseCaseSensitiveFileNames { - // pathIgnoreCase := tspath.ToPath(file.FileName(), loader.comparePathsOptions.CurrentDirectory, false) - // // for case-sensitsive file systems check if we've already seen some file with similar filename ignoring case - // if _, ok := filesByNameIgnoreCase[pathIgnoreCase]; ok { - // reportFileNamesDifferOnlyInCasingError(fileName, existingFile, reason); - // } else { - // filesByNameIgnoreCase[pathIgnoreCase] = file - // } - // } + if filesByNameIgnoreCase != nil { + pathLowerCase := tspath.ToFileNameLowerCase(string(path)) + if existingFile, ok := filesByNameIgnoreCase[pathLowerCase]; ok { + loader.includeProcessor.addProcessingDiagnosticsForFileCasing(path, existingFile.FileName(), file.FileName(), includeReason) + } else { + filesByNameIgnoreCase[pathLowerCase] = file + } + } if task.libFile != nil { libFiles = append(libFiles, file) diff --git a/internal/compiler/includeprocessor.go b/internal/compiler/includeprocessor.go index 83691f45a1..a40534bbde 100644 --- a/internal/compiler/includeprocessor.go +++ b/internal/compiler/includeprocessor.go @@ -1,6 +1,7 @@ package compiler import ( + "slices" "sync" "github.com/microsoft/typescript-go/internal/ast" @@ -59,6 +60,32 @@ func (i *includeProcessor) addProcessingDiagnostic(d ...*processingDiagnostic) { i.processingDiagnostics = append(i.processingDiagnostics, d...) } +func (i *includeProcessor) addProcessingDiagnosticsForFileCasing(file tspath.Path, existingCasing string, currentCasing string, reason *FileIncludeReason) { + if !reason.isReferencedFile() && slices.ContainsFunc(i.fileIncludeReasons[file], func(r *FileIncludeReason) bool { + return r.isReferencedFile() + }) { + i.addProcessingDiagnostic(&processingDiagnostic{ + kind: processingDiagnosticKindExplainingFileInclude, + data: &includeExplainingDiagnostic{ + file: file, + diagnosticReason: reason, + message: diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + args: []any{existingCasing, currentCasing}, + }, + }) + } else { + i.addProcessingDiagnostic(&processingDiagnostic{ + kind: processingDiagnosticKindExplainingFileInclude, + data: &includeExplainingDiagnostic{ + file: file, + diagnosticReason: reason, + message: diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, + args: []any{currentCasing, existingCasing}, + }, + }) + } +} + func (i *includeProcessor) getReferenceLocation(r *FileIncludeReason, program *Program) *referenceFileLocation { if existing, ok := i.reasonToReferenceLocation.Load(r); ok { return existing diff --git a/internal/tspath/path.go b/internal/tspath/path.go index de09221507..26abe6778d 100644 --- a/internal/tspath/path.go +++ b/internal/tspath/path.go @@ -334,6 +334,12 @@ func GetNormalizedPathComponents(path string, currentDirectory string) []string return reducePathComponents(GetPathComponents(path, currentDirectory)) } +func GetNormalizedAbsolutePathWithoutRoot(fileName string, currentDirectory string) string { + absolutePath := GetNormalizedAbsolutePath(fileName, currentDirectory) + rootLength := GetRootLength(absolutePath) + return absolutePath[rootLength:] +} + func GetNormalizedAbsolutePath(fileName string, currentDirectory string) string { rootLength := GetRootLength(fileName) if rootLength == 0 && currentDirectory != "" { From 73aa765cde7b758892346b235f216bc81b6cf75a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 25 Nov 2025 16:33:48 -0800 Subject: [PATCH 09/15] Tests TODO: some formatting issue with reason repeating --- internal/execute/tsctests/tsc_test.go | 66 ++++++ ...m-multiple-places-with-different-casing.js | 211 ++++++++++++++++++ ...ative-and-non-relative-file-resolutions.js | 83 +++++++ .../with-type-ref-from-file.js | 58 +++++ 4 files changed, 418 insertions(+) create mode 100644 testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js create mode 100644 testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js create mode 100644 testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-type-ref-from-file.js diff --git a/internal/execute/tsctests/tsc_test.go b/internal/execute/tsctests/tsc_test.go index e618ce7829..e0a2263bc6 100644 --- a/internal/execute/tsctests/tsc_test.go +++ b/internal/execute/tsctests/tsc_test.go @@ -981,6 +981,72 @@ func TestTscExtends(t *testing.T) { } } +func TestForceConsistentCasingInFileNames(t *testing.T) { + t.Parallel() + testCases := []*tscInput{ + { + subScenario: "with relative and non relative file resolutions", + files: FileMap{ + "/user/username/projects/myproject/src/struct.d.ts": stringtestutil.Dedent(` + import * as xs1 from "fp-ts/lib/Struct"; + import * as xs2 from "fp-ts/lib/struct"; + import * as xs3 from "./Struct"; + import * as xs4 from "./struct"; + `), + "/user/username/projects/myproject/node_modules/fp-ts/lib/struct.d.ts": `export function foo(): void`, + }, + cwd: "/user/username/projects/myproject", + commandLineArgs: []string{"/user/username/projects/myproject/src/struct.d.ts", "--forceConsistentCasingInFileNames", "--explainFiles"}, + ignoreCase: true, + }, + { + subScenario: "when file is included from multiple places with different casing", + files: FileMap{ + "/home/src/projects/project/src/struct.d.ts": stringtestutil.Dedent(` + import * as xs1 from "fp-ts/lib/Struct"; + import * as xs2 from "fp-ts/lib/struct"; + import * as xs3 from "./Struct"; + import * as xs4 from "./struct"; + `), + "/home/src/projects/project/src/anotherFile.ts": stringtestutil.Dedent(` + import * as xs1 from "fp-ts/lib/Struct"; + import * as xs2 from "fp-ts/lib/struct"; + import * as xs3 from "./Struct"; + import * as xs4 from "./struct"; + `), + "/home/src/projects/project/src/oneMore.ts": stringtestutil.Dedent(` + import * as xs1 from "fp-ts/lib/Struct"; + import * as xs2 from "fp-ts/lib/struct"; + import * as xs3 from "./Struct"; + import * as xs4 from "./struct"; + `), + "/home/src/projects/project/tsconfig.json": `{}`, + "/home/src/projects/project/node_modules/fp-ts/lib/struct.d.ts": `export function foo(): void`, + }, + cwd: "/home/src/projects/project", + commandLineArgs: []string{"--explainFiles"}, + ignoreCase: true, + }, + { + subScenario: "with type ref from file", + files: FileMap{ + "/user/username/projects/myproject/src/fileOne.d.ts": `declare class c { }`, + "/user/username/projects/myproject/src/file2.d.ts": stringtestutil.Dedent(` + /// + declare const y: c; + `), + "/user/username/projects/myproject/tsconfig.json": "{ }", + }, + cwd: "/user/username/projects/myproject", + commandLineArgs: []string{"-p", "/user/username/projects/myproject", "--explainFiles", "--traceResolution"}, + ignoreCase: true, + }, + } + for _, test := range testCases { + test.run(t, "forceConsistentCasingInFileNames") + } +} + func TestTscIgnoreConfig(t *testing.T) { t.Parallel() filesWithoutConfig := func() FileMap { diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js new file mode 100644 index 0000000000..d35c87fc41 --- /dev/null +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js @@ -0,0 +1,211 @@ +currentDirectory::/home/src/projects/project +useCaseSensitiveFileNames::false +Input:: +//// [/home/src/projects/project/node_modules/fp-ts/lib/struct.d.ts] *new* +export function foo(): void +//// [/home/src/projects/project/src/anotherFile.ts] *new* +import * as xs1 from "fp-ts/lib/Struct"; +import * as xs2 from "fp-ts/lib/struct"; +import * as xs3 from "./Struct"; +import * as xs4 from "./struct"; +//// [/home/src/projects/project/src/oneMore.ts] *new* +import * as xs1 from "fp-ts/lib/Struct"; +import * as xs2 from "fp-ts/lib/struct"; +import * as xs3 from "./Struct"; +import * as xs4 from "./struct"; +//// [/home/src/projects/project/src/struct.d.ts] *new* +import * as xs1 from "fp-ts/lib/Struct"; +import * as xs2 from "fp-ts/lib/struct"; +import * as xs3 from "./Struct"; +import * as xs4 from "./struct"; +//// [/home/src/projects/project/tsconfig.json] *new* +{} + +tsgo --explainFiles +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +src/anotherFile.ts:2:22 - error TS1149: File name '/home/src/projects/project/node_modules/fp-ts/lib/struct.d.ts' differs from already included file name '/home/src/projects/project/node_modules/fp-ts/lib/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/oneMore.ts' + +2 import * as xs2 from "fp-ts/lib/struct"; +   ~~~~~~~~~~~~~~~~~~ + + src/anotherFile.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/anotherFile.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/oneMore.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/oneMore.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + +src/anotherFile.ts:3:22 - error TS1261: Already included file name '/home/src/projects/project/src/Struct.d.ts' differs from file name '/home/src/projects/project/src/struct.d.ts' only in casing. + The file is in the program because: + Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "./struct" from file '/home/src/projects/project/src/oneMore.ts' + Matched by default include pattern '**/*' + +3 import * as xs3 from "./Struct"; +   ~~~~~~~~~~ + + src/anotherFile.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + +src/anotherFile.ts:4:22 - error TS1149: File name '/home/src/projects/project/src/struct.d.ts' differs from already included file name '/home/src/projects/project/src/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "./struct" from file '/home/src/projects/project/src/oneMore.ts' + Matched by default include pattern '**/*' + +4 import * as xs4 from "./struct"; +   ~~~~~~~~~~ + + src/anotherFile.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/anotherFile.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + +src/oneMore.ts:2:22 - error TS1149: File name '/home/src/projects/project/node_modules/fp-ts/lib/struct.d.ts' differs from already included file name '/home/src/projects/project/node_modules/fp-ts/lib/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/oneMore.ts' + +2 import * as xs2 from "fp-ts/lib/struct"; +   ~~~~~~~~~~~~~~~~~~ + + src/anotherFile.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/anotherFile.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/oneMore.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/oneMore.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + +src/oneMore.ts:4:22 - error TS1149: File name '/home/src/projects/project/src/struct.d.ts' differs from already included file name '/home/src/projects/project/src/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "./struct" from file '/home/src/projects/project/src/oneMore.ts' + Matched by default include pattern '**/*' + +4 import * as xs4 from "./struct"; +   ~~~~~~~~~~ + + src/anotherFile.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/anotherFile.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + +../../tslibs/TS/Lib/lib.d.ts + Default library for target 'ES5' +node_modules/fp-ts/lib/Struct.d.ts + Imported via "fp-ts/lib/Struct" from file 'src/anotherFile.ts' + Imported via "fp-ts/lib/struct" from file 'src/anotherFile.ts' + Imported via "fp-ts/lib/Struct" from file 'src/oneMore.ts' + Imported via "fp-ts/lib/struct" from file 'src/oneMore.ts' +src/Struct.d.ts + Imported via "./Struct" from file 'src/anotherFile.ts' + Imported via "./struct" from file 'src/anotherFile.ts' + Imported via "./Struct" from file 'src/oneMore.ts' + Imported via "./struct" from file 'src/oneMore.ts' + Matched by default include pattern '**/*' +src/anotherFile.ts + Matched by default include pattern '**/*' +src/oneMore.ts + Matched by default include pattern '**/*' + +Found 5 errors in 2 files. + +Errors Files + 3 src/anotherFile.ts:2 + 2 src/oneMore.ts:2 + +//// [/home/src/projects/project/src/anotherFile.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); + +//// [/home/src/projects/project/src/oneMore.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); + +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js new file mode 100644 index 0000000000..e39a374e3a --- /dev/null +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js @@ -0,0 +1,83 @@ +currentDirectory::/user/username/projects/myproject +useCaseSensitiveFileNames::false +Input:: +//// [/user/username/projects/myproject/node_modules/fp-ts/lib/struct.d.ts] *new* +export function foo(): void +//// [/user/username/projects/myproject/src/struct.d.ts] *new* +import * as xs1 from "fp-ts/lib/Struct"; +import * as xs2 from "fp-ts/lib/struct"; +import * as xs3 from "./Struct"; +import * as xs4 from "./struct"; + +tsgo /user/username/projects/myproject/src/struct.d.ts --forceConsistentCasingInFileNames --explainFiles +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +src/struct.d.ts:2:22 - error TS1149: File name '/user/username/projects/myproject/node_modules/fp-ts/lib/struct.d.ts' differs from already included file name '/user/username/projects/myproject/node_modules/fp-ts/lib/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "fp-ts/lib/Struct" from file '/user/username/projects/myproject/src/struct.d.ts' + Imported via "fp-ts/lib/struct" from file '/user/username/projects/myproject/src/struct.d.ts' + +2 import * as xs2 from "fp-ts/lib/struct"; +   ~~~~~~~~~~~~~~~~~~ + + src/struct.d.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/struct.d.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + +src/struct.d.ts:3:22 - error TS1149: File name '/user/username/projects/myproject/src/Struct.d.ts' differs from already included file name '/user/username/projects/myproject/src/struct.d.ts' only in casing. + The file is in the program because: + Root file specified for compilation + Imported via "./Struct" from file '/user/username/projects/myproject/src/struct.d.ts' + Imported via "./struct" from file '/user/username/projects/myproject/src/struct.d.ts' + +3 import * as xs3 from "./Struct"; +   ~~~~~~~~~~ + + src/struct.d.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/struct.d.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + +../../../../home/src/tslibs/TS/Lib/lib.d.ts + Default library for target 'ES5' +node_modules/fp-ts/lib/Struct.d.ts + Imported via "fp-ts/lib/Struct" from file 'src/struct.d.ts' + Imported via "fp-ts/lib/struct" from file 'src/struct.d.ts' +src/struct.d.ts + Root file specified for compilation + Imported via "./Struct" from file 'src/struct.d.ts' + Imported via "./struct" from file 'src/struct.d.ts' + +Found 2 errors in the same file, starting at: src/struct.d.ts:2 + +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-type-ref-from-file.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-type-ref-from-file.js new file mode 100644 index 0000000000..2f55514b08 --- /dev/null +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-type-ref-from-file.js @@ -0,0 +1,58 @@ +currentDirectory::/user/username/projects/myproject +useCaseSensitiveFileNames::false +Input:: +//// [/user/username/projects/myproject/src/file2.d.ts] *new* +/// +declare const y: c; +//// [/user/username/projects/myproject/src/fileOne.d.ts] *new* +declare class c { } +//// [/user/username/projects/myproject/tsconfig.json] *new* +{ } + +tsgo -p /user/username/projects/myproject --explainFiles --traceResolution +ExitStatus:: Success +Output:: +======== Resolving type reference directive './fileOne.d.ts', containing file '/user/username/projects/myproject/src/file2.d.ts', root directory '/user/username/projects/myproject/node_modules/@types,/user/username/projects/node_modules/@types,/user/username/node_modules/@types,/user/node_modules/@types,/node_modules/@types'. ======== +Resolving with primary search path '/user/username/projects/myproject/node_modules/@types, /user/username/projects/node_modules/@types, /user/username/node_modules/@types, /user/node_modules/@types, /node_modules/@types'. +Directory '/user/username/projects/myproject/node_modules/@types' does not exist, skipping all lookups in it. +Directory '/user/username/projects/node_modules/@types' does not exist, skipping all lookups in it. +Directory '/user/username/node_modules/@types' does not exist, skipping all lookups in it. +Directory '/user/node_modules/@types' does not exist, skipping all lookups in it. +Directory '/node_modules/@types' does not exist, skipping all lookups in it. +Looking up in 'node_modules' folder, initial location '/user/username/projects/myproject/src'. +Loading module as file / folder, candidate module location '/user/username/projects/myproject/src/fileOne.d.ts', target file types: Declaration. +File name '/user/username/projects/myproject/src/fileOne.d.ts' has a '.d.ts' extension - stripping it. +File '/user/username/projects/myproject/src/fileOne.d.ts' exists - use it as a name resolution result. +Resolving real path for '/user/username/projects/myproject/src/fileOne.d.ts', result '/user/username/projects/myproject/src/fileOne.d.ts'. +======== Type reference directive './fileOne.d.ts' was successfully resolved to '/user/username/projects/myproject/src/fileOne.d.ts', primary: false. ======== +../../../../home/src/tslibs/TS/Lib/lib.d.ts + Default library for target 'ES5' +src/fileOne.d.ts + Type library referenced via './fileOne.d.ts' from file 'src/file2.d.ts' + Matched by default include pattern '**/*' +src/file2.d.ts + Matched by default include pattern '**/*' +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + From b288016a04e085cb467b41c319ccf488915f34d8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Nov 2025 13:04:43 -0800 Subject: [PATCH 10/15] Fix related info when related info is added for reason where the diagnostics is reported --- internal/compiler/processingDiagnostic.go | 2 +- ...from-multiple-places-with-different-casing.js | 16 ---------------- ...relative-and-non-relative-file-resolutions.js | 8 -------- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/internal/compiler/processingDiagnostic.go b/internal/compiler/processingDiagnostic.go index 20e5c8d18c..a2f7d89e90 100644 --- a/internal/compiler/processingDiagnostic.go +++ b/internal/compiler/processingDiagnostic.go @@ -79,7 +79,7 @@ func (d *processingDiagnostic) createDiagnosticExplainingFile(program *Program) processRelatedInfo := func(includeReason *FileIncludeReason) { if preferredLocation == nil && includeReason.isReferencedFile() && !program.includeProcessor.getReferenceLocation(includeReason, program).isSynthetic { preferredLocation = includeReason - } else { + } else if preferredLocation != includeReason { info := program.includeProcessor.getRelatedInfo(includeReason, program) if info != nil { relatedInfo = append(relatedInfo, info) diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js index d35c87fc41..3613e8cbe0 100644 --- a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js @@ -38,10 +38,6 @@ Output:: 1 import * as xs1 from "fp-ts/lib/Struct";    ~~~~~~~~~~~~~~~~~~ - src/anotherFile.ts:2:22 - File is included via import here. - 2 import * as xs2 from "fp-ts/lib/struct"; -    ~~~~~~~~~~~~~~~~~~ - src/oneMore.ts:1:22 - File is included via import here. 1 import * as xs1 from "fp-ts/lib/Struct";    ~~~~~~~~~~~~~~~~~~ @@ -88,10 +84,6 @@ Output:: 3 import * as xs3 from "./Struct";    ~~~~~~~~~~ - src/anotherFile.ts:4:22 - File is included via import here. - 4 import * as xs4 from "./struct"; -    ~~~~~~~~~~ - src/oneMore.ts:3:22 - File is included via import here. 3 import * as xs3 from "./Struct";    ~~~~~~~~~~ @@ -122,10 +114,6 @@ Output:: 1 import * as xs1 from "fp-ts/lib/Struct";    ~~~~~~~~~~~~~~~~~~ - src/oneMore.ts:2:22 - File is included via import here. - 2 import * as xs2 from "fp-ts/lib/struct"; -    ~~~~~~~~~~~~~~~~~~ - src/oneMore.ts:4:22 - error TS1149: File name '/home/src/projects/project/src/struct.d.ts' differs from already included file name '/home/src/projects/project/src/Struct.d.ts' only in casing. The file is in the program because: Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' @@ -149,10 +137,6 @@ Output:: 3 import * as xs3 from "./Struct";    ~~~~~~~~~~ - src/oneMore.ts:4:22 - File is included via import here. - 4 import * as xs4 from "./struct"; -    ~~~~~~~~~~ - ../../tslibs/TS/Lib/lib.d.ts Default library for target 'ES5' node_modules/fp-ts/lib/Struct.d.ts diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js index e39a374e3a..19ce089f68 100644 --- a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-relative-and-non-relative-file-resolutions.js @@ -24,10 +24,6 @@ Output:: 1 import * as xs1 from "fp-ts/lib/Struct";    ~~~~~~~~~~~~~~~~~~ - src/struct.d.ts:2:22 - File is included via import here. - 2 import * as xs2 from "fp-ts/lib/struct"; -    ~~~~~~~~~~~~~~~~~~ - src/struct.d.ts:3:22 - error TS1149: File name '/user/username/projects/myproject/src/Struct.d.ts' differs from already included file name '/user/username/projects/myproject/src/struct.d.ts' only in casing. The file is in the program because: Root file specified for compilation @@ -37,10 +33,6 @@ Output:: 3 import * as xs3 from "./Struct";    ~~~~~~~~~~ - src/struct.d.ts:3:22 - File is included via import here. - 3 import * as xs3 from "./Struct"; -    ~~~~~~~~~~ - src/struct.d.ts:4:22 - File is included via import here. 4 import * as xs4 from "./struct";    ~~~~~~~~~~ From 5743d7aaa394b561073e8b916c057f6504f74f19 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Nov 2025 14:47:51 -0800 Subject: [PATCH 11/15] Ensure the subtasks for file with different casing are started if we already startedSubTasks with earlier seen different casing --- internal/compiler/filesparser.go | 18 ++- ...m-multiple-places-with-different-casing.js | 122 +++++++++++++++++- 2 files changed, 133 insertions(+), 7 deletions(-) diff --git a/internal/compiler/filesparser.go b/internal/compiler/filesparser.go index ad470ff934..f324308669 100644 --- a/internal/compiler/filesparser.go +++ b/internal/compiler/filesparser.go @@ -21,6 +21,7 @@ type parseTask struct { redirectedParseTask *parseTask subTasks []*parseTask loaded bool + startedSubTasks bool isForAutomaticTypeDirective bool includeReason *FileIncludeReason @@ -155,9 +156,10 @@ type filesParser struct { type parseTaskData struct { // map of tasks by file casing - tasks map[string]*parseTask - mu sync.Mutex - lowestDepth int + tasks map[string]*parseTask + mu sync.Mutex + lowestDepth int + startedSubTasks bool } func (w *filesParser) parse(loader *fileLoader, tasks []*parseTask) { @@ -177,22 +179,24 @@ func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int) { 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 } } - startSubtasks := false - 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 { @@ -206,9 +210,11 @@ func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int) { if taskByFileName.redirectedParseTask != nil { // Always load redirected task loadSubTasks = true + data.startedSubTasks = true } } - if loadSubTasks { + if !taskByFileName.startedSubTasks && loadSubTasks { + taskByFileName.startedSubTasks = true w.start(loader, taskByFileName.subTasks, data.lowestDepth) } } diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js index 3613e8cbe0..2c5f0bfa46 100644 --- a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/when-file-is-included-from-multiple-places-with-different-casing.js @@ -24,10 +24,77 @@ import * as xs4 from "./struct"; tsgo --explainFiles ExitStatus:: DiagnosticsPresent_OutputsGenerated Output:: +src/Struct.d.ts:2:22 - error TS1149: File name '/home/src/projects/project/node_modules/fp-ts/lib/struct.d.ts' differs from already included file name '/home/src/projects/project/node_modules/fp-ts/lib/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/oneMore.ts' + +2 import * as xs2 from "fp-ts/lib/struct"; +   ~~~~~~~~~~~~~~~~~~ + + src/anotherFile.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/anotherFile.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/Struct.d.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/oneMore.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/oneMore.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + +src/Struct.d.ts:4:22 - error TS1149: File name '/home/src/projects/project/src/struct.d.ts' differs from already included file name '/home/src/projects/project/src/Struct.d.ts' only in casing. + The file is in the program because: + Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/oneMore.ts' + Imported via "./struct" from file '/home/src/projects/project/src/oneMore.ts' + Matched by default include pattern '**/*' + +4 import * as xs4 from "./struct"; +   ~~~~~~~~~~ + + src/anotherFile.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/Struct.d.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/anotherFile.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/oneMore.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + src/anotherFile.ts:2:22 - error TS1149: File name '/home/src/projects/project/node_modules/fp-ts/lib/struct.d.ts' differs from already included file name '/home/src/projects/project/node_modules/fp-ts/lib/Struct.d.ts' only in casing. The file is in the program because: Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/anotherFile.ts' Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/Struct.d.ts' Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/oneMore.ts' Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/oneMore.ts' @@ -38,6 +105,14 @@ Output:: 1 import * as xs1 from "fp-ts/lib/Struct";    ~~~~~~~~~~~~~~~~~~ + src/Struct.d.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/Struct.d.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + src/oneMore.ts:1:22 - File is included via import here. 1 import * as xs1 from "fp-ts/lib/Struct";    ~~~~~~~~~~~~~~~~~~ @@ -49,6 +124,8 @@ Output:: src/anotherFile.ts:3:22 - error TS1261: Already included file name '/home/src/projects/project/src/Struct.d.ts' differs from file name '/home/src/projects/project/src/struct.d.ts' only in casing. The file is in the program because: Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/Struct.d.ts' Imported via "./struct" from file '/home/src/projects/project/src/anotherFile.ts' Imported via "./Struct" from file '/home/src/projects/project/src/oneMore.ts' Imported via "./struct" from file '/home/src/projects/project/src/oneMore.ts' @@ -57,6 +134,14 @@ Output:: 3 import * as xs3 from "./Struct";    ~~~~~~~~~~ + src/Struct.d.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/Struct.d.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + src/anotherFile.ts:4:22 - File is included via import here. 4 import * as xs4 from "./struct";    ~~~~~~~~~~ @@ -72,6 +157,8 @@ Output:: src/anotherFile.ts:4:22 - error TS1149: File name '/home/src/projects/project/src/struct.d.ts' differs from already included file name '/home/src/projects/project/src/Struct.d.ts' only in casing. The file is in the program because: Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/Struct.d.ts' Imported via "./struct" from file '/home/src/projects/project/src/anotherFile.ts' Imported via "./Struct" from file '/home/src/projects/project/src/oneMore.ts' Imported via "./struct" from file '/home/src/projects/project/src/oneMore.ts' @@ -84,6 +171,14 @@ Output:: 3 import * as xs3 from "./Struct";    ~~~~~~~~~~ + src/Struct.d.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/Struct.d.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + src/oneMore.ts:3:22 - File is included via import here. 3 import * as xs3 from "./Struct";    ~~~~~~~~~~ @@ -96,6 +191,8 @@ Output:: The file is in the program because: Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/anotherFile.ts' Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/Struct.d.ts' Imported via "fp-ts/lib/Struct" from file '/home/src/projects/project/src/oneMore.ts' Imported via "fp-ts/lib/struct" from file '/home/src/projects/project/src/oneMore.ts' @@ -110,6 +207,14 @@ Output:: 2 import * as xs2 from "fp-ts/lib/struct";    ~~~~~~~~~~~~~~~~~~ + src/Struct.d.ts:1:22 - File is included via import here. + 1 import * as xs1 from "fp-ts/lib/Struct"; +    ~~~~~~~~~~~~~~~~~~ + + src/Struct.d.ts:2:22 - File is included via import here. + 2 import * as xs2 from "fp-ts/lib/struct"; +    ~~~~~~~~~~~~~~~~~~ + src/oneMore.ts:1:22 - File is included via import here. 1 import * as xs1 from "fp-ts/lib/Struct";    ~~~~~~~~~~~~~~~~~~ @@ -117,6 +222,8 @@ Output:: src/oneMore.ts:4:22 - error TS1149: File name '/home/src/projects/project/src/struct.d.ts' differs from already included file name '/home/src/projects/project/src/Struct.d.ts' only in casing. The file is in the program because: Imported via "./Struct" from file '/home/src/projects/project/src/anotherFile.ts' + Imported via "./Struct" from file '/home/src/projects/project/src/Struct.d.ts' + Imported via "./struct" from file '/home/src/projects/project/src/Struct.d.ts' Imported via "./struct" from file '/home/src/projects/project/src/anotherFile.ts' Imported via "./Struct" from file '/home/src/projects/project/src/oneMore.ts' Imported via "./struct" from file '/home/src/projects/project/src/oneMore.ts' @@ -129,6 +236,14 @@ Output:: 3 import * as xs3 from "./Struct";    ~~~~~~~~~~ + src/Struct.d.ts:3:22 - File is included via import here. + 3 import * as xs3 from "./Struct"; +    ~~~~~~~~~~ + + src/Struct.d.ts:4:22 - File is included via import here. + 4 import * as xs4 from "./struct"; +    ~~~~~~~~~~ + src/anotherFile.ts:4:22 - File is included via import here. 4 import * as xs4 from "./struct";    ~~~~~~~~~~ @@ -142,10 +257,14 @@ Output:: node_modules/fp-ts/lib/Struct.d.ts Imported via "fp-ts/lib/Struct" from file 'src/anotherFile.ts' Imported via "fp-ts/lib/struct" from file 'src/anotherFile.ts' + Imported via "fp-ts/lib/Struct" from file 'src/Struct.d.ts' + Imported via "fp-ts/lib/struct" from file 'src/Struct.d.ts' Imported via "fp-ts/lib/Struct" from file 'src/oneMore.ts' Imported via "fp-ts/lib/struct" from file 'src/oneMore.ts' src/Struct.d.ts Imported via "./Struct" from file 'src/anotherFile.ts' + Imported via "./Struct" from file 'src/Struct.d.ts' + Imported via "./struct" from file 'src/Struct.d.ts' Imported via "./struct" from file 'src/anotherFile.ts' Imported via "./Struct" from file 'src/oneMore.ts' Imported via "./struct" from file 'src/oneMore.ts' @@ -155,9 +274,10 @@ src/anotherFile.ts src/oneMore.ts Matched by default include pattern '**/*' -Found 5 errors in 2 files. +Found 7 errors in 3 files. Errors Files + 2 src/Struct.d.ts:2 3 src/anotherFile.ts:2 2 src/oneMore.ts:2 From d09ac1f75f9d916f2d1009278202f2f4328fc99a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Nov 2025 15:00:56 -0800 Subject: [PATCH 12/15] Test for GetNormalizedAbsolutePathWithoutRoot --- internal/tspath/path_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/tspath/path_test.go b/internal/tspath/path_test.go index 85d7829de6..711030a1cd 100644 --- a/internal/tspath/path_test.go +++ b/internal/tspath/path_test.go @@ -417,6 +417,14 @@ func TestGetNormalizedAbsolutePath(t *testing.T) { assert.Equal(t, GetNormalizedAbsolutePath("\\\\a\\b\\\\c", ""), "//a/b/c") } +func TestGetNormalizedAbsolutePathWithoutRoot(t *testing.T) { + t.Parallel() + + assert.Equal(t, GetNormalizedAbsolutePathWithoutRoot("/a/b/c.txt", "/a/b"), "a/b/c.txt") + assert.Equal(t, GetNormalizedAbsolutePathWithoutRoot("c:/work/hello.txt", "c:/work"), "work/hello.txt") + assert.Equal(t, GetNormalizedAbsolutePathWithoutRoot("c:/work/hello.txt", "d:/worspaces"), "work/hello.txt") +} + var getNormalizedAbsolutePathTests = map[string][][]string{ "non-normalized inputs": { {"/.", ""}, From 06b18b354c07ad2c4c09d5f2819b87939a7b94a0 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Nov 2025 15:42:51 -0800 Subject: [PATCH 13/15] More tests --- internal/execute/tsctests/tsc_test.go | 21 ++++++ ...ist-on-disk-that-differs-only-in-casing.js | 67 +++++++++++++++++++ .../with-triple-slash-ref-from-file.js | 53 +++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/two-files-exist-on-disk-that-differs-only-in-casing.js create mode 100644 testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-triple-slash-ref-from-file.js diff --git a/internal/execute/tsctests/tsc_test.go b/internal/execute/tsctests/tsc_test.go index e0a2263bc6..52586c4d6d 100644 --- a/internal/execute/tsctests/tsc_test.go +++ b/internal/execute/tsctests/tsc_test.go @@ -1041,6 +1041,27 @@ func TestForceConsistentCasingInFileNames(t *testing.T) { commandLineArgs: []string{"-p", "/user/username/projects/myproject", "--explainFiles", "--traceResolution"}, ignoreCase: true, }, + { + subScenario: "with triple slash ref from file", + files: FileMap{ + "/home/src/workspaces/project/src/c.ts": `/// `, + "/home/src/workspaces/project/src/d.ts": `declare class c { }`, + "/home/src/workspaces/project/tsconfig.json": "{ }", + }, + ignoreCase: true, + }, + { + subScenario: "two files exist on disk that differs only in casing", + files: FileMap{ + "/home/src/workspaces/project/c.ts": `import {x} from "./D"`, + "/home/src/workspaces/project/D.ts": `export const x = 10;`, + "/home/src/workspaces/project/d.ts": `export const y = 20;`, + "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` + { + "files": ["c.ts", "d.ts"] + }`), + }, + }, } for _, test := range testCases { test.run(t, "forceConsistentCasingInFileNames") diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/two-files-exist-on-disk-that-differs-only-in-casing.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/two-files-exist-on-disk-that-differs-only-in-casing.js new file mode 100644 index 0000000000..eb8e5043ee --- /dev/null +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/two-files-exist-on-disk-that-differs-only-in-casing.js @@ -0,0 +1,67 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/D.ts] *new* +export const x = 10; +//// [/home/src/workspaces/project/c.ts] *new* +import {x} from "./D" +//// [/home/src/workspaces/project/d.ts] *new* +export const y = 20; +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + "files": ["c.ts", "d.ts"] +} + +tsgo +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +error TS1149: File name '/home/src/workspaces/project/d.ts' differs from already included file name '/home/src/workspaces/project/D.ts' only in casing. + The file is in the program because: + Part of 'files' list in tsconfig.json + tsconfig.json:2:23 - File is matched by 'files' list specified here. + 2 "files": ["c.ts", "d.ts"] +    ~~~~~~ + + +Found 1 error. + +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; +//// [/home/src/workspaces/project/D.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.x = void 0; +exports.x = 10; + +//// [/home/src/workspaces/project/c.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); + +//// [/home/src/workspaces/project/d.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.y = void 0; +exports.y = 20; + + diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-triple-slash-ref-from-file.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-triple-slash-ref-from-file.js new file mode 100644 index 0000000000..1d16927567 --- /dev/null +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/with-triple-slash-ref-from-file.js @@ -0,0 +1,53 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::false +Input:: +//// [/home/src/workspaces/project/src/c.ts] *new* +/// +//// [/home/src/workspaces/project/src/d.ts] *new* +declare class c { } +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ } + +tsgo +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +src/c.ts:1:22 - error TS1261: Already included file name '/home/src/workspaces/project/src/D.ts' differs from file name '/home/src/workspaces/project/src/d.ts' only in casing. + The file is in the program because: + Referenced via './D.ts' from file '/home/src/workspaces/project/src/c.ts' + Matched by default include pattern '**/*' + +1 /// +   ~~~~~~ + + +Found 1 error in src/c.ts:1 + +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; +//// [/home/src/workspaces/project/src/D.js] *new* + +//// [/home/src/workspaces/project/src/c.js] *new* +/// + + From 6742e55c7ce1d8bc00910945a3372f20828ca52d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Nov 2025 15:56:09 -0800 Subject: [PATCH 14/15] Fix error reporting to match with strada for case sensitive file system --- internal/compiler/filesparser.go | 22 +++++++++---------- ...ist-on-disk-that-differs-only-in-casing.js | 9 ++++++-- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/internal/compiler/filesparser.go b/internal/compiler/filesparser.go index f324308669..0aa19b024a 100644 --- a/internal/compiler/filesparser.go +++ b/internal/compiler/filesparser.go @@ -233,9 +233,9 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { 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 filesByNameIgnoreCase map[string]*ast.SourceFile + var tasksSeenByNameIgnoreCase map[string]*parseTask if loader.comparePathsOptions.UseCaseSensitiveFileNames { - filesByNameIgnoreCase = make(map[string]*ast.SourceFile, totalFileCount) + tasksSeenByNameIgnoreCase = make(map[string]*parseTask, totalFileCount) } loader.includeProcessor.fileIncludeReasons = make(map[tspath.Path][]*FileIncludeReason, totalFileCount) @@ -284,6 +284,15 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { 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...) } @@ -316,15 +325,6 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { continue } - if filesByNameIgnoreCase != nil { - pathLowerCase := tspath.ToFileNameLowerCase(string(path)) - if existingFile, ok := filesByNameIgnoreCase[pathLowerCase]; ok { - loader.includeProcessor.addProcessingDiagnosticsForFileCasing(path, existingFile.FileName(), file.FileName(), includeReason) - } else { - filesByNameIgnoreCase[pathLowerCase] = file - } - } - if task.libFile != nil { libFiles = append(libFiles, file) libFilesMap[path] = task.libFile diff --git a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/two-files-exist-on-disk-that-differs-only-in-casing.js b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/two-files-exist-on-disk-that-differs-only-in-casing.js index eb8e5043ee..cf19f856c1 100644 --- a/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/two-files-exist-on-disk-that-differs-only-in-casing.js +++ b/testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/two-files-exist-on-disk-that-differs-only-in-casing.js @@ -15,15 +15,20 @@ export const y = 20; tsgo ExitStatus:: DiagnosticsPresent_OutputsGenerated Output:: -error TS1149: File name '/home/src/workspaces/project/d.ts' differs from already included file name '/home/src/workspaces/project/D.ts' only in casing. +c.ts:1:17 - error TS1261: Already included file name '/home/src/workspaces/project/D.ts' differs from file name '/home/src/workspaces/project/d.ts' only in casing. The file is in the program because: + Imported via "./D" from file '/home/src/workspaces/project/c.ts' Part of 'files' list in tsconfig.json + +1 import {x} from "./D" +   ~~~~~ + tsconfig.json:2:23 - File is matched by 'files' list specified here. 2 "files": ["c.ts", "d.ts"]    ~~~~~~ -Found 1 error. +Found 1 error in c.ts:1 //// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* /// From 7ef9a158069ae583bc1598f58b1e6c8b6cedf9c6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Nov 2025 15:58:52 -0800 Subject: [PATCH 15/15] Revert duplicate file check when writing buildInfo --- .../incremental/snapshottobuildinfo.go | 10 +- internal/execute/tsctests/tsc_test.go | 48 +++++ ...mental-with-case-insensitive-file-names.js | 196 ++++++++++++++++++ 3 files changed, 245 insertions(+), 9 deletions(-) create mode 100644 testdata/baselines/reference/tsc/incremental/Compile-incremental-with-case-insensitive-file-names.js diff --git a/internal/execute/incremental/snapshottobuildinfo.go b/internal/execute/incremental/snapshottobuildinfo.go index fc83153362..528097d564 100644 --- a/internal/execute/incremental/snapshottobuildinfo.go +++ b/internal/execute/incremental/snapshottobuildinfo.go @@ -197,7 +197,7 @@ func (t *toBuildInfo) collectRootFiles() { } func (t *toBuildInfo) setFileInfoAndEmitSignatures() { - t.buildInfo.FileInfos = core.MapNonNil(t.program.GetSourceFiles(), func(file *ast.SourceFile) *BuildInfoFileInfo { + t.buildInfo.FileInfos = core.Map(t.program.GetSourceFiles(), func(file *ast.SourceFile) *BuildInfoFileInfo { info, _ := t.snapshot.fileInfos.Load(file.Path()) fileId := t.toFileId(file.Path()) // tryAddRoot(key, fileId); @@ -206,11 +206,6 @@ func (t *toBuildInfo) setFileInfoAndEmitSignatures() { panic(fmt.Sprintf("File name at index %d does not match expected relative path or libName: %s != %s", fileId-1, t.buildInfo.FileNames[fileId-1], t.relativeToBuildInfo(string(file.Path())))) } } - if int(fileId) != len(t.buildInfo.FileNames) { - // Duplicate - for now ignore - return nil - } - if t.snapshot.options.Composite.IsTrue() { if !ast.IsJsonSourceFile(file) && t.program.SourceFileMayBeEmitted(file, false) { if emitSignature, loaded := t.snapshot.emitSignatures.Load(file.Path()); !loaded { @@ -235,9 +230,6 @@ func (t *toBuildInfo) setFileInfoAndEmitSignatures() { } return newBuildInfoFileInfo(info) }) - if t.buildInfo.FileInfos == nil { - t.buildInfo.FileInfos = []*BuildInfoFileInfo{} - } } func (t *toBuildInfo) setRootOfIncrementalProgram() { diff --git a/internal/execute/tsctests/tsc_test.go b/internal/execute/tsctests/tsc_test.go index 52586c4d6d..87fca6fdca 100644 --- a/internal/execute/tsctests/tsc_test.go +++ b/internal/execute/tsctests/tsc_test.go @@ -1832,6 +1832,54 @@ func TestTscIncremental(t *testing.T) { }, }, }, + { + subScenario: "Compile incremental with case insensitive file names", + commandLineArgs: []string{"-p", "."}, + files: FileMap{ + "/home/project/tsconfig.json": stringtestutil.Dedent(` + { + "compilerOptions": { + "incremental": true + }, + }`), + "/home/project/src/index.ts": stringtestutil.Dedent(` + import type { Foo1 } from 'lib1'; + import type { Foo2 } from 'lib2'; + export const foo1: Foo1 = { foo: "a" }; + export const foo2: Foo2 = { foo: "b" };`), + "/home/node_modules/lib1/index.d.ts": stringtestutil.Dedent(` + import type { Foo } from 'someLib'; + export type { Foo as Foo1 };`), + "/home/node_modules/lib1/package.json": stringtestutil.Dedent(` + { + "name": "lib1" + }`), + "/home/node_modules/lib2/index.d.ts": stringtestutil.Dedent(` + import type { Foo } from 'somelib'; + export type { Foo as Foo2 }; + export declare const foo2: Foo;`), + "/home/node_modules/lib2/package.json": stringtestutil.Dedent(` + { + "name": "lib2" + } + `), + "/home/node_modules/someLib/index.d.ts": stringtestutil.Dedent(` + import type { Str } from 'otherLib'; + export type Foo = { foo: Str; };`), + "/home/node_modules/someLib/package.json": stringtestutil.Dedent(` + { + "name": "somelib" + }`), + "/home/node_modules/otherLib/index.d.ts": stringtestutil.Dedent(` + export type Str = string;`), + "/home/node_modules/otherLib/package.json": stringtestutil.Dedent(` + { + "name": "otherlib" + }`), + }, + cwd: "/home/project", + ignoreCase: true, + }, } for _, test := range testCases { diff --git a/testdata/baselines/reference/tsc/incremental/Compile-incremental-with-case-insensitive-file-names.js b/testdata/baselines/reference/tsc/incremental/Compile-incremental-with-case-insensitive-file-names.js new file mode 100644 index 0000000000..416b8b8923 --- /dev/null +++ b/testdata/baselines/reference/tsc/incremental/Compile-incremental-with-case-insensitive-file-names.js @@ -0,0 +1,196 @@ +currentDirectory::/home/project +useCaseSensitiveFileNames::false +Input:: +//// [/home/node_modules/lib1/index.d.ts] *new* +import type { Foo } from 'someLib'; +export type { Foo as Foo1 }; +//// [/home/node_modules/lib1/package.json] *new* +{ + "name": "lib1" +} +//// [/home/node_modules/lib2/index.d.ts] *new* +import type { Foo } from 'somelib'; +export type { Foo as Foo2 }; +export declare const foo2: Foo; +//// [/home/node_modules/lib2/package.json] *new* +{ + "name": "lib2" +} +//// [/home/node_modules/otherLib/index.d.ts] *new* +export type Str = string; +//// [/home/node_modules/otherLib/package.json] *new* +{ + "name": "otherlib" +} +//// [/home/node_modules/someLib/index.d.ts] *new* +import type { Str } from 'otherLib'; +export type Foo = { foo: Str; }; +//// [/home/node_modules/someLib/package.json] *new* +{ + "name": "somelib" +} +//// [/home/project/src/index.ts] *new* +import type { Foo1 } from 'lib1'; +import type { Foo2 } from 'lib2'; +export const foo1: Foo1 = { foo: "a" }; +export const foo2: Foo2 = { foo: "b" }; +//// [/home/project/tsconfig.json] *new* +{ + "compilerOptions": { + "incremental": true + }, +} + +tsgo -p . +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +../node_modules/lib2/index.d.ts:1:26 - error TS1149: File name '/home/node_modules/somelib/index.d.ts' differs from already included file name '/home/node_modules/someLib/index.d.ts' only in casing. + The file is in the program because: + Imported via 'someLib' from file '/home/node_modules/lib1/index.d.ts' + Imported via 'somelib' from file '/home/node_modules/lib2/index.d.ts' + +1 import type { Foo } from 'somelib'; +   ~~~~~~~~~ + + ../node_modules/lib1/index.d.ts:1:26 - File is included via import here. + 1 import type { Foo } from 'someLib'; +    ~~~~~~~~~ + + +Found 1 error in ../node_modules/lib2/index.d.ts:1 + +//// [/home/project/src/index.js] *new* +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.foo2 = exports.foo1 = void 0; +exports.foo1 = { foo: "a" }; +exports.foo2 = { foo: "b" }; + +//// [/home/project/tsconfig.tsbuildinfo] *new* +{"version":"FakeTSVersion","errors":true,"root":[6],"fileNames":["lib.d.ts","../node_modules/otherlib/index.d.ts","../node_modules/somelib/index.d.ts","../node_modules/lib1/index.d.ts","../node_modules/lib2/index.d.ts","./src/index.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},"1fe659ed0634bb57b6dc25e9062f1162-export type Str = string;","12e112ff6e2744bb42d8e0b511e44117-import type { Str } from 'otherLib';\nexport type Foo = { foo: Str; };","b6305455d920a6729c435e6acf45eff6-import type { Foo } from 'someLib';\nexport type { Foo as Foo1 };","a5393e550a9c20a242a120bf6410db48-import type { Foo } from 'somelib';\nexport type { Foo as Foo2 };\nexport declare const foo2: Foo;","42aef197ff5f079223e2c29fb2e77cc5-import type { Foo1 } from 'lib1';\nimport type { Foo2 } from 'lib2';\nexport const foo1: Foo1 = { foo: \"a\" };\nexport const foo2: Foo2 = { foo: \"b\" };"],"fileIdsList":[[3],[2],[4,5]],"referencedMap":[[4,1],[5,1],[3,2],[6,3]]} +//// [/home/project/tsconfig.tsbuildinfo.readable.baseline.txt] *new* +{ + "version": "FakeTSVersion", + "errors": true, + "root": [ + { + "files": [ + "./src/index.ts" + ], + "original": 6 + } + ], + "fileNames": [ + "lib.d.ts", + "../node_modules/otherlib/index.d.ts", + "../node_modules/somelib/index.d.ts", + "../node_modules/lib1/index.d.ts", + "../node_modules/lib2/index.d.ts", + "./src/index.ts" + ], + "fileInfos": [ + { + "fileName": "lib.d.ts", + "version": "8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", + "signature": "8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true, + "impliedNodeFormat": "CommonJS", + "original": { + "version": "8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true, + "impliedNodeFormat": 1 + } + }, + { + "fileName": "../node_modules/otherlib/index.d.ts", + "version": "1fe659ed0634bb57b6dc25e9062f1162-export type Str = string;", + "signature": "1fe659ed0634bb57b6dc25e9062f1162-export type Str = string;", + "impliedNodeFormat": "CommonJS" + }, + { + "fileName": "../node_modules/somelib/index.d.ts", + "version": "12e112ff6e2744bb42d8e0b511e44117-import type { Str } from 'otherLib';\nexport type Foo = { foo: Str; };", + "signature": "12e112ff6e2744bb42d8e0b511e44117-import type { Str } from 'otherLib';\nexport type Foo = { foo: Str; };", + "impliedNodeFormat": "CommonJS" + }, + { + "fileName": "../node_modules/lib1/index.d.ts", + "version": "b6305455d920a6729c435e6acf45eff6-import type { Foo } from 'someLib';\nexport type { Foo as Foo1 };", + "signature": "b6305455d920a6729c435e6acf45eff6-import type { Foo } from 'someLib';\nexport type { Foo as Foo1 };", + "impliedNodeFormat": "CommonJS" + }, + { + "fileName": "../node_modules/lib2/index.d.ts", + "version": "a5393e550a9c20a242a120bf6410db48-import type { Foo } from 'somelib';\nexport type { Foo as Foo2 };\nexport declare const foo2: Foo;", + "signature": "a5393e550a9c20a242a120bf6410db48-import type { Foo } from 'somelib';\nexport type { Foo as Foo2 };\nexport declare const foo2: Foo;", + "impliedNodeFormat": "CommonJS" + }, + { + "fileName": "./src/index.ts", + "version": "42aef197ff5f079223e2c29fb2e77cc5-import type { Foo1 } from 'lib1';\nimport type { Foo2 } from 'lib2';\nexport const foo1: Foo1 = { foo: \"a\" };\nexport const foo2: Foo2 = { foo: \"b\" };", + "signature": "42aef197ff5f079223e2c29fb2e77cc5-import type { Foo1 } from 'lib1';\nimport type { Foo2 } from 'lib2';\nexport const foo1: Foo1 = { foo: \"a\" };\nexport const foo2: Foo2 = { foo: \"b\" };", + "impliedNodeFormat": "CommonJS" + } + ], + "fileIdsList": [ + [ + "../node_modules/somelib/index.d.ts" + ], + [ + "../node_modules/otherlib/index.d.ts" + ], + [ + "../node_modules/lib1/index.d.ts", + "../node_modules/lib2/index.d.ts" + ] + ], + "referencedMap": { + "../node_modules/lib1/index.d.ts": [ + "../node_modules/somelib/index.d.ts" + ], + "../node_modules/lib2/index.d.ts": [ + "../node_modules/somelib/index.d.ts" + ], + "../node_modules/somelib/index.d.ts": [ + "../node_modules/otherlib/index.d.ts" + ], + "./src/index.ts": [ + "../node_modules/lib1/index.d.ts", + "../node_modules/lib2/index.d.ts" + ] + }, + "size": 1685 +} +//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + +tsconfig.json:: +SemanticDiagnostics:: +*refresh* /home/src/tslibs/TS/Lib/lib.d.ts +*refresh* /home/node_modules/otherLib/index.d.ts +*refresh* /home/node_modules/someLib/index.d.ts +*refresh* /home/node_modules/lib1/index.d.ts +*refresh* /home/node_modules/lib2/index.d.ts +*refresh* /home/project/src/index.ts +Signatures::