Skip to content

Conversation

@sheetalkamat
Copy link
Member

@sheetalkamat sheetalkamat commented Nov 27, 2025

Handle file casing when creating program so that we dont add duplicate files that only differ in casing on case insesitive file system.
This also adds checks for forceConsistentCasingInFileNames

Fixes #1549
Fixes #1619

Copilot AI review requested due to automatic review settings November 27, 2025 00:05
Copilot finished reviewing on behalf of sheetalkamat November 27, 2025 00:08
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements file casing handling for case-insensitive file systems to prevent duplicate files that only differ in casing and adds checks for the forceConsistentCasingInFileNames compiler option.

  • Adds a new utility function GetNormalizedAbsolutePathWithoutRoot to strip root paths for casing comparison
  • Refactors the file parser to track files by both path and casing, allowing detection of duplicate files with different casings
  • Implements diagnostic reporting for files that differ only in casing (TS1149 and TS1261)

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
testdata/baselines/reference/tsc/incremental/Compile-incremental-with-case-insensitive-file-names.js New baseline test for incremental compilation with case-insensitive file names
testdata/baselines/reference/tsc/forceConsistentCasingInFileNames/*.js New baseline tests covering various forceConsistentCasingInFileNames scenarios
internal/tspath/path_test.go Adds test for GetNormalizedAbsolutePathWithoutRoot function
internal/tspath/path.go Implements GetNormalizedAbsolutePathWithoutRoot to strip root from absolute paths
internal/execute/tsctests/tsc_test.go Adds comprehensive test cases for forceConsistentCasingInFileNames feature
internal/execute/incremental/snapshottobuildinfo.go Changes MapNonNil to Map and removes duplicate file filtering logic
internal/compiler/processingDiagnostic.go Prevents duplicate related info when the preferred location is the same as the include reason
internal/compiler/includeprocessor.go Adds addProcessingDiagnosticsForFileCasing to generate appropriate casing error diagnostics
internal/compiler/filesparser.go Major refactor to track files by casing, detect duplicates, and generate casing diagnostics
internal/compiler/fileloader.go Moves file collection logic into filesParser.getProcessedFiles()

Comment on lines 171 to 177
for i, task := range tasks {
taskIsFromExternalLibrary := isFromExternalLibrary || task.fromExternalLibrary
newTask := &queuedParseTask{task: task, lowestDepth: math.MaxInt}
loadedTask, loaded := w.tasksByFileName.LoadOrStore(task.FileName(), newTask)
task = loadedTask.task
if loaded {
tasks[i].loadedTask = task
// Add in the loaded task's external-ness.
taskIsFromExternalLibrary = taskIsFromExternalLibrary || task.fromExternalLibrary
}
task.path = loader.toPath(task.normalizedFilePath)
data, loaded := w.taskDataByPath.LoadOrStore(task.path, &parseTaskData{
tasks: map[string]*parseTask{task.normalizedFilePath: task},
lowestDepth: math.MaxInt,
})

Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loop variable capture issue: The task and i variables are captured in the closure passed to w.wg.Queue(), but the loop continues to modify these variables. This means all goroutines will see the final values of task and i from the loop, not the values from when the closure was created.

To fix this, capture the loop variables in the loop scope:

for i, task := range tasks {
    i := i
    task := task
    task.path = loader.toPath(task.normalizedFilePath)
    // ... rest of the code
}

Copilot uses AI. Check for mistakes.

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")
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in test case: "worspaces" should be "workspaces".

Suggested change
assert.Equal(t, GetNormalizedAbsolutePathWithoutRoot("c:/work/hello.txt", "d:/worspaces"), "work/hello.txt")
assert.Equal(t, GetNormalizedAbsolutePathWithoutRoot("c:/work/hello.txt", "d:/workspaces"), "work/hello.txt")

Copilot uses AI. Check for mistakes.
// ensure we only walk each task once
if checkedName, ok := seen[data]; ok {
if !loader.opts.Config.CompilerOptions().ForceConsistentCasingInFileNames.IsFalse() {
// Check if it differs only in drive letters its ok to ignore that error:
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar issue in comment: Missing semicolon/period, and "its" should be "it's".

Suggested change
// Check if it differs only in drive letters its ok to ignore that error:
// Check if it differs only in drive letters; it's ok to ignore that error.

Copilot uses AI. Check for mistakes.
seen[data] = task.normalizedFilePath
}

if tasksSeenByNameIgnoreCase != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make sure I understand this, why can't this check be made above along with the check for when ForceConsistentCasingInFileNames is true?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Panic in incremental with tsbuildinfo with mismatched fileNames and fileInfos Crash in incremental due to inconsistent casing

3 participants