ttsc 0.31.6 → 0.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/cmd/ttsc/compiler_argument_cwd_test.go +38 -38
  2. package/cmd/ttscserver/main.go +2 -12
  3. package/cmd/ttscserver/resolve_launch_uses_native_flag_authority_test.go +7 -7
  4. package/driver/lsp.go +4 -43
  5. package/driver/rewrite.go +1 -1
  6. package/internal/e2etrace/completion_hints.go +3 -3
  7. package/internal/e2etrace/completion_hints_test.go +26 -12
  8. package/internal/graph/build.go +5 -6
  9. package/internal/graph/graph.go +3 -3
  10. package/internal/graph/line_model.go +2 -35
  11. package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +13 -36
  12. package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +9 -3
  13. package/internal/lspserver/lsp_plugin.go +19 -0
  14. package/internal/lspserver/lsp_proxy.go +9 -129
  15. package/internal/lspserver/lsp_server.go +0 -8
  16. package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +6 -36
  17. package/internal/sourceprocess/windows_target_owns_suspended_admission_test.go +5 -5
  18. package/lib/TtscCompiler.js +1 -1
  19. package/lib/TtscCompiler.js.map +1 -1
  20. package/lib/compiler/internal/ResidentCheckProcess.d.ts +74 -96
  21. package/lib/compiler/internal/ResidentCheckProcess.js +150 -335
  22. package/lib/compiler/internal/ResidentCheckProcess.js.map +1 -1
  23. package/lib/compiler/internal/ResidentCheckProcessHost.d.ts +127 -0
  24. package/lib/compiler/internal/ResidentCheckProcessHost.js +447 -0
  25. package/lib/compiler/internal/ResidentCheckProcessHost.js.map +1 -0
  26. package/lib/compiler/internal/ResidentCheckProcessProtocol.d.ts +64 -0
  27. package/lib/compiler/internal/ResidentCheckProcessProtocol.js +3 -0
  28. package/lib/compiler/internal/ResidentCheckProcessProtocol.js.map +1 -0
  29. package/lib/compiler/internal/build/BuildExecution.d.ts +103 -1
  30. package/lib/compiler/internal/build/BuildExecution.js +92 -12
  31. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  32. package/lib/compiler/internal/build/ResidentCheckWatchSession.d.ts +16 -11
  33. package/lib/compiler/internal/build/ResidentCheckWatchSession.js +37 -21
  34. package/lib/compiler/internal/build/ResidentCheckWatchSession.js.map +1 -1
  35. package/lib/compiler/internal/build/RunBuildOptions.d.ts +6 -5
  36. package/lib/compiler/internal/build/TsgoArguments.js.map +1 -1
  37. package/lib/compiler/internal/build/WatchInputPublication.d.ts +59 -0
  38. package/lib/compiler/internal/build/WatchInputPublication.js +93 -0
  39. package/lib/compiler/internal/build/WatchInputPublication.js.map +1 -0
  40. package/lib/compiler/internal/build/runBuild.d.ts +29 -2
  41. package/lib/compiler/internal/build/runBuild.js +45 -19
  42. package/lib/compiler/internal/build/runBuild.js.map +1 -1
  43. package/lib/compiler/internal/buildNativeCompiler.d.ts +2 -0
  44. package/lib/compiler/internal/buildNativeCompiler.js +1 -0
  45. package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
  46. package/lib/compiler/internal/compileProjectInMemory.js +4 -2
  47. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  48. package/lib/compiler/internal/project/CompilerProjectSelection.d.ts +9 -8
  49. package/lib/compiler/internal/project/CompilerProjectSelection.js +11 -9
  50. package/lib/compiler/internal/project/CompilerProjectSelection.js.map +1 -1
  51. package/lib/compiler/internal/project/resolveOwningProjectConfig.d.ts +25 -8
  52. package/lib/compiler/internal/project/resolveOwningProjectConfig.js +26 -14
  53. package/lib/compiler/internal/project/resolveOwningProjectConfig.js.map +1 -1
  54. package/lib/compiler/internal/readEffectiveCompilerOptions.js +1 -1
  55. package/lib/compiler/internal/readEffectiveCompilerOptions.js.map +1 -1
  56. package/lib/compiler/internal/resolveBinary.d.ts +1 -0
  57. package/lib/compiler/internal/resolveBinary.js.map +1 -1
  58. package/lib/compiler/internal/resolveTsgo.d.ts +6 -0
  59. package/lib/compiler/internal/resolveTsgo.js.map +1 -1
  60. package/lib/compiler/internal/runSingleFileEmit.d.ts +32 -4
  61. package/lib/compiler/internal/runSingleFileEmit.js +124 -89
  62. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  63. package/lib/compiler/internal/startResidentTransform.js +8 -2
  64. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  65. package/lib/compiler/internal/transformProjectInMemory.js +11 -3
  66. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  67. package/lib/compiler/internal/transformProjectInWorker.d.ts +1 -1
  68. package/lib/compiler/internal/transformProjectInWorker.js +3 -2
  69. package/lib/compiler/internal/transformProjectInWorker.js.map +1 -1
  70. package/lib/flags/ParseOptions.d.ts +2 -2
  71. package/lib/internal/E2ETrace.js.map +1 -1
  72. package/lib/internal/OwnedNativeProcess.d.ts +2 -2
  73. package/lib/internal/OwnedNativeProcess.js +2 -2
  74. package/lib/internal/SourceNativeRetirement.d.ts +8 -7
  75. package/lib/internal/SourceNativeRetirement.js +19 -10
  76. package/lib/internal/SourceNativeRetirement.js.map +1 -1
  77. package/lib/internal/TtsxNodeOptions.d.ts +79 -0
  78. package/lib/internal/TtsxNodeOptions.js +121 -0
  79. package/lib/internal/TtsxNodeOptions.js.map +1 -0
  80. package/lib/internal/javascriptRuntimeCapabilities.js +4 -1
  81. package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
  82. package/lib/internal/parseWindowsDirectoryCaseSensitivity.d.ts +5 -5
  83. package/lib/internal/parseWindowsDirectoryCaseSensitivity.js +5 -5
  84. package/lib/internal/pathIdentity/createCaseSensitivityObservations.d.ts +35 -0
  85. package/lib/internal/pathIdentity/createCaseSensitivityObservations.js +209 -0
  86. package/lib/internal/pathIdentity/createCaseSensitivityObservations.js.map +1 -0
  87. package/lib/internal/pathIdentity/createFilesystemPathIdentityContext.js +2 -143
  88. package/lib/internal/pathIdentity/createFilesystemPathIdentityContext.js.map +1 -1
  89. package/lib/internal/projectInputPathIdentity.d.ts +1 -0
  90. package/lib/internal/projectInputPathIdentity.js +1 -0
  91. package/lib/internal/projectInputPathIdentity.js.map +1 -1
  92. package/lib/internal/workerModuleUrl.d.ts +28 -0
  93. package/lib/internal/workerModuleUrl.js +40 -0
  94. package/lib/internal/workerModuleUrl.js.map +1 -0
  95. package/lib/launcher/internal/buildSingleRootProject.js +2 -1
  96. package/lib/launcher/internal/buildSingleRootProject.js.map +1 -1
  97. package/lib/launcher/internal/getCompilerVersionText.js +3 -2
  98. package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
  99. package/lib/launcher/internal/parseTtsxCLI.d.ts +5 -4
  100. package/lib/launcher/internal/parseTtsxCLI.js +5 -4
  101. package/lib/launcher/internal/parseTtsxCLI.js.map +1 -1
  102. package/lib/launcher/internal/prepareExecution.js +2 -1
  103. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  104. package/lib/launcher/internal/resolveSingleFileOutput.d.ts +8 -9
  105. package/lib/launcher/internal/resolveSingleFileOutput.js +8 -9
  106. package/lib/launcher/internal/resolveSingleFileOutput.js.map +1 -1
  107. package/lib/launcher/internal/runTtsc.js +91 -41
  108. package/lib/launcher/internal/runTtsc.js.map +1 -1
  109. package/lib/launcher/internal/runTtsx.js +5 -2
  110. package/lib/launcher/internal/runTtsx.js.map +1 -1
  111. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.d.ts +10 -6
  112. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.js +11 -9
  113. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.js.map +1 -1
  114. package/lib/launcher/internal/runtime/RuntimeEntrySources.d.ts +40 -0
  115. package/lib/launcher/internal/runtime/RuntimeEntrySources.js +57 -0
  116. package/lib/launcher/internal/runtime/RuntimeEntrySources.js.map +1 -0
  117. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.d.ts +5 -4
  118. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.js +8 -6
  119. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.js.map +1 -1
  120. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.d.ts +20 -0
  121. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.js +27 -0
  122. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.js.map +1 -0
  123. package/lib/launcher/internal/runtime/commonJsExportNames.d.ts +2 -1
  124. package/lib/launcher/internal/runtime/commonJsExportNames.js +2 -1
  125. package/lib/launcher/internal/runtime/commonJsExportNames.js.map +1 -1
  126. package/lib/launcher/internal/runtime/installRuntimeHooks.d.ts +5 -3
  127. package/lib/launcher/internal/runtime/installRuntimeHooks.js +42 -12
  128. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  129. package/lib/launcher/internal/runtime/resolveRuntimeCleanTargets.js +3 -1
  130. package/lib/launcher/internal/runtime/resolveRuntimeCleanTargets.js.map +1 -1
  131. package/lib/launcher/internal/watch/AsyncWatchTopology.d.ts +113 -0
  132. package/lib/launcher/internal/watch/AsyncWatchTopology.js +232 -0
  133. package/lib/launcher/internal/watch/AsyncWatchTopology.js.map +1 -0
  134. package/lib/launcher/internal/watch/WatchSettlement.d.ts +49 -0
  135. package/lib/launcher/internal/watch/WatchSettlement.js +70 -0
  136. package/lib/launcher/internal/watch/WatchSettlement.js.map +1 -0
  137. package/lib/launcher/internal/watch/WatchTopology.d.ts +103 -13
  138. package/lib/launcher/internal/watch/WatchTopology.js +380 -95
  139. package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
  140. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.d.ts +121 -0
  141. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.js +3 -0
  142. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.js.map +1 -0
  143. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.d.ts +4 -1
  144. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.js +3 -2
  145. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.js.map +1 -1
  146. package/lib/launcher/internal/watch/serveWatchTopologyWorker.d.ts +19 -0
  147. package/lib/launcher/internal/watch/serveWatchTopologyWorker.js +62 -0
  148. package/lib/launcher/internal/watch/serveWatchTopologyWorker.js.map +1 -0
  149. package/lib/launcher/internal/watch/watchTopologyWorker.d.ts +1 -0
  150. package/lib/launcher/internal/watch/watchTopologyWorker.js +22 -0
  151. package/lib/launcher/internal/watch/watchTopologyWorker.js.map +1 -0
  152. package/lib/plugin/CapabilityPluginResolver.d.ts +1 -1
  153. package/lib/plugin/CapabilityPluginResolver.js +3 -2
  154. package/lib/plugin/CapabilityPluginResolver.js.map +1 -1
  155. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +15 -12
  156. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +13 -11
  157. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
  158. package/lib/plugin/internal/load/PluginPackageResolution.d.ts +2 -2
  159. package/lib/plugin/internal/load/PluginPackageResolution.js +13 -7
  160. package/lib/plugin/internal/load/PluginPackageResolution.js.map +1 -1
  161. package/lib/plugin/internal/load/ProjectPluginEntries.d.ts +23 -7
  162. package/lib/plugin/internal/load/ProjectPluginEntries.js +36 -14
  163. package/lib/plugin/internal/load/ProjectPluginEntries.js.map +1 -1
  164. package/lib/plugin/internal/load/collectProjectHostInputs.d.ts +3 -1
  165. package/lib/plugin/internal/load/collectProjectHostInputs.js +5 -2
  166. package/lib/plugin/internal/load/collectProjectHostInputs.js.map +1 -1
  167. package/lib/plugin/internal/load/hasProjectPluginEntries.d.ts +3 -1
  168. package/lib/plugin/internal/load/hasProjectPluginEntries.js +4 -2
  169. package/lib/plugin/internal/load/hasProjectPluginEntries.js.map +1 -1
  170. package/lib/plugin/internal/load/loadProjectPlugins.d.ts +36 -5
  171. package/lib/plugin/internal/load/loadProjectPlugins.js +477 -421
  172. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  173. package/lib/plugin/internal/load/pluginResolutionFailure.d.ts +18 -0
  174. package/lib/plugin/internal/load/pluginResolutionFailure.js +32 -0
  175. package/lib/plugin/internal/load/pluginResolutionFailure.js.map +1 -0
  176. package/lib/plugin/internal/load/resolveNativeSource.js +2 -3
  177. package/lib/plugin/internal/load/resolveNativeSource.js.map +1 -1
  178. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +1 -1
  179. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +3 -2
  180. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
  181. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +4 -1
  182. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +9 -2
  183. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
  184. package/lib/structures/ITtscCompilerContext.d.ts +12 -12
  185. package/lib/structures/ITtscPlugin.d.ts +7 -5
  186. package/lib/structures/ITtscPluginFactoryContext.d.ts +9 -8
  187. package/lib/structures/internal/TtscCommonOptions.d.ts +2 -2
  188. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +12 -6
  189. package/package.json +9 -9
  190. package/src/TtscCompiler.ts +1 -1
  191. package/src/compiler/internal/ResidentCheckProcess.ts +159 -374
  192. package/src/compiler/internal/ResidentCheckProcessHost.ts +502 -0
  193. package/src/compiler/internal/ResidentCheckProcessProtocol.ts +53 -0
  194. package/src/compiler/internal/build/BuildExecution.ts +123 -11
  195. package/src/compiler/internal/build/ResidentCheckWatchSession.ts +41 -24
  196. package/src/compiler/internal/build/RunBuildOptions.ts +9 -5
  197. package/src/compiler/internal/build/TsgoArguments.ts +4 -1
  198. package/src/compiler/internal/build/WatchInputPublication.ts +106 -0
  199. package/src/compiler/internal/build/runBuild.ts +58 -34
  200. package/src/compiler/internal/buildNativeCompiler.ts +4 -0
  201. package/src/compiler/internal/compileProjectInMemory.ts +8 -2
  202. package/src/compiler/internal/project/CompilerProjectSelection.ts +14 -14
  203. package/src/compiler/internal/project/resolveOwningProjectConfig.ts +42 -16
  204. package/src/compiler/internal/readEffectiveCompilerOptions.ts +1 -1
  205. package/src/compiler/internal/resolveBinary.ts +4 -1
  206. package/src/compiler/internal/resolveTsgo.ts +7 -0
  207. package/src/compiler/internal/runSingleFileEmit.ts +145 -101
  208. package/src/compiler/internal/startResidentTransform.ts +9 -2
  209. package/src/compiler/internal/transformProjectInMemory.ts +12 -3
  210. package/src/compiler/internal/transformProjectInWorker.ts +10 -2
  211. package/src/flags/ParseOptions.ts +2 -2
  212. package/src/internal/E2ETrace.ts +3 -1
  213. package/src/internal/OwnedNativeProcess.ts +2 -2
  214. package/src/internal/SourceNativeRetirement.ts +42 -19
  215. package/src/internal/TtsxNodeOptions.ts +124 -0
  216. package/src/internal/javascriptRuntimeCapabilities.ts +6 -1
  217. package/src/internal/parseWindowsDirectoryCaseSensitivity.ts +5 -5
  218. package/src/internal/pathIdentity/createCaseSensitivityObservations.ts +242 -0
  219. package/src/internal/pathIdentity/createFilesystemPathIdentityContext.ts +2 -169
  220. package/src/internal/projectInputPathIdentity.ts +1 -0
  221. package/src/internal/workerModuleUrl.ts +34 -0
  222. package/src/launcher/internal/buildSingleRootProject.ts +2 -0
  223. package/src/launcher/internal/getCompilerVersionText.ts +3 -2
  224. package/src/launcher/internal/parseTtsxCLI.ts +5 -4
  225. package/src/launcher/internal/prepareExecution.ts +6 -1
  226. package/src/launcher/internal/resolveSingleFileOutput.ts +8 -9
  227. package/src/launcher/internal/runTtsc.ts +127 -49
  228. package/src/launcher/internal/runTtsx.ts +17 -8
  229. package/src/launcher/internal/runtime/CommonJsRuntimeSource.ts +11 -9
  230. package/src/launcher/internal/runtime/RuntimeEntrySources.ts +57 -0
  231. package/src/launcher/internal/runtime/RuntimeProjectOwnership.ts +23 -10
  232. package/src/launcher/internal/runtime/advertiseTypeScriptExtensions.ts +25 -0
  233. package/src/launcher/internal/runtime/commonJsExportNames.ts +11 -4
  234. package/src/launcher/internal/runtime/installRuntimeHooks.ts +62 -16
  235. package/src/launcher/internal/runtime/resolveRuntimeCleanTargets.ts +17 -5
  236. package/src/launcher/internal/watch/AsyncWatchTopology.ts +267 -0
  237. package/src/launcher/internal/watch/WatchSettlement.ts +67 -0
  238. package/src/launcher/internal/watch/WatchTopology.ts +461 -102
  239. package/src/launcher/internal/watch/WatchTopologyWorkerProtocol.ts +106 -0
  240. package/src/launcher/internal/watch/projectInputReloadEventShouldNotify.ts +14 -9
  241. package/src/launcher/internal/watch/serveWatchTopologyWorker.ts +75 -0
  242. package/src/launcher/internal/watch/watchTopologyWorker.ts +29 -0
  243. package/src/plugin/CapabilityPluginResolver.ts +9 -2
  244. package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +17 -12
  245. package/src/plugin/internal/load/PluginPackageResolution.ts +14 -9
  246. package/src/plugin/internal/load/ProjectPluginEntries.ts +40 -15
  247. package/src/plugin/internal/load/collectProjectHostInputs.ts +7 -1
  248. package/src/plugin/internal/load/hasProjectPluginEntries.ts +8 -3
  249. package/src/plugin/internal/load/loadProjectPlugins.ts +683 -593
  250. package/src/plugin/internal/load/pluginResolutionFailure.ts +37 -0
  251. package/src/plugin/internal/load/resolveNativeSource.ts +10 -4
  252. package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +8 -2
  253. package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +10 -1
  254. package/src/structures/ITtscCompilerContext.ts +12 -12
  255. package/src/structures/ITtscPlugin.ts +7 -5
  256. package/src/structures/ITtscPluginFactoryContext.ts +9 -8
  257. package/src/structures/internal/TtscCommonOptions.ts +2 -2
  258. package/src/structures/internal/TtscSingleFileEmitOptions.ts +22 -11
  259. package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +0 -78
  260. package/internal/graphsymbols/provider.go +0 -650
  261. package/internal/lspserver/lsp_symbols.go +0 -212
