Fix case sensitivity fswatch and users - #64210
Jake Bailey (jakebailey) wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
🔵 Needs a closer look
The broad changes to native watcher routing, snapshot synchronization, realpath caching, and incremental builds require final human validation, especially on macOS.
Pull request overview
Extends macOS watch handling so differently cased or normalized filesystem paths are routed back to compiler-visible names without changing compiler identity.
Changes:
- Adds native Darwin path comparison and case-aware watch routing.
- Introduces watch-alias indexing across project, CLI, and build watches.
- Adds realpath caching support and broad regression/performance coverage.
File summaries
| File | Description |
|---|---|
tsc/testdata/baselines/reference/tsbuildWatch/dependencyUpdate/watches-absolute-non-root-dependency-updates.js |
Updates preserved watch-path casing. |
tsc/internal/watchalias/registration.go |
Implements alias registration and event matching. |
tsc/internal/watchalias/index.go |
Adds the watch-alias index. |
tsc/internal/watchalias/index_test.go |
Tests alias and realpath behavior. |
tsc/internal/watchalias/index_darwin_test.go |
Tests native Darwin aliases. |
tsc/internal/watchalias/capability_test.go |
Tests capability propagation. |
tsc/internal/vfs/vfs.go |
Adds parent-assisted realpath resolution. |
tsc/internal/vfs/vfs_test.go |
Tests realpath fallback behavior. |
tsc/internal/vfs/osvfs/realpath_darwin.go |
Optimizes Darwin realpath resolution. |
tsc/internal/vfs/osvfs/realpath_darwin_test.go |
Tests Darwin realpath semantics. |
tsc/internal/vfs/osvfs/os.go |
Exposes native watch comparison. |
tsc/internal/vfs/cachedvfs/cachedvfs.go |
Propagates new VFS capabilities. |
tsc/internal/project/watchnotifications.go |
Prepares and expands watch notifications. |
tsc/internal/project/watchalias.go |
Integrates aliases into snapshots. |
tsc/internal/project/watchalias_darwin_test.go |
Adds end-to-end Darwin watch tests. |
tsc/internal/project/watchalias_coalescing_darwin_test.go |
Tests native lifecycle coalescing. |
tsc/internal/project/watch.go |
Preserves original watch spellings. |
tsc/internal/project/snapshotfs.go |
Tracks realpath observations. |
tsc/internal/project/snapshot.go |
Refreshes aliases during cloning. |
tsc/internal/project/session.go |
Serializes watch preparation and publication. |
tsc/internal/project/session_test.go |
Updates expected watch casing. |
tsc/internal/project/projectcollectionbuilder.go |
Reuses missing-directory tracking. |
tsc/internal/project/project.go |
Stores original watched filenames. |
tsc/internal/project/overlayfs.go |
Coalesces compiler-equivalent notifications. |
tsc/internal/project/filechange.go |
Retains filesystem-change metadata. |
tsc/internal/project/configfileregistrybuilder.go |
Supports full config invalidation. |
tsc/internal/project/configfileregistry.go |
Retains config lookup spelling. |
tsc/internal/project/api.go |
Expands API-provided watch summaries. |
tsc/internal/fswatch/watcher.go |
Adds comparer-aware routing and filtering. |
tsc/internal/fswatch/watcher_test.go |
Tests comparer and callback behavior. |
tsc/internal/fswatch/README.md |
Documents macOS path semantics. |
tsc/internal/fswatch/pathkey.go |
Exposes immutable path comparers. |
tsc/internal/fswatch/pathcompare.go |
Implements path comparison and rebasing. |
tsc/internal/fswatch/fsevents_darwin.go |
Applies native comparison to FSEvents. |
tsc/internal/fswatch/fsevents_darwin_shared_test.go |
Tests FSEvents routing and performance. |
tsc/internal/fswatch/fsevents_darwin_nfd_test.go |
Tests Darwin Unicode aliases. |
tsc/internal/fswatch/fsevents_darwin_ffi.s |
Adds the CFStringFold trampoline. |
tsc/internal/fswatch/fsevents_darwin_ffi.go |
Implements CoreFoundation folding. |
tsc/internal/fswatch/CHANGES.md |
Records macOS comparison behavior. |
tsc/internal/fswatch/canonicalize_other.go |
Defines exact non-Darwin comparison. |
tsc/internal/fswatch/canonicalize_darwin.go |
Queries Darwin volume sensitivity. |
tsc/internal/execute/watchmanager/watchmanager.go |
Adds alias and resolution lifecycle management. |
tsc/internal/execute/watchmanager/watchmanager_test.go |
Tests watch generations and deletions. |
tsc/internal/execute/watchmanager/watchalias_darwin_test.go |
Tests native alias resolution. |
tsc/internal/execute/watcher.go |
Integrates aliases into CLI watch mode. |
tsc/internal/execute/tsctests/contentmapper_watch_test.go |
Tests manifest casing changes. |
tsc/internal/execute/build/watchchanges_test.go |
Tests ancestor deletion matching. |
tsc/internal/execute/build/orchestrator.go |
Integrates aliases into build watch mode. |
tsc/internal/execute/build/compilerHost.go |
Tracks build filesystem reads. |
tsc/internal/execute/build/buildtask.go |
Retains watched build dependencies. |
tsc/internal/bundled/embed.go |
Propagates VFS watch capabilities. |
Review details
- Files reviewed: 55/55 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
|
This is nasty, I'm going to try and simplify it, but I think it can only be simpler by doing less precise tracking... |
|
It doesn't save much code to simplify it, with much worse downsides, sadly. |
|
The FS overlay stuff very much conflicted, so, I have to figure that out |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Symlinked logical directory roots are not registered as directories, so descendant creation events can still be missed.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The large cross-cutting watch, filesystem, snapshot, and build changes include platform-specific behavior that requires final human validation on macOS.
Review effort: Balanced
Findings: None
Resolved since last review (1)
ec619ae to
62b605c
Compare
Case-insensitive deduplication must not change the name passed to the filesystem watcher. FSEvents reports the on-disk spelling, so replacing Mixed with mixed can make an otherwise ordinary project miss events. Keep canonical keys for coverage checks and the first observed spelling for subscriptions. Update the watch-set regression and build baseline to distinguish these two responsibilities.
A valid path can differ from the name returned by FSEvents in casing or Unicode normalization, including folds that change length. Exact prefix and WatchFile comparisons silently lose those notifications. Use volume-aware native comparison for FSEvents and kqueue, rebasing matches to the subscriber's root without changing child identities. Case-sensitive volumes and unrelated siblings remain distinct. Cache prepared roots and fold incoming paths lazily so shared streams do not repeat Unicode work for every subscription. The native routing, normalization, overflow, and file-watch regressions belong with this fix; compiler invalidation is handled separately.
|
I'm currently splitting this PR into a few, since this actually fixes three marginally related things:
|
62b605c to
e5112b6
Compare
|
|
||
| // PathComparerForPath returns exact comparison on platforms without native | ||
| // Darwin watch aliases. It does not inspect the host filesystem. | ||
| func PathComparerForPath(path string) (PathComparer, error) { |
There was a problem hiding this comment.
This API has leaked in from future changes; it's probably not harmful to leave.
|
I'm going to not bother with 3 as I don't think any normal client would do that, but for 4-8, that depends on this PR, so I'll send that later. |

Fixes #64089
This has two parts:
fseventsis unlike other watchers. On Linux, Windows, etc, you register a watch at a location and then get updates relative to that. Butfseventsdoes callbacks with paths of its own making, which can differ from the path names you originally watched. This causes mismatches where we get an event and it doesn't match anything we registered. So, we need to apply macOS's file path comparison to check the paths it sends us to line them back up with our own watches, then re-root them properly.DirWatchSetstored paths as canonical, but then used those for watching, which caused more mismatches. Store both forms.