diff --git a/tests/lsp/worker_map_test.mjs b/tests/lsp/worker_map_test.mjs index 3eb2c773..864615f0 100644 --- a/tests/lsp/worker_map_test.mjs +++ b/tests/lsp/worker_map_test.mjs @@ -136,6 +136,13 @@ function workerBootstrap() { fs.watch = (directory, ...args) => { const listener = args.pop(); const watcher = watch(directory, ...args, (event, filename) => { + // macOS FSEvents can drop a change made while the stream starts; the worker must not need it. + if ( + directory === registrationParent && + filename === path.basename(path.dirname(registrationDirectory)) + ) { + return; + } const sibling = path.basename(workerData.ancestorNoise); if ( directory === workerData.workspaceRoot && diff --git a/tools/tsserver-hook-worker.js b/tools/tsserver-hook-worker.js index 55789bf8..8c341a1c 100644 --- a/tools/tsserver-hook-worker.js +++ b/tools/tsserver-hook-worker.js @@ -20,6 +20,8 @@ const BUILD_FILES = new Set(['BUILD.bazel', 'BUILD']); const DEBUG = !!process.env.TSSERVER_HOOK_DEBUG; const directoryWatchers = new Map(); +// Declared before the initial build, whose watch attachment may already schedule a rebuild. +let rebuildTimer = null; function log(msg) { if (DEBUG) { @@ -315,8 +317,6 @@ parentPort.postMessage({ type: 'resolution-map', data: initialMap }); // ── File-system watchers ────────────────────────────────────────────────────── -let rebuildTimer = null; - function scheduleRebuild() { if (rebuildTimer) return; rebuildTimer = setTimeout(() => { @@ -386,6 +386,14 @@ function watchDirectory(dir, root, watched, filename) { scheduleRebuild(); } }); + // A directory replaced while its watch attaches may never send an event (macOS FSEvents). + let attached; + try { + attached = fs.statSync(canonical); + } catch (_) {} + if (!attached || attached.dev !== stat.dev || attached.ino !== stat.ino) { + scheduleRebuild(); + } } else { current.filter = filter; }