@@ -12,9 +12,11 @@ const CompilerProjectSelection_1 = require("../../../compiler/internal/project/C
12
12
  const readJsoncFile_1 = require("../../../compiler/internal/project/readJsoncFile");
13
13
  const readProjectConfig_1 = require("../../../compiler/internal/project/readProjectConfig");
14
14
  const resolveTsgo_1 = require("../../../compiler/internal/resolveTsgo");
15
+ const SidecarEnvironment_1 = require("../../../compiler/internal/sharedHost/SidecarEnvironment");
15
16
  const spawnNative_1 = require("../../../compiler/internal/spawnNative");
16
17
  const normalizeCompilerEnumValue_1 = require("../../../flags/normalizeCompilerEnumValue");
17
18
  const readCompilerOptionValues_1 = require("../../../flags/readCompilerOptionValues");
19
+ const createCaseSensitivityObservations_1 = require("../../../internal/pathIdentity/createCaseSensitivityObservations");
18
20
  const createProjectInputPathIdentityContext_1 = require("../../../internal/pathIdentity/createProjectInputPathIdentityContext");
19
21
  const isProjectInputPathIdentityWithin_1 = require("../../../internal/pathIdentity/isProjectInputPathIdentityWithin");
20
22
  const resolveProjectInputPath_1 = require("../../../internal/pathIdentity/resolveProjectInputPath");
@@ -44,26 +46,26 @@ const watchDirectory_1 = require("./watchDirectory");
44
46
  * output for source and declaration membership. An explicit reader supplies the
45
47
  * same absolute-path membership and failure contract for each resolved project.
46
48
  * Configuration files, project-reference roots, output inference and native
47
- * filesystem identity remain this topology's responsibility.
48
- * Response frames are observed once per project refresh at its compiler cwd;
49
- * that option view serves all project-output queries while the original argv
50
- * reaches compiler membership. A second observation rejects response changes
51
- * across membership resolution. Response files join the config reload set;
52
- * these observations do not pin files or detect a change restored in between.
49
+ * filesystem identity remain this topology's responsibility. Response frames
50
+ * are observed once per project refresh at its compiler cwd; that option view
51
+ * serves all project-output queries while the original argv reaches compiler
52
+ * membership. A second observation rejects response changes across membership
53
+ * resolution. Response files join the config reload set; these observations do
54
+ * not pin files or detect a change restored in between.
53
55
  *
54
56
  * `TTSC_WATCH_DEBUG_INPUTS` reports the named event, observed population deltas
55
57
  * and reload decision. These diagnostics reuse the decision's existing inputs;
56
58
  * they perform no extra filesystem observations.
57
59
  *
58
60
  * @evidence contracts/common.md#principled-implementation The compiler-input operation's membership, published rule inputs and actual content fingerprints qualify notifications; its native default obtains the real compiler list. Post-registration reconciliation compares baselines to recover observed handoff changes, not an atomic guarantee against every transient mutation. Explicit operations preserve native defaults without replacing global filesystem or compiler methods. Project-input attribution: Observed member deltas establish attribution; stale selected names cannot override them, while directory names retain supported population causality and unrelated multiple members remain unnamed.
59
- * @evidence contracts/common.md#clear-and-simple-design Compiler, plugin and project-input watch populations keep their own baselines and callbacks under one topology owner; small classifiers separate membership, selection and handle replacement. Four distinct operations own directory subscriptions, file subscriptions, immediate reload-directory reads and compiler membership; existing callers need no new argument. Project-input attribution: One private reconciliation separates member deltas from immediate resolution-directory digests before choosing a supported event name or an observed single member.
61
+ * @evidence contracts/common.md#clear-and-simple-design Compiler, plugin and project-input watch populations keep their own baselines and callbacks under one topology owner; small classifiers separate membership, selection and handle replacement. Five distinct operations own directory subscriptions, file subscriptions, immediate reload-directory reads, compiler membership and each decision's case-policy observations; existing callers need no new argument. Project-input attribution: One private reconciliation separates member deltas from immediate resolution-directory digests before choosing a supported event name or an observed single member.
60
62
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Supported native backends and explicit gap/replacement ownership replace foreign watcher patching; failed observation is reported rather than represented as complete coverage. The constructor manufactures no observations and bypasses no input or content proof; supplied operations retain the existing callback and result contracts. Project-input attribution: Native event names do not manufacture a delta; the current and previous physical membership identify already observed names without filename or fixture exceptions.
61
63
  * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain membership authority, plugin fingerprinting, publication races, physical registration and public lifecycle operations following the documentation skill. Parameters identify each supplied operation and native default beside the constructor. Project-input attribution: Native paragraphs explain stale attention, directory causality, accompanying resolution digests and unselected immediate entries.
62
64
  * @evidence contracts/portability.md#os-neutral-implementation Native paths use Node APIs and registration uses realpath; compiler/plugin maps preserve lexical aliases and fold only measured insensitive ASCII components. Project inputs use physical identities, while unknown/native Unicode relations remain conservative event candidates rather than identity proof. Registration paths and read options are passed unchanged; compiler-input readers supply absolute membership paths without replacing the topology's native identity policy. Project-input attribution: The supplied filesystem identity transaction establishes selected names and ancestry without an OS-wide case assumption or lexical alias guess.
63
65
  *
64
- * @evidence contracts/performance.md#efficient-algorithms Identity-keyed maps separate membership, baselines and handle registries; reconciliation delegates project/config discovery, native compiler listing, filesystem traversal/hashing and backend callbacks, with costs described by its members. Construction retains operation references and empty indexes without a filesystem observation or build. Attribution indexed visits additionally pay path/key text, ancestor and native identity work; shared ancestor keys avoid repeating the same walk within a transaction.
65
- * @evidence contracts/performance.md#reuse-equivalent-work Current membership, content baselines and live registrations belong to one topology; reconciliation owners decide their reuse against fresh premises. Supplied operation identities are retained without adding an observation-result cache or assuming changed inputs remain equivalent. Project-input attribution: This classifier borrows one scan's deltas and identity transaction without retaining cross-event answers.
66
- * @evidence contracts/performance.md#bound-retention-and-release-resources Current and pending maps and live subscriptions remain topology-owned without a population cap; reconciliation attempts obsolete handle retirement. Constructor acquires no handle; directory arrays and attribution deltas are call-local. close invokes supplied close methods but does not await backend closure, and a thrown close can interrupt later map cleanup; retained snapshots remain until the facade is released.
66
+ * @evidence contracts/performance.md#efficient-algorithms Identity-keyed maps separate membership, baselines and handle registries; reconciliation delegates project/config discovery, native compiler listing, filesystem traversal/hashing and backend callbacks, with costs described by its members. A directory-triggered refresh additionally visits R active reload files even on failure and compares their metadata/owner snapshots, reading bytes only when those snapshots changed. Changed-owner and notification arrays and a reported-key set require O(R) references; moved owners share one existing watcher synchronization and post-registration reconciliation, whose selected-file traversal remains part of the operation. No additional compiler listing is requested. Construction retains operation references and empty indexes without a filesystem observation or build. Attribution indexed visits additionally pay path/key text, ancestor and native identity work; shared ancestor keys avoid repeating the same walk within a transaction.
67
+ * @evidence contracts/performance.md#reuse-equivalent-work Current membership, content baselines and live registrations belong to one topology; reconciliation owners decide their reuse against fresh premises. Supplied operation identities are retained without assuming changed inputs remain equivalent. Each project's last compiler listing is kept with its arguments and answers only a plugin-input change, which cannot move Program membership; every other refresh lists again. Notifications delivered in one poll iteration are decided together in the check phase, so one change lists and rescans once. Project-input attribution: This classifier borrows one scan's deltas and identity transaction without retaining cross-event answers.
68
+ * @evidence contracts/performance.md#bound-retention-and-release-resources Current and pending maps and live subscriptions remain topology-owned without a population cap; compiler listings are replaced by each resolution and hold only its projects; reconciliation attempts obsolete handle retirement. Constructor acquires no handle; directory arrays and attribution deltas are call-local. close invokes supplied close methods but does not await backend closure, and a thrown close can interrupt later map cleanup; retained snapshots remain until the facade is released.
67
69
  */
68
70
  class WatchTopology {
69
71
  options;
@@ -72,6 +74,7 @@ class WatchTopology {
72
74
  openFileWatch;
73
75
  readDirectory;
74
76
  getCompilerInputs;
77
+ observeCaseSensitivity;
75
78
  analysisOnly = false;
76
79
  closed = false;
77
80
  compilerPostRegistrationMembershipRefresh = false;
@@ -102,15 +105,36 @@ class WatchTopology {
102
105
  /** The plugin inputs whose directories the last sync already watched. */
103
106
  watchedExtraInputs = new Set();
104
107
  compilerFileSnapshots = new Map();
108
+ /**
109
+ * The last compiler listing of each project the topology resolved, by its
110
+ * config path, with the arguments it was asked under (`listingQuestion`). A
111
+ * change of plugin inputs reuses it (samchon/ttsc#1741).
112
+ */
113
+ compilerListings = new Map();
114
+ /**
115
+ * Notifications the current poll iteration delivered, decided together in the
116
+ * check phase (`deferNotification`).
117
+ */
118
+ pendingNotifications = new Map();
119
+ notificationsScheduled = false;
105
120
  files = new Map();
106
- lexicalIdentities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)({
107
- throwOnRealpathError: false,
108
- });
121
+ /**
122
+ * The current decision's directory case-policy observations, which every
123
+ * identity context the decision creates shares (samchon/ttsc#1742).
124
+ */
125
+ caseObservations;
126
+ lexicalIdentities;
109
127
  fileWatchers = new Map();
110
128
  observedDirectories = new Map();
111
129
  outputFiles = new Map();
112
130
  outputs = new Map();
113
131
  projectInputFingerprints = new Map();
132
+ /**
133
+ * The metadata and physical owner each matched project-input file had when
134
+ * its fingerprint was taken, by match key; a named notification re-reads only
135
+ * the files whose metadata moved (samchon/ttsc#1748).
136
+ */
137
+ projectInputFileSnapshots = new Map();
114
138
  projectInputMatches = new Map();
115
139
  projectInputs = {
116
140
  files: [],
@@ -150,14 +174,18 @@ class WatchTopology {
150
174
  * membership; the default invokes the native compiler. Readers throw
151
175
  * listing failures and retain ownership of their returned array, which this
152
176
  * topology does not mutate.
177
+ * @param observeCaseSensitivity Create one decision's directory case-policy
178
+ * observations; the default asks the native filesystem.
153
179
  */
154
- constructor(options, callbacks, openDirectoryWatch = watchDirectory_1.watchDirectory, openFileWatch = node_fs_1.default.watch, readDirectory = node_fs_1.default.readdirSync, getCompilerInputs = listCompilerInputs) {
180
+ constructor(options, callbacks, openDirectoryWatch = watchDirectory_1.watchDirectory, openFileWatch = node_fs_1.default.watch, readDirectory = node_fs_1.default.readdirSync, getCompilerInputs = listCompilerInputs, observeCaseSensitivity = createCaseSensitivityObservations_1.createCaseSensitivityObservations) {
155
181
  this.options = options;
156
182
  this.callbacks = callbacks;
157
183
  this.openDirectoryWatch = openDirectoryWatch;
158
184
  this.openFileWatch = openFileWatch;
159
185
  this.readDirectory = readDirectory;
160
186
  this.getCompilerInputs = getCompilerInputs;
187
+ this.observeCaseSensitivity = observeCaseSensitivity;
188
+ this.beginLexicalTransaction();
161
189
  }
162
190
  /**
163
191
  * Re-resolve compiler inputs and notify when the resolved topology changes,
@@ -177,16 +205,61 @@ class WatchTopology {
177
205
  refresh(notify) {
178
206
  this.refreshCompilerInputs(notify, false);
179
207
  }
180
- refreshCompilerInputs(notify, skipUnobservedProjectInputWatchRoots) {
208
+ /**
209
+ * Wait until delivered notifications and registration reconciliation finish.
210
+ *
211
+ * A completed build does not imply a quiet topology: its final refresh can
212
+ * queue another decision and another build (#1756). Yield through the check
213
+ * phase before inspecting every scheduled owner so a decision queued by
214
+ * another decision remains part of this barrier. Later filesystem changes
215
+ * belong to a later barrier; this does not freeze the filesystem.
216
+ *
217
+ * @evidence contracts/common.md#principled-implementation A check-phase observation follows delivered events, and every notification, recovery and registration queue must be empty before completion.
218
+ * @evidence contracts/common.md#clear-and-simple-design The topology owns the barrier because it alone owns all five scheduled queues.
219
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Queue state establishes completion without a quiet-time estimate or an invented filesystem snapshot.
220
+ * @evidence contracts/common.md#meaningful-documentation Native prose distinguishes delivered work from later mutations and explains the post-build queued-decision race.
221
+ * @evidenceExclude contracts/portability.md#os-neutral-implementation The barrier observes owned JavaScript queues without a separate native operation.
222
+ * @evidence contracts/performance.md#efficient-algorithms Each check turn inspects five flags and two map sizes; notification processing retains its owning traversal and native-operation costs.
223
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work Each call observes current work rather than reusing an earlier completion.
224
+ * @evidence contracts/performance.md#bound-retention-and-release-resources One immediate callback and promise remain live per waiting call; continuous new work can prevent completion and no deadline is imposed.
225
+ */
226
+ async whenIdle() {
227
+ do {
228
+ await new Promise((resolve) => setImmediate(resolve));
229
+ } while (!this.closed &&
230
+ (this.notificationsScheduled ||
231
+ this.pluginNotificationsScheduled ||
232
+ this.compilerPostRegistrationReconciliationScheduled ||
233
+ this.projectInputRecoveryScheduled ||
234
+ this.projectInputPostRegistrationReconciliationScheduled ||
235
+ this.pendingNotifications.size !== 0 ||
236
+ this.pendingPluginNotifications.size !== 0));
237
+ }
238
+ /**
239
+ * @param reuseListings Answer each project's membership with its last listing
240
+ * under the same arguments instead of asking the compiler again. Only a
241
+ * refresh whose cause cannot move Program membership passes it.
242
+ */
243
+ refreshCompilerInputs(notify, skipUnobservedProjectInputWatchRoots, reuseListings = false) {
181
244
  this.beginLexicalTransaction();
182
- const next = resolveWatchTopology(this.options, this.extraInputs, this.lexicalIdentities, this.getCompilerInputs);
245
+ const listings = new Map();
246
+ const next = resolveWatchTopology(this.options, this.extraInputs, this.lexicalIdentities, (project, options) => {
247
+ const question = listingQuestion(project, options);
248
+ const last = this.compilerListings.get(project.path);
249
+ const inputs = reuseListings && last?.question === question
250
+ ? last.inputs
251
+ : this.getCompilerInputs(project, options);
252
+ listings.set(project.path, { inputs, question });
253
+ return inputs;
254
+ });
255
+ this.compilerListings = listings;
183
256
  const compilerProgramMembershipChange = next.analysisOnly &&
184
257
  WatchPaths_1.WatchPaths.mapsEqual(this.reloadFiles, next.reloadFiles) &&
185
258
  WatchPaths_1.WatchPaths.mapsEqual(this.outputFiles, next.outputFiles) &&
186
259
  WatchPaths_1.WatchPaths.mapsEqual(this.outputs, next.outputs)
187
260
  ? compilerMembershipChange(this.files, next.files)
188
261
  : [];
189
- const projectInputProgramOverlap = projectInputCompilerMembershipChange(this.projectInputs, compilerProgramMembershipChange);
262
+ const projectInputProgramOverlap = projectInputCompilerMembershipChange(this.projectInputs, compilerProgramMembershipChange, this.projectInputIdentities());
190
263
  const projectInputProgramChange = compilerProgramMembershipChange.length !== 0 &&
191
264
  projectInputProgramOverlap.length ===
192
265
  compilerProgramMembershipChange.length
@@ -260,7 +333,7 @@ class WatchTopology {
260
333
  */
261
334
  acknowledgeProjectInputCompilerMembership(changed) {
262
335
  const matches = this.collectProjectInputMatches();
263
- const fingerprints = fingerprintProjectInputMatches(matches, this.readDirectory);
336
+ const { fingerprints, snapshots } = fingerprintProjectInputMatches(matches, this.readDirectory);
264
337
  const changedInputs = projectInputChangedPaths({
265
338
  next: matches,
266
339
  nextFingerprints: fingerprints,
@@ -268,7 +341,7 @@ class WatchTopology {
268
341
  previousFingerprints: this.projectInputFingerprints,
269
342
  });
270
343
  const population = this.projectInputPopulation();
271
- const causedBy = projectInputCompilerMembershipProjectChanges(changed, population.globs);
344
+ const causedBy = projectInputCompilerMembershipProjectChanges(changed, population.globs, this.projectInputIdentities());
272
345
  const reloadInput = {
273
346
  causedBy,
274
347
  changed: causedBy.length === 1 ? causedBy[0] : undefined,
@@ -277,7 +350,7 @@ class WatchTopology {
277
350
  reloadDirectories: population.reloadDirectories,
278
351
  reloadFiles: population.reloadFiles ?? [],
279
352
  };
280
- const reload = (0, projectInputReloadEventShouldNotify_1.projectInputReloadEventShouldNotify)(reloadInput);
353
+ const reload = (0, projectInputReloadEventShouldNotify_1.projectInputReloadEventShouldNotify)(reloadInput, this.projectInputIdentities());
281
354
  reportProjectInputDecision({
282
355
  source: "compiler-membership",
283
356
  ...reloadInput,
@@ -289,6 +362,7 @@ class WatchTopology {
289
362
  // instead of disappearing behind the warm compiler-membership handoff.
290
363
  this.projectInputMatches = matches;
291
364
  this.projectInputFingerprints = fingerprints;
365
+ this.projectInputFileSnapshots = snapshots;
292
366
  return reload;
293
367
  }
294
368
  /**
@@ -313,8 +387,8 @@ class WatchTopology {
313
387
  * @evidence contracts/common.md#prohibited-implementation-shortcuts The actual plugin source digest applies its owning source-selection and pruning policy; named plugins or fixture output do not replace observed input state.
314
388
  * @evidence contracts/common.md#meaningful-documentation Native prose explains baseline timing and why existing inputs are not restamped following the documentation skill.
315
389
  * @evidence contracts/portability.md#os-neutral-implementation Native path resolution and canonical backend registration govern source trees; lexical keys preserve declarations and unknown names, folding ASCII only under measured insensitive parents rather than an OS-derived rule.
316
- * @evidence contracts/performance.md#efficient-algorithms Input/path keys deduplicate with text and native case/ancestor work; only newly admitted inputs compute initial source digests, each proven from the selected files' metadata when the load's records hold and read from the selected pruned corpus otherwise; refresh(false) also pays project/compiler listing, directory traversal and watcher reconciliation; fixed publication count does not bound delegated bytes or backend work.
317
- * @evidence contracts/performance.md#reuse-equivalent-work Unchanged ordered input sets return immediately; existing inputs reuse their recorded digest until actual notification processing establishes changed consumed state; a new input's digest is the one the load recorded while its metadata holds.
390
+ * @evidence contracts/performance.md#efficient-algorithms Input/path keys deduplicate with text and native case/ancestor work; only newly admitted inputs compute initial source digests, each proven from the selected files' metadata when the load's records hold and read from the selected pruned corpus otherwise; the following reconciliation pays project configuration, directory traversal and watcher work but answers membership from each project's last compiler listing; fixed publication count does not bound delegated bytes or backend work.
391
+ * @evidence contracts/performance.md#reuse-equivalent-work Unchanged ordered input sets return immediately; a changed set reuses each project's last compiler listing under the same arguments, since plugin inputs cannot move Program membership; existing inputs reuse their recorded digest until actual notification processing establishes changed consumed state; a new input's digest is the one the load recorded while its metadata holds.
318
392
  * @evidence contracts/performance.md#bound-retention-and-release-resources Publications replace input/digest populations and case-observation caches without a population cap; synchronization attempts obsolete watcher closure. Native/supplied closure has its own lifetime and can throw; owner close does not prove every backend has stopped or every retained snapshot byte was released.
319
393
  */
320
394
  setExtraInputs(inputs, identities) {
@@ -337,7 +411,12 @@ class WatchTopology {
337
411
  pluginInputState(input, this.pluginInputIdentities));
338
412
  }
339
413
  this.pluginInputStates = states;
340
- this.refresh(false);
414
+ // Plugin inputs join the watched files and directories, never Program
415
+ // membership, so the compiler's last answer stands. A load reports up to
416
+ // three different sets, and asking again for each spawned tsgo three times
417
+ // per load (samchon/ttsc#1741). A membership change meanwhile reaches the
418
+ // refresh its own notification starts, which lists again.
419
+ this.refreshCompilerInputs(false, false, true);
341
420
  }
342
421
  /**
343
422
  * Reconcile project-rule dependencies, retaining absent files and empty glob
@@ -353,7 +432,8 @@ class WatchTopology {
353
432
  * @evidence contracts/performance.md#bound-retention-and-release-resources Publication prunes retired declaration-owner choices and replaces uncapped match/fingerprint populations. Watcher retirement invokes close without awaiting backend completion, and supplied errors can interrupt cleanup. Weak output-overlap keys do not retain old identity transactions; the current declaration snapshot can borrow caller arrays until replaced or the facade is released.
354
433
  */
355
434
  setProjectInputs(inputs) {
356
- const next = normalizeProjectInputSnapshot(inputs);
435
+ this.beginLexicalTransaction();
436
+ const next = normalizeProjectInputSnapshot(inputs, this.projectInputIdentities());
357
437
  // The declared spellings are recorded even when the normalized snapshot did
358
438
  // not move, because a republication can carry a new alias for identities
359
439
  // that already matched, and anchoring the spelling that was retired would
@@ -362,7 +442,7 @@ class WatchTopology {
362
442
  inputs.declared === undefined
363
443
  ? inputs
364
444
  : { ...inputs.declared, root: inputs.root };
365
- if (projectInputSnapshotsEqual(this.projectInputs, next)) {
445
+ if (projectInputSnapshotsEqual(this.projectInputs, next, this.projectInputIdentities())) {
366
446
  this.syncProjectInputWatchers();
367
447
  return;
368
448
  }
@@ -374,7 +454,9 @@ class WatchTopology {
374
454
  { ...(inputs.declared ?? inputs), root: inputs.root },
375
455
  ]);
376
456
  this.projectInputMatches = this.collectProjectInputMatches();
377
- this.projectInputFingerprints = fingerprintProjectInputMatches(this.projectInputMatches, this.readDirectory);
457
+ const baseline = fingerprintProjectInputMatches(this.projectInputMatches, this.readDirectory);
458
+ this.projectInputFingerprints = baseline.fingerprints;
459
+ this.projectInputFileSnapshots = baseline.snapshots;
378
460
  this.syncProjectInputWatchers();
379
461
  }
380
462
  /**
@@ -406,10 +488,24 @@ class WatchTopology {
406
488
  }
407
489
  /** Start fresh native case observations for one synchronous watch decision. */
408
490
  beginLexicalTransaction() {
491
+ this.caseObservations = this.observeCaseSensitivity();
409
492
  this.lexicalIdentities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)({
493
+ caseSensitive: this.caseObservations,
410
494
  throwOnRealpathError: false,
411
495
  });
412
496
  }
497
+ /**
498
+ * A strict project-input identity context for the current decision. Its
499
+ * physical paths are resolved afresh, and its case answers are the
500
+ * decision's: one decision builds such contexts per declaration and per
501
+ * candidate, and each asked every directory again, which on Windows is an
502
+ * `fsutil` process per question (samchon/ttsc#1742).
503
+ */
504
+ projectInputIdentities() {
505
+ return (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)({
506
+ caseSensitive: this.caseObservations,
507
+ });
508
+ }
413
509
  /** Keep lexical alias ownership while sharing this decision's case probes. */
414
510
  pathKey(location) {
415
511
  return WatchPaths_1.WatchPaths.pathKey(location, this.lexicalIdentities);
@@ -487,32 +583,93 @@ class WatchTopology {
487
583
  }
488
584
  }
489
585
  }
490
- (0, syncWatchers_1.syncWatchers)(this.directoryWatchers, desired, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), process.platform === "win32", (event, filename, gap) => {
491
- this.beginLexicalTransaction();
492
- const changed = filename === null ? undefined : node_path_1.default.resolve(location, filename);
493
- const pluginInput = changed ?? location;
494
- if (this.isPluginInput(pluginInput)) {
495
- this.notePluginNotification(pluginInput);
496
- return;
497
- }
498
- const plan = (0, planCompilerDirectoryWatchEvent_1.planCompilerDirectoryWatchEvent)({
499
- changed,
500
- event,
501
- exists: node_fs_1.default.existsSync,
502
- location,
503
- identities: this.lexicalIdentities,
504
- platform: process.platform,
505
- trackedFiles: this.files,
506
- });
507
- this.rearmFileWatchers(plan.rearm);
508
- for (const file of this.compilerChangesToReport(plan.changes, changed, gap === true)) {
509
- this.reportCompilerFileChange(file);
510
- }
511
- if (plan.refresh)
512
- this.refreshFromDirectory(location, changed);
513
- }), (location, error) => this.callbacks.onError(location, error), () => this.closed === false);
586
+ (0, syncWatchers_1.syncWatchers)(this.directoryWatchers, desired, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), process.platform === "win32", (event, filename, gap) => this.deferNotification({
587
+ gap: gap === true,
588
+ lane: "compiler",
589
+ location,
590
+ changed: filename === null
591
+ ? undefined
592
+ : node_path_1.default.resolve(location, filename),
593
+ rename: event === "rename",
594
+ })), (location, error) => this.callbacks.onError(location, error), () => this.closed === false);
514
595
  return [...this.directoryWatchers].some(([key, watcher]) => previous.get(key) !== watcher);
515
596
  }
597
+ /**
598
+ * Decide a notification in the event loop's check phase, together with every
599
+ * other notification the same poll iteration delivered (samchon/ttsc#1741).
600
+ *
601
+ * One filesystem change usually arrives as several notifications, rename and
602
+ * change or create and modify, and each listed the compiler inputs or
603
+ * rescanned the project inputs again. Identical notifications of one lane,
604
+ * location and path merge, keeping a rename or a gap any of them carried, and
605
+ * the batch refreshes compiler membership once after deciding them all. The
606
+ * refresh therefore starts after every notification it absorbs, and one
607
+ * delivered later schedules another batch.
608
+ */
609
+ deferNotification(notification) {
610
+ if (this.closed)
611
+ return;
612
+ const key = [
613
+ notification.lane,
614
+ notification.location,
615
+ notification.changed ?? "",
616
+ ].join("\0");
617
+ const pending = this.pendingNotifications.get(key);
618
+ if (pending !== undefined) {
619
+ pending.rename ||= notification.rename;
620
+ pending.gap ||= notification.gap;
621
+ return;
622
+ }
623
+ this.pendingNotifications.set(key, { ...notification });
624
+ if (this.notificationsScheduled)
625
+ return;
626
+ this.notificationsScheduled = true;
627
+ setImmediate(() => this.decideNotifications());
628
+ }
629
+ decideNotifications() {
630
+ this.notificationsScheduled = false;
631
+ const pending = [...this.pendingNotifications.values()];
632
+ this.pendingNotifications.clear();
633
+ if (this.closed)
634
+ return;
635
+ const refreshes = [];
636
+ for (const notification of pending) {
637
+ if (notification.lane === "project-input") {
638
+ this.refreshProjectInputs(notification.location, notification.changed);
639
+ continue;
640
+ }
641
+ this.decideCompilerDirectoryNotification(notification, refreshes);
642
+ }
643
+ if (refreshes.length === 0 || this.closed)
644
+ return;
645
+ this.beginLexicalTransaction();
646
+ this.refreshFromDirectories(refreshes);
647
+ }
648
+ /** Decide one merged directory notification of the compiler lane. */
649
+ decideCompilerDirectoryNotification(notification, refreshes) {
650
+ this.beginLexicalTransaction();
651
+ const { changed, location } = notification;
652
+ const pluginInput = changed ?? location;
653
+ if (this.isPluginInput(pluginInput)) {
654
+ this.notePluginNotification(pluginInput);
655
+ return;
656
+ }
657
+ const plan = (0, planCompilerDirectoryWatchEvent_1.planCompilerDirectoryWatchEvent)({
658
+ changed,
659
+ event: notification.rename ? "rename" : "change",
660
+ exists: node_fs_1.default.existsSync,
661
+ location,
662
+ identities: this.lexicalIdentities,
663
+ platform: process.platform,
664
+ trackedFiles: this.files,
665
+ });
666
+ this.rearmFileWatchers(plan.rearm);
667
+ for (const file of this.compilerChangesToReport(plan.changes, changed, notification.gap)) {
668
+ this.reportCompilerFileChange(file);
669
+ }
670
+ if (plan.refresh)
671
+ refreshes.push({ changed, location });
672
+ }
516
673
  /**
517
674
  * Reconcile tracked compiler files after a newly registered watcher returns.
518
675
  *
@@ -607,6 +764,7 @@ class WatchTopology {
607
764
  if (node_fs_1.default.existsSync(location)) {
608
765
  if (this.projectInputMatches.size !== 0) {
609
766
  const key = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)({
767
+ caseSensitive: this.caseObservations,
610
768
  throwOnRealpathError: false,
611
769
  }).resolve(location).key;
612
770
  if (this.projectInputMatches.has(key) &&
@@ -701,7 +859,7 @@ class WatchTopology {
701
859
  return;
702
860
  const previous = new Map(this.projectInputWatchers);
703
861
  const previousLinks = new Map(this.projectInputLinkWatchers);
704
- const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
862
+ const identities = this.projectInputIdentities();
705
863
  const desired = new Map();
706
864
  const required = new Map();
707
865
  for (const file of this.projectInputDeclarations("file")) {
@@ -737,10 +895,15 @@ class WatchTopology {
737
895
  active.set(identity.key, identity.path);
738
896
  }
739
897
  this.projectInputRequiredWatchRoots = required;
740
- (0, syncWatchers_1.syncWatchers)(this.projectInputWatchers, active, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), true, (_event, filename) => {
741
- const changed = filename === null ? undefined : node_path_1.default.resolve(location, filename);
742
- this.refreshProjectInputs(location, changed);
743
- }), (location, error) => {
898
+ (0, syncWatchers_1.syncWatchers)(this.projectInputWatchers, active, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), true, (_event, filename) => this.deferNotification({
899
+ gap: false,
900
+ lane: "project-input",
901
+ location,
902
+ changed: filename === null
903
+ ? undefined
904
+ : node_path_1.default.resolve(location, filename),
905
+ rename: false,
906
+ })), (location, error) => {
744
907
  const key = identities.resolve(location).key;
745
908
  const firstFailure = !this.projectInputRejectedWatchRoots.has(key);
746
909
  this.projectInputRejectedWatchRoots.add(key);
@@ -796,10 +959,15 @@ class WatchTopology {
796
959
  desired.set(identities.resolve(parent).key, parent);
797
960
  }
798
961
  }
799
- (0, syncWatchers_1.syncWatchers)(this.projectInputLinkWatchers, desired, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), false, (_event, filename) => {
800
- const changed = filename === null ? undefined : node_path_1.default.resolve(location, filename);
801
- this.refreshProjectInputs(location, changed);
802
- }), (location, error) => this.callbacks.onError(location, error), () => this.closed === false);
962
+ (0, syncWatchers_1.syncWatchers)(this.projectInputLinkWatchers, desired, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), false, (_event, filename) => this.deferNotification({
963
+ gap: false,
964
+ lane: "project-input",
965
+ location,
966
+ changed: filename === null
967
+ ? undefined
968
+ : node_path_1.default.resolve(location, filename),
969
+ rename: false,
970
+ })), (location, error) => this.callbacks.onError(location, error), () => this.closed === false);
803
971
  }
804
972
  /** Drop the watcher that just reported a directory replacement. */
805
973
  retireProjectInputWatcher(location, identities) {
@@ -857,6 +1025,7 @@ class WatchTopology {
857
1025
  return;
858
1026
  this.projectInputRecoveryScheduled = true;
859
1027
  queueMicrotask(() => {
1028
+ this.beginLexicalTransaction();
860
1029
  try {
861
1030
  let previousRejectionCount = -1;
862
1031
  while (this.closed === false &&
@@ -895,6 +1064,7 @@ class WatchTopology {
895
1064
  this.projectInputPostRegistrationReconciliationScheduled = false;
896
1065
  if (this.closed)
897
1066
  return;
1067
+ this.beginLexicalTransaction();
898
1068
  this.refreshPublishedProjectInputIdentities();
899
1069
  this.refreshProjectInputs(this.projectInputs.root, undefined, true);
900
1070
  });
@@ -908,8 +1078,9 @@ class WatchTopology {
908
1078
  * that must observe later target changes.
909
1079
  */
910
1080
  refreshPublishedProjectInputIdentities() {
911
- const next = normalizeProjectInputSnapshot(this.declaredProjectInputs);
912
- if (projectInputSnapshotsEqual(this.projectInputs, next))
1081
+ const identities = this.projectInputIdentities();
1082
+ const next = normalizeProjectInputSnapshot(this.declaredProjectInputs, identities);
1083
+ if (projectInputSnapshotsEqual(this.projectInputs, next, identities))
913
1084
  return;
914
1085
  this.projectInputs = next;
915
1086
  this.pruneProjectInputWatchRoots([next, this.declaredProjectInputs]);
@@ -997,7 +1168,7 @@ class WatchTopology {
997
1168
  const retained = this.projectInputWatchRoots.get(key);
998
1169
  if (retained !== undefined && WatchPaths_1.WatchPaths.isDirectory(retained))
999
1170
  return retained;
1000
- const resolved = ProjectInputWatchRules_1.ProjectInputWatchRules.projectInputRecursiveWatchRoot(target, this.projectInputs.root);
1171
+ const resolved = ProjectInputWatchRules_1.ProjectInputWatchRules.projectInputRecursiveWatchRoot(target, this.projectInputs.root, this.projectInputIdentities());
1001
1172
  if (resolved !== undefined)
1002
1173
  this.projectInputWatchRoots.set(key, resolved);
1003
1174
  return resolved;
@@ -1006,7 +1177,7 @@ class WatchTopology {
1006
1177
  this.beginLexicalTransaction();
1007
1178
  try {
1008
1179
  const previous = this.projectInputMatches;
1009
- const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
1180
+ const identities = this.projectInputIdentities();
1010
1181
  // One population for the whole decision. Every question below is asked of
1011
1182
  // the same declarations, and rebuilding it per question would both cost
1012
1183
  // more and let two answers disagree about what was declared.
@@ -1033,12 +1204,25 @@ class WatchTopology {
1033
1204
  }
1034
1205
  const next = this.collectProjectInputMatches();
1035
1206
  const membershipChanged = WatchPaths_1.WatchPaths.mapsEqual(previous, next) === false;
1036
- const nextFingerprints = changed === undefined ||
1207
+ // A named notification reads the named path's bytes and any file whose
1208
+ // metadata or owner moved; one that names nothing reads every file
1209
+ // (samchon/ttsc#1748).
1210
+ const scanned = changed === undefined ||
1037
1211
  membershipChanged ||
1038
1212
  directlyMatched ||
1039
1213
  topologyMatched
1040
- ? fingerprintProjectInputMatches(next, this.readDirectory)
1041
- : this.projectInputFingerprints;
1214
+ ? fingerprintProjectInputMatches(next, this.readDirectory, changed === undefined
1215
+ ? undefined
1216
+ : {
1217
+ fingerprints: this.projectInputFingerprints,
1218
+ named: identities.resolve(changed).key,
1219
+ snapshots: this.projectInputFileSnapshots,
1220
+ })
1221
+ : {
1222
+ fingerprints: this.projectInputFingerprints,
1223
+ snapshots: this.projectInputFileSnapshots,
1224
+ };
1225
+ const nextFingerprints = scanned.fingerprints;
1042
1226
  const contentChanged = WatchPaths_1.WatchPaths.mapsEqual(this.projectInputFingerprints, nextFingerprints) === false;
1043
1227
  const changedInputs = projectInputChangedPaths({
1044
1228
  next,
@@ -1065,7 +1249,7 @@ class WatchTopology {
1065
1249
  reloadDirectories: population.reloadDirectories ?? [],
1066
1250
  reloadFiles: population.reloadFiles ?? [],
1067
1251
  };
1068
- const reload = (0, projectInputReloadEventShouldNotify_1.projectInputReloadEventShouldNotify)(reloadInput);
1252
+ const reload = (0, projectInputReloadEventShouldNotify_1.projectInputReloadEventShouldNotify)(reloadInput, identities);
1069
1253
  const invalidate = (0, projectInputMembershipInvalidatesProgram_1.projectInputMembershipInvalidatesProgram)({
1070
1254
  changed: reconciledChange,
1071
1255
  changedInputs,
@@ -1075,6 +1259,7 @@ class WatchTopology {
1075
1259
  });
1076
1260
  this.projectInputMatches = next;
1077
1261
  this.projectInputFingerprints = nextFingerprints;
1262
+ this.projectInputFileSnapshots = scanned.snapshots;
1078
1263
  this.syncProjectInputWatchers(skipUnobservedProjectInputWatchRoots);
1079
1264
  // A JSON/TS/JS project-input member can simultaneously enter or leave
1080
1265
  // the compiler Program. Reconcile the compiler watch snapshot before
@@ -1126,7 +1311,7 @@ class WatchTopology {
1126
1311
  }
1127
1312
  }
1128
1313
  collectProjectInputMatches() {
1129
- const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
1314
+ const identities = this.projectInputIdentities();
1130
1315
  const matches = new Map();
1131
1316
  // Both spellings are scanned, and each is resolved here rather than when
1132
1317
  // the snapshot arrived. A declaration reached through a symlink otherwise
@@ -1207,20 +1392,81 @@ class WatchTopology {
1207
1392
  return matches;
1208
1393
  }
1209
1394
  refreshFromDirectory(location, changed) {
1210
- if (changed !== undefined &&
1211
- WatchPaths_1.WatchPaths.isDirectory(changed) &&
1212
- this.isCompilerOutputDirectory(changed) === false &&
1213
- this.isProjectInputDirectory(changed) === false) {
1214
- this.observedDirectories.set(this.pathKey(changed), changed);
1395
+ this.refreshFromDirectories([{ changed, location }]);
1396
+ }
1397
+ /**
1398
+ * Refresh compiler membership once for every directory a decision asked
1399
+ * about. A failed refresh still reports, once each, the reload inputs every
1400
+ * requested change names.
1401
+ *
1402
+ * A replacement can be delivered under only its vanished temporary name,
1403
+ * including when its malformed bytes prevent refresh. Reconcile active
1404
+ * reload-file snapshots on either outcome, deduplicate failed-refresh
1405
+ * recovery against actual reported movement, and preserve the original
1406
+ * refresh error after recovery. Batch moved owners through the existing rearm
1407
+ * operation before notifying, because a POSIX per-file handle still owns the
1408
+ * old inode. Unchanged snapshots stay quiet unless the existing
1409
+ * failed-refresh selector explicitly names or cannot find the reload input.
1410
+ */
1411
+ refreshFromDirectories(refreshes) {
1412
+ for (const { changed } of refreshes) {
1413
+ if (changed !== undefined &&
1414
+ WatchPaths_1.WatchPaths.isDirectory(changed) &&
1415
+ this.isCompilerOutputDirectory(changed) === false &&
1416
+ this.isProjectInputDirectory(changed) === false) {
1417
+ this.observedDirectories.set(this.pathKey(changed), changed);
1418
+ }
1215
1419
  }
1420
+ let refreshFailure;
1216
1421
  try {
1217
1422
  this.refresh(true);
1218
1423
  }
1219
1424
  catch (error) {
1220
- for (const reload of (0, reloadInputsForFailedTopologyRefresh_1.reloadInputsForFailedTopologyRefresh)(this.reloadFiles.values(), changed, this.lexicalIdentities)) {
1221
- this.callbacks.onInputChange({ kind: "config", path: reload });
1425
+ refreshFailure = { error };
1426
+ }
1427
+ try {
1428
+ if (this.closed)
1429
+ return;
1430
+ const changed = [];
1431
+ const rearm = [];
1432
+ const reported = new Set();
1433
+ for (const reload of this.reloadFiles.values()) {
1434
+ if (this.closed)
1435
+ return;
1436
+ const movement = this.compilerFileMovement(reload);
1437
+ if (movement.owner)
1438
+ rearm.push(reload);
1439
+ if (this.compilerMovementReports(reload, movement))
1440
+ changed.push(reload);
1222
1441
  }
1223
- this.callbacks.onError(location, error);
1442
+ if (rearm.length !== 0)
1443
+ this.rearmFileWatchers(rearm, true);
1444
+ for (const reload of changed) {
1445
+ if (this.closed)
1446
+ return;
1447
+ reported.add(this.pathKey(reload));
1448
+ this.reportCompilerFileChange(reload);
1449
+ }
1450
+ if (refreshFailure !== undefined) {
1451
+ for (const { changed } of refreshes) {
1452
+ for (const reload of (0, reloadInputsForFailedTopologyRefresh_1.reloadInputsForFailedTopologyRefresh)(this.reloadFiles.values(), changed, this.lexicalIdentities)) {
1453
+ if (this.closed)
1454
+ return;
1455
+ const key = this.pathKey(reload);
1456
+ if (reported.has(key))
1457
+ continue;
1458
+ reported.add(key);
1459
+ this.callbacks.onInputChange({ kind: "config", path: reload });
1460
+ }
1461
+ }
1462
+ }
1463
+ }
1464
+ catch (error) {
1465
+ this.callbacks.onError(refreshes[0].location, error);
1466
+ }
1467
+ finally {
1468
+ if (refreshFailure !== undefined)
1469
+ this.callbacks.onError(refreshes[0].location, refreshFailure.error);
1224
1470
  }
1225
1471
  }
1226
1472
  isCompilerOutputDirectory(location) {
@@ -1265,7 +1511,7 @@ class WatchTopology {
1265
1511
  }
1266
1512
  isProjectInputDirectory(location) {
1267
1513
  const resolved = node_path_1.default.resolve(location);
1268
- const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
1514
+ const identities = this.projectInputIdentities();
1269
1515
  return (this.projectInputs.files.some((file) => identities.isWithin(resolved, file) ||
1270
1516
  identities.isWithin(node_path_1.default.dirname(file), resolved)) ||
1271
1517
  (this.projectInputs.reloadFiles ?? []).some((file) => identities.isWithin(resolved, file) ||
@@ -1378,6 +1624,23 @@ class WatchTopology {
1378
1624
  }
1379
1625
  }
1380
1626
  exports.WatchTopology = WatchTopology;
1627
+ /**
1628
+ * Everything a compiler listing of `project` is asked with: the project, and
1629
+ * each option `listCompilerInputs` passes to the compiler. Two refreshes with
1630
+ * the same question ask the compiler the same thing; only the disk can answer
1631
+ * them differently.
1632
+ */
1633
+ function listingQuestion(project, options) {
1634
+ return JSON.stringify([
1635
+ project.path,
1636
+ project.root,
1637
+ options.binary ?? null,
1638
+ options.compilerArgsCwd ?? null,
1639
+ options.env ?? null,
1640
+ options.passthrough ?? null,
1641
+ options.pinCompilerProject ?? null,
1642
+ ]);
1643
+ }
1381
1644
  function resolveWatchTopology(options, extraInputs, identities, getCompilerInputs) {
1382
1645
  const selection = CompilerProjectSelection_1.CompilerProjectSelection.read(options);
1383
1646
  if (selection.inspectionError !== undefined)
@@ -1506,10 +1769,11 @@ function readProjectReferences(tsconfig) {
1506
1769
  return references;
1507
1770
  }
1508
1771
  function listCompilerInputs(project, options) {
1772
+ const env = SidecarEnvironment_1.SidecarEnvironment.merge(process.env, options.env);
1509
1773
  const tsgo = (0, resolveTsgo_1.resolveTsgo)({
1510
1774
  binary: options.binary,
1511
1775
  cwd: project.root,
1512
- env: options.env,
1776
+ env,
1513
1777
  });
1514
1778
  const result = (0, spawnNative_1.spawnNative)(tsgo.binary, [
1515
1779
  "-p",
@@ -1521,7 +1785,7 @@ function listCompilerInputs(project, options) {
1521
1785
  ...(options.pinCompilerProject === true ? ["-p", project.path] : []),
1522
1786
  ], {
1523
1787
  cwd: options.compilerArgsCwd ?? project.root,
1524
- env: { ...process.env, ...options.env },
1788
+ env,
1525
1789
  encoding: "utf8",
1526
1790
  });
1527
1791
  if (result.error) {
@@ -1811,8 +2075,7 @@ function uniqueExistingPaths(paths, identities) {
1811
2075
  }
1812
2076
  return [...unique.values()];
1813
2077
  }
1814
- function normalizeProjectInputSnapshot(snapshot) {
1815
- const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
2078
+ function normalizeProjectInputSnapshot(snapshot, identities) {
1816
2079
  const files = new Map();
1817
2080
  const globs = new Map();
1818
2081
  const reloadDirectories = new Map();
@@ -1841,8 +2104,7 @@ function normalizeProjectInputSnapshot(snapshot) {
1841
2104
  root: identities.resolve(snapshot.root).path,
1842
2105
  };
1843
2106
  }
1844
- function projectInputSnapshotsEqual(left, right) {
1845
- const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
2107
+ function projectInputSnapshotsEqual(left, right, identities) {
1846
2108
  return (
1847
2109
  // Every sibling comparison in this class decides on identity, and this one
1848
2110
  // is only safe today because both operands come from the same normalizer.
@@ -1885,7 +2147,7 @@ function isSymbolicLink(location) {
1885
2147
  return false;
1886
2148
  }
1887
2149
  }
1888
- function matchesProjectInput(snapshot, location, identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)()) {
2150
+ function matchesProjectInput(snapshot, location, identities) {
1889
2151
  const key = identities.resolve(location).key;
1890
2152
  return (snapshot.files.some((file) => identities.resolve(file).key === key) ||
1891
2153
  (snapshot.reloadFiles ?? []).some((file) => identities.resolve(file).key === key) ||
@@ -1971,8 +2233,7 @@ function compilerMembershipChange(previous, next) {
1971
2233
  }
1972
2234
  return [...changed.values()].sort();
1973
2235
  }
1974
- function projectInputCompilerMembershipChange(snapshot, changed) {
1975
- const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
2236
+ function projectInputCompilerMembershipChange(snapshot, changed, identities) {
1976
2237
  return changed.filter((location) => matchesProjectInput(snapshot, location, identities) &&
1977
2238
  ProjectInputWatchRules_1.ProjectInputWatchRules.projectInputPathMayAffectProgram(location));
1978
2239
  }
@@ -1985,8 +2246,7 @@ function projectInputCompilerMembershipChange(snapshot, changed) {
1985
2246
  * immediate-entry delta instead of turning ordinary project data into a cold
1986
2247
  * selection reload. The deepest matching glob owns the most specific delivery.
1987
2248
  */
1988
- function projectInputCompilerMembershipProjectChanges(locations, globs) {
1989
- const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
2249
+ function projectInputCompilerMembershipProjectChanges(locations, globs, identities) {
1990
2250
  const roots = globs.map((glob) => identities.resolve((0, literalGlobRoot_1.literalGlobRoot)(glob)));
1991
2251
  const changes = new Map();
1992
2252
  for (const location of locations) {
@@ -2003,14 +2263,39 @@ function projectInputCompilerMembershipProjectChanges(locations, globs) {
2003
2263
  }
2004
2264
  return [...changes.values()].sort();
2005
2265
  }
2006
- function fingerprintProjectInputMatches(matches, readDirectory) {
2266
+ /**
2267
+ * Fingerprint every matched project input: a directory by its entries, a file
2268
+ * by its bytes.
2269
+ *
2270
+ * Without `previous`, every file is read. With it, a file keeps its previous
2271
+ * digest while its metadata and physical owner hold, unless it is the `named`
2272
+ * path, whose bytes are always read: a notification that names a file is the
2273
+ * one whose same-size rewrite within a timestamp tick metadata cannot show.
2274
+ */
2275
+ function fingerprintProjectInputMatches(matches, readDirectory, previous) {
2007
2276
  const fingerprints = new Map();
2277
+ const snapshots = new Map();
2008
2278
  for (const [key, location] of matches) {
2009
- fingerprints.set(key, WatchPaths_1.WatchPaths.isDirectory(location)
2010
- ? fingerprintProjectInputDirectory(location, readDirectory)
2011
- : fingerprintProjectInputFile(location));
2012
- }
2013
- return fingerprints;
2279
+ if (WatchPaths_1.WatchPaths.isDirectory(location)) {
2280
+ fingerprints.set(key, fingerprintProjectInputDirectory(location, readDirectory));
2281
+ continue;
2282
+ }
2283
+ const metadata = compilerFileSnapshot(location, "");
2284
+ const before = previous?.snapshots.get(key);
2285
+ const kept = previous?.fingerprints.get(key);
2286
+ const fingerprint = previous !== undefined &&
2287
+ key !== previous.named &&
2288
+ before !== undefined &&
2289
+ kept !== undefined &&
2290
+ metadata.content !== "" &&
2291
+ before.content === metadata.content &&
2292
+ before.owner === metadata.owner
2293
+ ? kept
2294
+ : fingerprintProjectInputFile(location);
2295
+ fingerprints.set(key, fingerprint);
2296
+ snapshots.set(key, { ...metadata, fingerprint });
2297
+ }
2298
+ return { fingerprints, snapshots };
2014
2299
  }
2015
2300
  function fingerprintProjectInputFile(location) {
2016
2301
  try {
@@ -2052,7 +2337,7 @@ function fingerprintProjectInputDirectory(location, readDirectory) {
2052
2337
  return "";
2053
2338
  }
2054
2339
  }
2055
- function matchesProjectInputGlob(pattern, location, identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)()) {
2340
+ function matchesProjectInputGlob(pattern, location, identities) {
2056
2341
  const root = identities.resolve((0, literalGlobRoot_1.literalGlobRoot)(pattern));
2057
2342
  const candidate = identities.resolve(location);
2058
2343
  if (!(0, isProjectInputPathIdentityWithin_1.isProjectInputPathIdentityWithin)(root.key, candidate.key))