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.
- package/cmd/ttsc/compiler_argument_cwd_test.go +38 -38
- package/cmd/ttscserver/main.go +2 -12
- package/cmd/ttscserver/resolve_launch_uses_native_flag_authority_test.go +7 -7
- package/driver/lsp.go +4 -43
- package/driver/rewrite.go +1 -1
- package/internal/e2etrace/completion_hints.go +3 -3
- package/internal/e2etrace/completion_hints_test.go +26 -12
- package/internal/graph/build.go +5 -6
- package/internal/graph/graph.go +3 -3
- package/internal/graph/line_model.go +2 -35
- package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +13 -36
- package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +9 -3
- package/internal/lspserver/lsp_plugin.go +19 -0
- package/internal/lspserver/lsp_proxy.go +9 -129
- package/internal/lspserver/lsp_server.go +0 -8
- package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +6 -36
- package/internal/sourceprocess/windows_target_owns_suspended_admission_test.go +5 -5
- package/lib/TtscCompiler.js +1 -1
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/ResidentCheckProcess.d.ts +74 -96
- package/lib/compiler/internal/ResidentCheckProcess.js +150 -335
- package/lib/compiler/internal/ResidentCheckProcess.js.map +1 -1
- package/lib/compiler/internal/ResidentCheckProcessHost.d.ts +127 -0
- package/lib/compiler/internal/ResidentCheckProcessHost.js +447 -0
- package/lib/compiler/internal/ResidentCheckProcessHost.js.map +1 -0
- package/lib/compiler/internal/ResidentCheckProcessProtocol.d.ts +64 -0
- package/lib/compiler/internal/ResidentCheckProcessProtocol.js +3 -0
- package/lib/compiler/internal/ResidentCheckProcessProtocol.js.map +1 -0
- package/lib/compiler/internal/build/BuildExecution.d.ts +103 -1
- package/lib/compiler/internal/build/BuildExecution.js +92 -12
- package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
- package/lib/compiler/internal/build/ResidentCheckWatchSession.d.ts +16 -11
- package/lib/compiler/internal/build/ResidentCheckWatchSession.js +37 -21
- package/lib/compiler/internal/build/ResidentCheckWatchSession.js.map +1 -1
- package/lib/compiler/internal/build/RunBuildOptions.d.ts +6 -5
- package/lib/compiler/internal/build/TsgoArguments.js.map +1 -1
- package/lib/compiler/internal/build/WatchInputPublication.d.ts +59 -0
- package/lib/compiler/internal/build/WatchInputPublication.js +93 -0
- package/lib/compiler/internal/build/WatchInputPublication.js.map +1 -0
- package/lib/compiler/internal/build/runBuild.d.ts +29 -2
- package/lib/compiler/internal/build/runBuild.js +45 -19
- package/lib/compiler/internal/build/runBuild.js.map +1 -1
- package/lib/compiler/internal/buildNativeCompiler.d.ts +2 -0
- package/lib/compiler/internal/buildNativeCompiler.js +1 -0
- package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
- package/lib/compiler/internal/compileProjectInMemory.js +4 -2
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/project/CompilerProjectSelection.d.ts +9 -8
- package/lib/compiler/internal/project/CompilerProjectSelection.js +11 -9
- package/lib/compiler/internal/project/CompilerProjectSelection.js.map +1 -1
- package/lib/compiler/internal/project/resolveOwningProjectConfig.d.ts +25 -8
- package/lib/compiler/internal/project/resolveOwningProjectConfig.js +26 -14
- package/lib/compiler/internal/project/resolveOwningProjectConfig.js.map +1 -1
- package/lib/compiler/internal/readEffectiveCompilerOptions.js +1 -1
- package/lib/compiler/internal/readEffectiveCompilerOptions.js.map +1 -1
- package/lib/compiler/internal/resolveBinary.d.ts +1 -0
- package/lib/compiler/internal/resolveBinary.js.map +1 -1
- package/lib/compiler/internal/resolveTsgo.d.ts +6 -0
- package/lib/compiler/internal/resolveTsgo.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.d.ts +32 -4
- package/lib/compiler/internal/runSingleFileEmit.js +124 -89
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.js +8 -2
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +11 -3
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/transformProjectInWorker.d.ts +1 -1
- package/lib/compiler/internal/transformProjectInWorker.js +3 -2
- package/lib/compiler/internal/transformProjectInWorker.js.map +1 -1
- package/lib/flags/ParseOptions.d.ts +2 -2
- package/lib/internal/E2ETrace.js.map +1 -1
- package/lib/internal/OwnedNativeProcess.d.ts +2 -2
- package/lib/internal/OwnedNativeProcess.js +2 -2
- package/lib/internal/SourceNativeRetirement.d.ts +8 -7
- package/lib/internal/SourceNativeRetirement.js +19 -10
- package/lib/internal/SourceNativeRetirement.js.map +1 -1
- package/lib/internal/TtsxNodeOptions.d.ts +79 -0
- package/lib/internal/TtsxNodeOptions.js +121 -0
- package/lib/internal/TtsxNodeOptions.js.map +1 -0
- package/lib/internal/javascriptRuntimeCapabilities.js +4 -1
- package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
- package/lib/internal/parseWindowsDirectoryCaseSensitivity.d.ts +5 -5
- package/lib/internal/parseWindowsDirectoryCaseSensitivity.js +5 -5
- package/lib/internal/pathIdentity/createCaseSensitivityObservations.d.ts +35 -0
- package/lib/internal/pathIdentity/createCaseSensitivityObservations.js +209 -0
- package/lib/internal/pathIdentity/createCaseSensitivityObservations.js.map +1 -0
- package/lib/internal/pathIdentity/createFilesystemPathIdentityContext.js +2 -143
- package/lib/internal/pathIdentity/createFilesystemPathIdentityContext.js.map +1 -1
- package/lib/internal/projectInputPathIdentity.d.ts +1 -0
- package/lib/internal/projectInputPathIdentity.js +1 -0
- package/lib/internal/projectInputPathIdentity.js.map +1 -1
- package/lib/internal/workerModuleUrl.d.ts +28 -0
- package/lib/internal/workerModuleUrl.js +40 -0
- package/lib/internal/workerModuleUrl.js.map +1 -0
- package/lib/launcher/internal/buildSingleRootProject.js +2 -1
- package/lib/launcher/internal/buildSingleRootProject.js.map +1 -1
- package/lib/launcher/internal/getCompilerVersionText.js +3 -2
- package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
- package/lib/launcher/internal/parseTtsxCLI.d.ts +5 -4
- package/lib/launcher/internal/parseTtsxCLI.js +5 -4
- package/lib/launcher/internal/parseTtsxCLI.js.map +1 -1
- package/lib/launcher/internal/prepareExecution.js +2 -1
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/resolveSingleFileOutput.d.ts +8 -9
- package/lib/launcher/internal/resolveSingleFileOutput.js +8 -9
- package/lib/launcher/internal/resolveSingleFileOutput.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +91 -41
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +5 -2
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtime/CommonJsRuntimeSource.d.ts +10 -6
- package/lib/launcher/internal/runtime/CommonJsRuntimeSource.js +11 -9
- package/lib/launcher/internal/runtime/CommonJsRuntimeSource.js.map +1 -1
- package/lib/launcher/internal/runtime/RuntimeEntrySources.d.ts +40 -0
- package/lib/launcher/internal/runtime/RuntimeEntrySources.js +57 -0
- package/lib/launcher/internal/runtime/RuntimeEntrySources.js.map +1 -0
- package/lib/launcher/internal/runtime/RuntimeProjectOwnership.d.ts +5 -4
- package/lib/launcher/internal/runtime/RuntimeProjectOwnership.js +8 -6
- package/lib/launcher/internal/runtime/RuntimeProjectOwnership.js.map +1 -1
- package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.d.ts +20 -0
- package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.js +27 -0
- package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.js.map +1 -0
- package/lib/launcher/internal/runtime/commonJsExportNames.d.ts +2 -1
- package/lib/launcher/internal/runtime/commonJsExportNames.js +2 -1
- package/lib/launcher/internal/runtime/commonJsExportNames.js.map +1 -1
- package/lib/launcher/internal/runtime/installRuntimeHooks.d.ts +5 -3
- package/lib/launcher/internal/runtime/installRuntimeHooks.js +42 -12
- package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
- package/lib/launcher/internal/runtime/resolveRuntimeCleanTargets.js +3 -1
- package/lib/launcher/internal/runtime/resolveRuntimeCleanTargets.js.map +1 -1
- package/lib/launcher/internal/watch/AsyncWatchTopology.d.ts +113 -0
- package/lib/launcher/internal/watch/AsyncWatchTopology.js +232 -0
- package/lib/launcher/internal/watch/AsyncWatchTopology.js.map +1 -0
- package/lib/launcher/internal/watch/WatchSettlement.d.ts +49 -0
- package/lib/launcher/internal/watch/WatchSettlement.js +70 -0
- package/lib/launcher/internal/watch/WatchSettlement.js.map +1 -0
- package/lib/launcher/internal/watch/WatchTopology.d.ts +103 -13
- package/lib/launcher/internal/watch/WatchTopology.js +380 -95
- package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
- package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.d.ts +121 -0
- package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.js +3 -0
- package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.js.map +1 -0
- package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.d.ts +4 -1
- package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.js +3 -2
- package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.js.map +1 -1
- package/lib/launcher/internal/watch/serveWatchTopologyWorker.d.ts +19 -0
- package/lib/launcher/internal/watch/serveWatchTopologyWorker.js +62 -0
- package/lib/launcher/internal/watch/serveWatchTopologyWorker.js.map +1 -0
- package/lib/launcher/internal/watch/watchTopologyWorker.d.ts +1 -0
- package/lib/launcher/internal/watch/watchTopologyWorker.js +22 -0
- package/lib/launcher/internal/watch/watchTopologyWorker.js.map +1 -0
- package/lib/plugin/CapabilityPluginResolver.d.ts +1 -1
- package/lib/plugin/CapabilityPluginResolver.js +3 -2
- package/lib/plugin/CapabilityPluginResolver.js.map +1 -1
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +15 -12
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +13 -11
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
- package/lib/plugin/internal/load/PluginPackageResolution.d.ts +2 -2
- package/lib/plugin/internal/load/PluginPackageResolution.js +13 -7
- package/lib/plugin/internal/load/PluginPackageResolution.js.map +1 -1
- package/lib/plugin/internal/load/ProjectPluginEntries.d.ts +23 -7
- package/lib/plugin/internal/load/ProjectPluginEntries.js +36 -14
- package/lib/plugin/internal/load/ProjectPluginEntries.js.map +1 -1
- package/lib/plugin/internal/load/collectProjectHostInputs.d.ts +3 -1
- package/lib/plugin/internal/load/collectProjectHostInputs.js +5 -2
- package/lib/plugin/internal/load/collectProjectHostInputs.js.map +1 -1
- package/lib/plugin/internal/load/hasProjectPluginEntries.d.ts +3 -1
- package/lib/plugin/internal/load/hasProjectPluginEntries.js +4 -2
- package/lib/plugin/internal/load/hasProjectPluginEntries.js.map +1 -1
- package/lib/plugin/internal/load/loadProjectPlugins.d.ts +36 -5
- package/lib/plugin/internal/load/loadProjectPlugins.js +477 -421
- package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
- package/lib/plugin/internal/load/pluginResolutionFailure.d.ts +18 -0
- package/lib/plugin/internal/load/pluginResolutionFailure.js +32 -0
- package/lib/plugin/internal/load/pluginResolutionFailure.js.map +1 -0
- package/lib/plugin/internal/load/resolveNativeSource.js +2 -3
- package/lib/plugin/internal/load/resolveNativeSource.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +3 -2
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +4 -1
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +9 -2
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
- package/lib/structures/ITtscCompilerContext.d.ts +12 -12
- package/lib/structures/ITtscPlugin.d.ts +7 -5
- package/lib/structures/ITtscPluginFactoryContext.d.ts +9 -8
- package/lib/structures/internal/TtscCommonOptions.d.ts +2 -2
- package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +12 -6
- package/package.json +9 -9
- package/src/TtscCompiler.ts +1 -1
- package/src/compiler/internal/ResidentCheckProcess.ts +159 -374
- package/src/compiler/internal/ResidentCheckProcessHost.ts +502 -0
- package/src/compiler/internal/ResidentCheckProcessProtocol.ts +53 -0
- package/src/compiler/internal/build/BuildExecution.ts +123 -11
- package/src/compiler/internal/build/ResidentCheckWatchSession.ts +41 -24
- package/src/compiler/internal/build/RunBuildOptions.ts +9 -5
- package/src/compiler/internal/build/TsgoArguments.ts +4 -1
- package/src/compiler/internal/build/WatchInputPublication.ts +106 -0
- package/src/compiler/internal/build/runBuild.ts +58 -34
- package/src/compiler/internal/buildNativeCompiler.ts +4 -0
- package/src/compiler/internal/compileProjectInMemory.ts +8 -2
- package/src/compiler/internal/project/CompilerProjectSelection.ts +14 -14
- package/src/compiler/internal/project/resolveOwningProjectConfig.ts +42 -16
- package/src/compiler/internal/readEffectiveCompilerOptions.ts +1 -1
- package/src/compiler/internal/resolveBinary.ts +4 -1
- package/src/compiler/internal/resolveTsgo.ts +7 -0
- package/src/compiler/internal/runSingleFileEmit.ts +145 -101
- package/src/compiler/internal/startResidentTransform.ts +9 -2
- package/src/compiler/internal/transformProjectInMemory.ts +12 -3
- package/src/compiler/internal/transformProjectInWorker.ts +10 -2
- package/src/flags/ParseOptions.ts +2 -2
- package/src/internal/E2ETrace.ts +3 -1
- package/src/internal/OwnedNativeProcess.ts +2 -2
- package/src/internal/SourceNativeRetirement.ts +42 -19
- package/src/internal/TtsxNodeOptions.ts +124 -0
- package/src/internal/javascriptRuntimeCapabilities.ts +6 -1
- package/src/internal/parseWindowsDirectoryCaseSensitivity.ts +5 -5
- package/src/internal/pathIdentity/createCaseSensitivityObservations.ts +242 -0
- package/src/internal/pathIdentity/createFilesystemPathIdentityContext.ts +2 -169
- package/src/internal/projectInputPathIdentity.ts +1 -0
- package/src/internal/workerModuleUrl.ts +34 -0
- package/src/launcher/internal/buildSingleRootProject.ts +2 -0
- package/src/launcher/internal/getCompilerVersionText.ts +3 -2
- package/src/launcher/internal/parseTtsxCLI.ts +5 -4
- package/src/launcher/internal/prepareExecution.ts +6 -1
- package/src/launcher/internal/resolveSingleFileOutput.ts +8 -9
- package/src/launcher/internal/runTtsc.ts +127 -49
- package/src/launcher/internal/runTtsx.ts +17 -8
- package/src/launcher/internal/runtime/CommonJsRuntimeSource.ts +11 -9
- package/src/launcher/internal/runtime/RuntimeEntrySources.ts +57 -0
- package/src/launcher/internal/runtime/RuntimeProjectOwnership.ts +23 -10
- package/src/launcher/internal/runtime/advertiseTypeScriptExtensions.ts +25 -0
- package/src/launcher/internal/runtime/commonJsExportNames.ts +11 -4
- package/src/launcher/internal/runtime/installRuntimeHooks.ts +62 -16
- package/src/launcher/internal/runtime/resolveRuntimeCleanTargets.ts +17 -5
- package/src/launcher/internal/watch/AsyncWatchTopology.ts +267 -0
- package/src/launcher/internal/watch/WatchSettlement.ts +67 -0
- package/src/launcher/internal/watch/WatchTopology.ts +461 -102
- package/src/launcher/internal/watch/WatchTopologyWorkerProtocol.ts +106 -0
- package/src/launcher/internal/watch/projectInputReloadEventShouldNotify.ts +14 -9
- package/src/launcher/internal/watch/serveWatchTopologyWorker.ts +75 -0
- package/src/launcher/internal/watch/watchTopologyWorker.ts +29 -0
- package/src/plugin/CapabilityPluginResolver.ts +9 -2
- package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +17 -12
- package/src/plugin/internal/load/PluginPackageResolution.ts +14 -9
- package/src/plugin/internal/load/ProjectPluginEntries.ts +40 -15
- package/src/plugin/internal/load/collectProjectHostInputs.ts +7 -1
- package/src/plugin/internal/load/hasProjectPluginEntries.ts +8 -3
- package/src/plugin/internal/load/loadProjectPlugins.ts +683 -593
- package/src/plugin/internal/load/pluginResolutionFailure.ts +37 -0
- package/src/plugin/internal/load/resolveNativeSource.ts +10 -4
- package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +8 -2
- package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +10 -1
- package/src/structures/ITtscCompilerContext.ts +12 -12
- package/src/structures/ITtscPlugin.ts +7 -5
- package/src/structures/ITtscPluginFactoryContext.ts +9 -8
- package/src/structures/internal/TtscCommonOptions.ts +2 -2
- package/src/structures/internal/TtscSingleFileEmitOptions.ts +22 -11
- package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +0 -78
- package/internal/graphsymbols/provider.go +0 -650
- package/internal/lspserver/lsp_symbols.go +0 -212
|
@@ -7,10 +7,12 @@ import { CompilerProjectSelection } from "../../../compiler/internal/project/Com
|
|
|
7
7
|
import { readJsoncFile } from "../../../compiler/internal/project/readJsoncFile";
|
|
8
8
|
import { readProjectConfig } from "../../../compiler/internal/project/readProjectConfig";
|
|
9
9
|
import { resolveTsgo } from "../../../compiler/internal/resolveTsgo";
|
|
10
|
+
import { SidecarEnvironment } from "../../../compiler/internal/sharedHost/SidecarEnvironment";
|
|
10
11
|
import { spawnNative } from "../../../compiler/internal/spawnNative";
|
|
11
12
|
import { normalizeCompilerEnumValue } from "../../../flags/normalizeCompilerEnumValue";
|
|
12
13
|
import { readCompilerOptionValues } from "../../../flags/readCompilerOptionValues";
|
|
13
14
|
import { type ProjectInputPathIdentityContext } from "../../../internal/pathIdentity/ProjectInputPathIdentityContext";
|
|
15
|
+
import { createCaseSensitivityObservations } from "../../../internal/pathIdentity/createCaseSensitivityObservations";
|
|
14
16
|
import { createProjectInputPathIdentityContext } from "../../../internal/pathIdentity/createProjectInputPathIdentityContext";
|
|
15
17
|
import { isProjectInputPathIdentityWithin } from "../../../internal/pathIdentity/isProjectInputPathIdentityWithin";
|
|
16
18
|
import { resolveProjectInputPath } from "../../../internal/pathIdentity/resolveProjectInputPath";
|
|
@@ -46,26 +48,26 @@ import { watchDirectory } from "./watchDirectory";
|
|
|
46
48
|
* output for source and declaration membership. An explicit reader supplies the
|
|
47
49
|
* same absolute-path membership and failure contract for each resolved project.
|
|
48
50
|
* Configuration files, project-reference roots, output inference and native
|
|
49
|
-
* filesystem identity remain this topology's responsibility.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
51
|
+
* filesystem identity remain this topology's responsibility. Response frames
|
|
52
|
+
* are observed once per project refresh at its compiler cwd; that option view
|
|
53
|
+
* serves all project-output queries while the original argv reaches compiler
|
|
54
|
+
* membership. A second observation rejects response changes across membership
|
|
55
|
+
* resolution. Response files join the config reload set; these observations do
|
|
56
|
+
* not pin files or detect a change restored in between.
|
|
55
57
|
*
|
|
56
58
|
* `TTSC_WATCH_DEBUG_INPUTS` reports the named event, observed population deltas
|
|
57
59
|
* and reload decision. These diagnostics reuse the decision's existing inputs;
|
|
58
60
|
* they perform no extra filesystem observations.
|
|
59
61
|
*
|
|
60
62
|
* @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.
|
|
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.
|
|
63
|
+
* @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.
|
|
62
64
|
* @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.
|
|
63
65
|
* @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.
|
|
64
66
|
* @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.
|
|
65
67
|
*
|
|
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. 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
|
|
68
|
-
* @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.
|
|
68
|
+
* @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.
|
|
69
|
+
* @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.
|
|
70
|
+
* @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.
|
|
69
71
|
*/
|
|
70
72
|
export class WatchTopology {
|
|
71
73
|
private analysisOnly = false;
|
|
@@ -102,15 +104,42 @@ export class WatchTopology {
|
|
|
102
104
|
/** The plugin inputs whose directories the last sync already watched. */
|
|
103
105
|
private watchedExtraInputs = new Set<string>();
|
|
104
106
|
private compilerFileSnapshots = new Map<string, CompilerFileSnapshot>();
|
|
107
|
+
|
|
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
|
+
private compilerListings = new Map<string, CompilerListing>();
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Notifications the current poll iteration delivered, decided together in the
|
|
117
|
+
* check phase (`deferNotification`).
|
|
118
|
+
*/
|
|
119
|
+
private pendingNotifications = new Map<string, PendingNotification>();
|
|
120
|
+
private notificationsScheduled = false;
|
|
105
121
|
private files = new Map<string, string>();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The current decision's directory case-policy observations, which every
|
|
125
|
+
* identity context the decision creates shares (samchon/ttsc#1742).
|
|
126
|
+
*/
|
|
127
|
+
private caseObservations!: ReturnType<
|
|
128
|
+
typeof createCaseSensitivityObservations
|
|
129
|
+
>;
|
|
130
|
+
private lexicalIdentities!: ProjectInputPathIdentityContext;
|
|
109
131
|
private fileWatchers = new Map<string, fs.FSWatcher>();
|
|
110
132
|
private observedDirectories = new Map<string, string>();
|
|
111
133
|
private outputFiles = new Map<string, string>();
|
|
112
134
|
private outputs = new Map<string, string>();
|
|
113
135
|
private projectInputFingerprints = new Map<string, string>();
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The metadata and physical owner each matched project-input file had when
|
|
139
|
+
* its fingerprint was taken, by match key; a named notification re-reads only
|
|
140
|
+
* the files whose metadata moved (samchon/ttsc#1748).
|
|
141
|
+
*/
|
|
142
|
+
private projectInputFileSnapshots = new Map<string, CompilerFileSnapshot>();
|
|
114
143
|
private projectInputMatches = new Map<string, string>();
|
|
115
144
|
private projectInputs: ITtscProjectInputSnapshot = {
|
|
116
145
|
files: [],
|
|
@@ -154,6 +183,8 @@ export class WatchTopology {
|
|
|
154
183
|
* membership; the default invokes the native compiler. Readers throw
|
|
155
184
|
* listing failures and retain ownership of their returned array, which this
|
|
156
185
|
* topology does not mutate.
|
|
186
|
+
* @param observeCaseSensitivity Create one decision's directory case-policy
|
|
187
|
+
* observations; the default asks the native filesystem.
|
|
157
188
|
*/
|
|
158
189
|
public constructor(
|
|
159
190
|
private readonly options: WatchTopologyOptions,
|
|
@@ -162,7 +193,10 @@ export class WatchTopology {
|
|
|
162
193
|
private readonly openFileWatch: typeof fs.watch = fs.watch,
|
|
163
194
|
private readonly readDirectory: typeof fs.readdirSync = fs.readdirSync,
|
|
164
195
|
private readonly getCompilerInputs: typeof listCompilerInputs = listCompilerInputs,
|
|
165
|
-
|
|
196
|
+
private readonly observeCaseSensitivity: typeof createCaseSensitivityObservations = createCaseSensitivityObservations,
|
|
197
|
+
) {
|
|
198
|
+
this.beginLexicalTransaction();
|
|
199
|
+
}
|
|
166
200
|
|
|
167
201
|
/**
|
|
168
202
|
* Re-resolve compiler inputs and notify when the resolved topology changes,
|
|
@@ -183,17 +217,67 @@ export class WatchTopology {
|
|
|
183
217
|
this.refreshCompilerInputs(notify, false);
|
|
184
218
|
}
|
|
185
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Wait until delivered notifications and registration reconciliation finish.
|
|
222
|
+
*
|
|
223
|
+
* A completed build does not imply a quiet topology: its final refresh can
|
|
224
|
+
* queue another decision and another build (#1756). Yield through the check
|
|
225
|
+
* phase before inspecting every scheduled owner so a decision queued by
|
|
226
|
+
* another decision remains part of this barrier. Later filesystem changes
|
|
227
|
+
* belong to a later barrier; this does not freeze the filesystem.
|
|
228
|
+
*
|
|
229
|
+
* @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.
|
|
230
|
+
* @evidence contracts/common.md#clear-and-simple-design The topology owns the barrier because it alone owns all five scheduled queues.
|
|
231
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts Queue state establishes completion without a quiet-time estimate or an invented filesystem snapshot.
|
|
232
|
+
* @evidence contracts/common.md#meaningful-documentation Native prose distinguishes delivered work from later mutations and explains the post-build queued-decision race.
|
|
233
|
+
* @evidenceExclude contracts/portability.md#os-neutral-implementation The barrier observes owned JavaScript queues without a separate native operation.
|
|
234
|
+
* @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.
|
|
235
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work Each call observes current work rather than reusing an earlier completion.
|
|
236
|
+
* @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.
|
|
237
|
+
*/
|
|
238
|
+
public async whenIdle(): Promise<void> {
|
|
239
|
+
do {
|
|
240
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
241
|
+
} while (
|
|
242
|
+
!this.closed &&
|
|
243
|
+
(this.notificationsScheduled ||
|
|
244
|
+
this.pluginNotificationsScheduled ||
|
|
245
|
+
this.compilerPostRegistrationReconciliationScheduled ||
|
|
246
|
+
this.projectInputRecoveryScheduled ||
|
|
247
|
+
this.projectInputPostRegistrationReconciliationScheduled ||
|
|
248
|
+
this.pendingNotifications.size !== 0 ||
|
|
249
|
+
this.pendingPluginNotifications.size !== 0)
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @param reuseListings Answer each project's membership with its last listing
|
|
255
|
+
* under the same arguments instead of asking the compiler again. Only a
|
|
256
|
+
* refresh whose cause cannot move Program membership passes it.
|
|
257
|
+
*/
|
|
186
258
|
private refreshCompilerInputs(
|
|
187
259
|
notify: boolean,
|
|
188
260
|
skipUnobservedProjectInputWatchRoots: boolean,
|
|
261
|
+
reuseListings: boolean = false,
|
|
189
262
|
): void {
|
|
190
263
|
this.beginLexicalTransaction();
|
|
264
|
+
const listings = new Map<string, CompilerListing>();
|
|
191
265
|
const next = resolveWatchTopology(
|
|
192
266
|
this.options,
|
|
193
267
|
this.extraInputs,
|
|
194
268
|
this.lexicalIdentities,
|
|
195
|
-
|
|
269
|
+
(project, options) => {
|
|
270
|
+
const question = listingQuestion(project, options);
|
|
271
|
+
const last = this.compilerListings.get(project.path);
|
|
272
|
+
const inputs =
|
|
273
|
+
reuseListings && last?.question === question
|
|
274
|
+
? last.inputs
|
|
275
|
+
: this.getCompilerInputs(project, options);
|
|
276
|
+
listings.set(project.path, { inputs, question });
|
|
277
|
+
return inputs;
|
|
278
|
+
},
|
|
196
279
|
);
|
|
280
|
+
this.compilerListings = listings;
|
|
197
281
|
const compilerProgramMembershipChange =
|
|
198
282
|
next.analysisOnly &&
|
|
199
283
|
WatchPaths.mapsEqual(this.reloadFiles, next.reloadFiles) &&
|
|
@@ -204,6 +288,7 @@ export class WatchTopology {
|
|
|
204
288
|
const projectInputProgramOverlap = projectInputCompilerMembershipChange(
|
|
205
289
|
this.projectInputs,
|
|
206
290
|
compilerProgramMembershipChange,
|
|
291
|
+
this.projectInputIdentities(),
|
|
207
292
|
);
|
|
208
293
|
const projectInputProgramChange =
|
|
209
294
|
compilerProgramMembershipChange.length !== 0 &&
|
|
@@ -293,7 +378,7 @@ export class WatchTopology {
|
|
|
293
378
|
changed: readonly string[],
|
|
294
379
|
): boolean {
|
|
295
380
|
const matches = this.collectProjectInputMatches();
|
|
296
|
-
const fingerprints = fingerprintProjectInputMatches(
|
|
381
|
+
const { fingerprints, snapshots } = fingerprintProjectInputMatches(
|
|
297
382
|
matches,
|
|
298
383
|
this.readDirectory,
|
|
299
384
|
);
|
|
@@ -307,6 +392,7 @@ export class WatchTopology {
|
|
|
307
392
|
const causedBy = projectInputCompilerMembershipProjectChanges(
|
|
308
393
|
changed,
|
|
309
394
|
population.globs,
|
|
395
|
+
this.projectInputIdentities(),
|
|
310
396
|
);
|
|
311
397
|
const reloadInput = {
|
|
312
398
|
causedBy,
|
|
@@ -316,7 +402,10 @@ export class WatchTopology {
|
|
|
316
402
|
reloadDirectories: population.reloadDirectories,
|
|
317
403
|
reloadFiles: population.reloadFiles ?? [],
|
|
318
404
|
};
|
|
319
|
-
const reload = projectInputReloadEventShouldNotify(
|
|
405
|
+
const reload = projectInputReloadEventShouldNotify(
|
|
406
|
+
reloadInput,
|
|
407
|
+
this.projectInputIdentities(),
|
|
408
|
+
);
|
|
320
409
|
reportProjectInputDecision({
|
|
321
410
|
source: "compiler-membership",
|
|
322
411
|
...reloadInput,
|
|
@@ -328,6 +417,7 @@ export class WatchTopology {
|
|
|
328
417
|
// instead of disappearing behind the warm compiler-membership handoff.
|
|
329
418
|
this.projectInputMatches = matches;
|
|
330
419
|
this.projectInputFingerprints = fingerprints;
|
|
420
|
+
this.projectInputFileSnapshots = snapshots;
|
|
331
421
|
return reload;
|
|
332
422
|
}
|
|
333
423
|
|
|
@@ -353,8 +443,8 @@ export class WatchTopology {
|
|
|
353
443
|
* @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.
|
|
354
444
|
* @evidence contracts/common.md#meaningful-documentation Native prose explains baseline timing and why existing inputs are not restamped following the documentation skill.
|
|
355
445
|
* @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.
|
|
356
|
-
* @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;
|
|
357
|
-
* @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.
|
|
446
|
+
* @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.
|
|
447
|
+
* @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.
|
|
358
448
|
* @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.
|
|
359
449
|
*/
|
|
360
450
|
public setExtraInputs(
|
|
@@ -382,7 +472,12 @@ export class WatchTopology {
|
|
|
382
472
|
);
|
|
383
473
|
}
|
|
384
474
|
this.pluginInputStates = states;
|
|
385
|
-
|
|
475
|
+
// Plugin inputs join the watched files and directories, never Program
|
|
476
|
+
// membership, so the compiler's last answer stands. A load reports up to
|
|
477
|
+
// three different sets, and asking again for each spawned tsgo three times
|
|
478
|
+
// per load (samchon/ttsc#1741). A membership change meanwhile reaches the
|
|
479
|
+
// refresh its own notification starts, which lists again.
|
|
480
|
+
this.refreshCompilerInputs(false, false, true);
|
|
386
481
|
}
|
|
387
482
|
|
|
388
483
|
/**
|
|
@@ -399,7 +494,11 @@ export class WatchTopology {
|
|
|
399
494
|
* @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.
|
|
400
495
|
*/
|
|
401
496
|
public setProjectInputs(inputs: ITtscProjectInputSnapshot): void {
|
|
402
|
-
|
|
497
|
+
this.beginLexicalTransaction();
|
|
498
|
+
const next = normalizeProjectInputSnapshot(
|
|
499
|
+
inputs,
|
|
500
|
+
this.projectInputIdentities(),
|
|
501
|
+
);
|
|
403
502
|
// The declared spellings are recorded even when the normalized snapshot did
|
|
404
503
|
// not move, because a republication can carry a new alias for identities
|
|
405
504
|
// that already matched, and anchoring the spelling that was retired would
|
|
@@ -408,7 +507,13 @@ export class WatchTopology {
|
|
|
408
507
|
inputs.declared === undefined
|
|
409
508
|
? inputs
|
|
410
509
|
: { ...inputs.declared, root: inputs.root };
|
|
411
|
-
if (
|
|
510
|
+
if (
|
|
511
|
+
projectInputSnapshotsEqual(
|
|
512
|
+
this.projectInputs,
|
|
513
|
+
next,
|
|
514
|
+
this.projectInputIdentities(),
|
|
515
|
+
)
|
|
516
|
+
) {
|
|
412
517
|
this.syncProjectInputWatchers();
|
|
413
518
|
return;
|
|
414
519
|
}
|
|
@@ -420,10 +525,12 @@ export class WatchTopology {
|
|
|
420
525
|
{ ...(inputs.declared ?? inputs), root: inputs.root },
|
|
421
526
|
]);
|
|
422
527
|
this.projectInputMatches = this.collectProjectInputMatches();
|
|
423
|
-
|
|
528
|
+
const baseline = fingerprintProjectInputMatches(
|
|
424
529
|
this.projectInputMatches,
|
|
425
530
|
this.readDirectory,
|
|
426
531
|
);
|
|
532
|
+
this.projectInputFingerprints = baseline.fingerprints;
|
|
533
|
+
this.projectInputFileSnapshots = baseline.snapshots;
|
|
427
534
|
this.syncProjectInputWatchers();
|
|
428
535
|
}
|
|
429
536
|
|
|
@@ -457,11 +564,26 @@ export class WatchTopology {
|
|
|
457
564
|
|
|
458
565
|
/** Start fresh native case observations for one synchronous watch decision. */
|
|
459
566
|
private beginLexicalTransaction(): void {
|
|
567
|
+
this.caseObservations = this.observeCaseSensitivity();
|
|
460
568
|
this.lexicalIdentities = createProjectInputPathIdentityContext({
|
|
569
|
+
caseSensitive: this.caseObservations,
|
|
461
570
|
throwOnRealpathError: false,
|
|
462
571
|
});
|
|
463
572
|
}
|
|
464
573
|
|
|
574
|
+
/**
|
|
575
|
+
* A strict project-input identity context for the current decision. Its
|
|
576
|
+
* physical paths are resolved afresh, and its case answers are the
|
|
577
|
+
* decision's: one decision builds such contexts per declaration and per
|
|
578
|
+
* candidate, and each asked every directory again, which on Windows is an
|
|
579
|
+
* `fsutil` process per question (samchon/ttsc#1742).
|
|
580
|
+
*/
|
|
581
|
+
private projectInputIdentities(): ProjectInputPathIdentityContext {
|
|
582
|
+
return createProjectInputPathIdentityContext({
|
|
583
|
+
caseSensitive: this.caseObservations,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
|
|
465
587
|
/** Keep lexical alias ownership while sharing this decision's case probes. */
|
|
466
588
|
private pathKey(location: string): string {
|
|
467
589
|
return WatchPaths.pathKey(location, this.lexicalIdentities);
|
|
@@ -585,34 +707,17 @@ export class WatchTopology {
|
|
|
585
707
|
this.openDirectoryWatch(
|
|
586
708
|
watcherRegistrationPath(location),
|
|
587
709
|
process.platform === "win32",
|
|
588
|
-
(event, filename, gap) =>
|
|
589
|
-
this.
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const pluginInput = changed ?? location;
|
|
593
|
-
if (this.isPluginInput(pluginInput)) {
|
|
594
|
-
this.notePluginNotification(pluginInput);
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
const plan = planCompilerDirectoryWatchEvent({
|
|
598
|
-
changed,
|
|
599
|
-
event,
|
|
600
|
-
exists: fs.existsSync,
|
|
710
|
+
(event, filename, gap) =>
|
|
711
|
+
this.deferNotification({
|
|
712
|
+
gap: gap === true,
|
|
713
|
+
lane: "compiler",
|
|
601
714
|
location,
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
plan.changes,
|
|
609
|
-
changed,
|
|
610
|
-
gap === true,
|
|
611
|
-
)) {
|
|
612
|
-
this.reportCompilerFileChange(file);
|
|
613
|
-
}
|
|
614
|
-
if (plan.refresh) this.refreshFromDirectory(location, changed);
|
|
615
|
-
},
|
|
715
|
+
changed:
|
|
716
|
+
filename === null
|
|
717
|
+
? undefined
|
|
718
|
+
: path.resolve(location, filename),
|
|
719
|
+
rename: event === "rename",
|
|
720
|
+
}),
|
|
616
721
|
),
|
|
617
722
|
(location, error) => this.callbacks.onError(location, error),
|
|
618
723
|
() => this.closed === false,
|
|
@@ -622,6 +727,87 @@ export class WatchTopology {
|
|
|
622
727
|
);
|
|
623
728
|
}
|
|
624
729
|
|
|
730
|
+
/**
|
|
731
|
+
* Decide a notification in the event loop's check phase, together with every
|
|
732
|
+
* other notification the same poll iteration delivered (samchon/ttsc#1741).
|
|
733
|
+
*
|
|
734
|
+
* One filesystem change usually arrives as several notifications, rename and
|
|
735
|
+
* change or create and modify, and each listed the compiler inputs or
|
|
736
|
+
* rescanned the project inputs again. Identical notifications of one lane,
|
|
737
|
+
* location and path merge, keeping a rename or a gap any of them carried, and
|
|
738
|
+
* the batch refreshes compiler membership once after deciding them all. The
|
|
739
|
+
* refresh therefore starts after every notification it absorbs, and one
|
|
740
|
+
* delivered later schedules another batch.
|
|
741
|
+
*/
|
|
742
|
+
private deferNotification(notification: PendingNotification): void {
|
|
743
|
+
if (this.closed) return;
|
|
744
|
+
const key = [
|
|
745
|
+
notification.lane,
|
|
746
|
+
notification.location,
|
|
747
|
+
notification.changed ?? "",
|
|
748
|
+
].join("\0");
|
|
749
|
+
const pending = this.pendingNotifications.get(key);
|
|
750
|
+
if (pending !== undefined) {
|
|
751
|
+
pending.rename ||= notification.rename;
|
|
752
|
+
pending.gap ||= notification.gap;
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
this.pendingNotifications.set(key, { ...notification });
|
|
756
|
+
if (this.notificationsScheduled) return;
|
|
757
|
+
this.notificationsScheduled = true;
|
|
758
|
+
setImmediate(() => this.decideNotifications());
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
private decideNotifications(): void {
|
|
762
|
+
this.notificationsScheduled = false;
|
|
763
|
+
const pending = [...this.pendingNotifications.values()];
|
|
764
|
+
this.pendingNotifications.clear();
|
|
765
|
+
if (this.closed) return;
|
|
766
|
+
const refreshes: DirectoryRefresh[] = [];
|
|
767
|
+
for (const notification of pending) {
|
|
768
|
+
if (notification.lane === "project-input") {
|
|
769
|
+
this.refreshProjectInputs(notification.location, notification.changed);
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
this.decideCompilerDirectoryNotification(notification, refreshes);
|
|
773
|
+
}
|
|
774
|
+
if (refreshes.length === 0 || this.closed) return;
|
|
775
|
+
this.beginLexicalTransaction();
|
|
776
|
+
this.refreshFromDirectories(refreshes);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/** Decide one merged directory notification of the compiler lane. */
|
|
780
|
+
private decideCompilerDirectoryNotification(
|
|
781
|
+
notification: PendingNotification,
|
|
782
|
+
refreshes: DirectoryRefresh[],
|
|
783
|
+
): void {
|
|
784
|
+
this.beginLexicalTransaction();
|
|
785
|
+
const { changed, location } = notification;
|
|
786
|
+
const pluginInput = changed ?? location;
|
|
787
|
+
if (this.isPluginInput(pluginInput)) {
|
|
788
|
+
this.notePluginNotification(pluginInput);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const plan = planCompilerDirectoryWatchEvent({
|
|
792
|
+
changed,
|
|
793
|
+
event: notification.rename ? "rename" : "change",
|
|
794
|
+
exists: fs.existsSync,
|
|
795
|
+
location,
|
|
796
|
+
identities: this.lexicalIdentities,
|
|
797
|
+
platform: process.platform,
|
|
798
|
+
trackedFiles: this.files,
|
|
799
|
+
});
|
|
800
|
+
this.rearmFileWatchers(plan.rearm);
|
|
801
|
+
for (const file of this.compilerChangesToReport(
|
|
802
|
+
plan.changes,
|
|
803
|
+
changed,
|
|
804
|
+
notification.gap,
|
|
805
|
+
)) {
|
|
806
|
+
this.reportCompilerFileChange(file);
|
|
807
|
+
}
|
|
808
|
+
if (plan.refresh) refreshes.push({ changed, location });
|
|
809
|
+
}
|
|
810
|
+
|
|
625
811
|
/**
|
|
626
812
|
* Reconcile tracked compiler files after a newly registered watcher returns.
|
|
627
813
|
*
|
|
@@ -730,6 +916,7 @@ export class WatchTopology {
|
|
|
730
916
|
if (fs.existsSync(location)) {
|
|
731
917
|
if (this.projectInputMatches.size !== 0) {
|
|
732
918
|
const key = createProjectInputPathIdentityContext({
|
|
919
|
+
caseSensitive: this.caseObservations,
|
|
733
920
|
throwOnRealpathError: false,
|
|
734
921
|
}).resolve(location).key;
|
|
735
922
|
if (
|
|
@@ -840,7 +1027,7 @@ export class WatchTopology {
|
|
|
840
1027
|
if (this.closed) return;
|
|
841
1028
|
const previous = new Map(this.projectInputWatchers);
|
|
842
1029
|
const previousLinks = new Map(this.projectInputLinkWatchers);
|
|
843
|
-
const identities =
|
|
1030
|
+
const identities = this.projectInputIdentities();
|
|
844
1031
|
const desired = new Map<string, string>();
|
|
845
1032
|
const required = new Map<string, string>();
|
|
846
1033
|
for (const file of this.projectInputDeclarations("file")) {
|
|
@@ -924,11 +1111,17 @@ export class WatchTopology {
|
|
|
924
1111
|
this.openDirectoryWatch(
|
|
925
1112
|
watcherRegistrationPath(location),
|
|
926
1113
|
true,
|
|
927
|
-
(_event, filename) =>
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1114
|
+
(_event, filename) =>
|
|
1115
|
+
this.deferNotification({
|
|
1116
|
+
gap: false,
|
|
1117
|
+
lane: "project-input",
|
|
1118
|
+
location,
|
|
1119
|
+
changed:
|
|
1120
|
+
filename === null
|
|
1121
|
+
? undefined
|
|
1122
|
+
: path.resolve(location, filename),
|
|
1123
|
+
rename: false,
|
|
1124
|
+
}),
|
|
932
1125
|
),
|
|
933
1126
|
(location, error) => {
|
|
934
1127
|
const key = identities.resolve(location).key;
|
|
@@ -1003,11 +1196,17 @@ export class WatchTopology {
|
|
|
1003
1196
|
this.openDirectoryWatch(
|
|
1004
1197
|
watcherRegistrationPath(location),
|
|
1005
1198
|
false,
|
|
1006
|
-
(_event, filename) =>
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1199
|
+
(_event, filename) =>
|
|
1200
|
+
this.deferNotification({
|
|
1201
|
+
gap: false,
|
|
1202
|
+
lane: "project-input",
|
|
1203
|
+
location,
|
|
1204
|
+
changed:
|
|
1205
|
+
filename === null
|
|
1206
|
+
? undefined
|
|
1207
|
+
: path.resolve(location, filename),
|
|
1208
|
+
rename: false,
|
|
1209
|
+
}),
|
|
1011
1210
|
),
|
|
1012
1211
|
(location, error) => this.callbacks.onError(location, error),
|
|
1013
1212
|
() => this.closed === false,
|
|
@@ -1088,6 +1287,7 @@ export class WatchTopology {
|
|
|
1088
1287
|
if (this.projectInputRecoveryScheduled) return;
|
|
1089
1288
|
this.projectInputRecoveryScheduled = true;
|
|
1090
1289
|
queueMicrotask(() => {
|
|
1290
|
+
this.beginLexicalTransaction();
|
|
1091
1291
|
try {
|
|
1092
1292
|
let previousRejectionCount = -1;
|
|
1093
1293
|
while (
|
|
@@ -1131,6 +1331,7 @@ export class WatchTopology {
|
|
|
1131
1331
|
queueMicrotask(() => {
|
|
1132
1332
|
this.projectInputPostRegistrationReconciliationScheduled = false;
|
|
1133
1333
|
if (this.closed) return;
|
|
1334
|
+
this.beginLexicalTransaction();
|
|
1134
1335
|
this.refreshPublishedProjectInputIdentities();
|
|
1135
1336
|
this.refreshProjectInputs(this.projectInputs.root, undefined, true);
|
|
1136
1337
|
});
|
|
@@ -1145,8 +1346,13 @@ export class WatchTopology {
|
|
|
1145
1346
|
* that must observe later target changes.
|
|
1146
1347
|
*/
|
|
1147
1348
|
private refreshPublishedProjectInputIdentities(): void {
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1349
|
+
const identities = this.projectInputIdentities();
|
|
1350
|
+
const next = normalizeProjectInputSnapshot(
|
|
1351
|
+
this.declaredProjectInputs,
|
|
1352
|
+
identities,
|
|
1353
|
+
);
|
|
1354
|
+
if (projectInputSnapshotsEqual(this.projectInputs, next, identities))
|
|
1355
|
+
return;
|
|
1150
1356
|
this.projectInputs = next;
|
|
1151
1357
|
this.pruneProjectInputWatchRoots([next, this.declaredProjectInputs]);
|
|
1152
1358
|
}
|
|
@@ -1264,6 +1470,7 @@ export class WatchTopology {
|
|
|
1264
1470
|
const resolved = ProjectInputWatchRules.projectInputRecursiveWatchRoot(
|
|
1265
1471
|
target,
|
|
1266
1472
|
this.projectInputs.root,
|
|
1473
|
+
this.projectInputIdentities(),
|
|
1267
1474
|
);
|
|
1268
1475
|
if (resolved !== undefined) this.projectInputWatchRoots.set(key, resolved);
|
|
1269
1476
|
return resolved;
|
|
@@ -1277,7 +1484,7 @@ export class WatchTopology {
|
|
|
1277
1484
|
this.beginLexicalTransaction();
|
|
1278
1485
|
try {
|
|
1279
1486
|
const previous = this.projectInputMatches;
|
|
1280
|
-
const identities =
|
|
1487
|
+
const identities = this.projectInputIdentities();
|
|
1281
1488
|
// One population for the whole decision. Every question below is asked of
|
|
1282
1489
|
// the same declarations, and rebuilding it per question would both cost
|
|
1283
1490
|
// more and let two answers disagree about what was declared.
|
|
@@ -1315,13 +1522,30 @@ export class WatchTopology {
|
|
|
1315
1522
|
}
|
|
1316
1523
|
const next = this.collectProjectInputMatches();
|
|
1317
1524
|
const membershipChanged = WatchPaths.mapsEqual(previous, next) === false;
|
|
1318
|
-
|
|
1525
|
+
// A named notification reads the named path's bytes and any file whose
|
|
1526
|
+
// metadata or owner moved; one that names nothing reads every file
|
|
1527
|
+
// (samchon/ttsc#1748).
|
|
1528
|
+
const scanned =
|
|
1319
1529
|
changed === undefined ||
|
|
1320
1530
|
membershipChanged ||
|
|
1321
1531
|
directlyMatched ||
|
|
1322
1532
|
topologyMatched
|
|
1323
|
-
? fingerprintProjectInputMatches(
|
|
1324
|
-
|
|
1533
|
+
? fingerprintProjectInputMatches(
|
|
1534
|
+
next,
|
|
1535
|
+
this.readDirectory,
|
|
1536
|
+
changed === undefined
|
|
1537
|
+
? undefined
|
|
1538
|
+
: {
|
|
1539
|
+
fingerprints: this.projectInputFingerprints,
|
|
1540
|
+
named: identities.resolve(changed).key,
|
|
1541
|
+
snapshots: this.projectInputFileSnapshots,
|
|
1542
|
+
},
|
|
1543
|
+
)
|
|
1544
|
+
: {
|
|
1545
|
+
fingerprints: this.projectInputFingerprints,
|
|
1546
|
+
snapshots: this.projectInputFileSnapshots,
|
|
1547
|
+
};
|
|
1548
|
+
const nextFingerprints = scanned.fingerprints;
|
|
1325
1549
|
const contentChanged =
|
|
1326
1550
|
WatchPaths.mapsEqual(
|
|
1327
1551
|
this.projectInputFingerprints,
|
|
@@ -1352,7 +1576,10 @@ export class WatchTopology {
|
|
|
1352
1576
|
reloadDirectories: population.reloadDirectories ?? [],
|
|
1353
1577
|
reloadFiles: population.reloadFiles ?? [],
|
|
1354
1578
|
};
|
|
1355
|
-
const reload = projectInputReloadEventShouldNotify(
|
|
1579
|
+
const reload = projectInputReloadEventShouldNotify(
|
|
1580
|
+
reloadInput,
|
|
1581
|
+
identities,
|
|
1582
|
+
);
|
|
1356
1583
|
const invalidate = projectInputMembershipInvalidatesProgram({
|
|
1357
1584
|
changed: reconciledChange,
|
|
1358
1585
|
changedInputs,
|
|
@@ -1362,6 +1589,7 @@ export class WatchTopology {
|
|
|
1362
1589
|
});
|
|
1363
1590
|
this.projectInputMatches = next;
|
|
1364
1591
|
this.projectInputFingerprints = nextFingerprints;
|
|
1592
|
+
this.projectInputFileSnapshots = scanned.snapshots;
|
|
1365
1593
|
this.syncProjectInputWatchers(skipUnobservedProjectInputWatchRoots);
|
|
1366
1594
|
// A JSON/TS/JS project-input member can simultaneously enter or leave
|
|
1367
1595
|
// the compiler Program. Reconcile the compiler watch snapshot before
|
|
@@ -1416,7 +1644,7 @@ export class WatchTopology {
|
|
|
1416
1644
|
}
|
|
1417
1645
|
|
|
1418
1646
|
private collectProjectInputMatches(): Map<string, string> {
|
|
1419
|
-
const identities =
|
|
1647
|
+
const identities = this.projectInputIdentities();
|
|
1420
1648
|
const matches = new Map<string, string>();
|
|
1421
1649
|
// Both spellings are scanned, and each is resolved here rather than when
|
|
1422
1650
|
// the snapshot arrived. A declaration reached through a symlink otherwise
|
|
@@ -1510,25 +1738,78 @@ export class WatchTopology {
|
|
|
1510
1738
|
}
|
|
1511
1739
|
|
|
1512
1740
|
private refreshFromDirectory(location: string, changed?: string): void {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1741
|
+
this.refreshFromDirectories([{ changed, location }]);
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
/**
|
|
1745
|
+
* Refresh compiler membership once for every directory a decision asked
|
|
1746
|
+
* about. A failed refresh still reports, once each, the reload inputs every
|
|
1747
|
+
* requested change names.
|
|
1748
|
+
*
|
|
1749
|
+
* A replacement can be delivered under only its vanished temporary name,
|
|
1750
|
+
* including when its malformed bytes prevent refresh. Reconcile active
|
|
1751
|
+
* reload-file snapshots on either outcome, deduplicate failed-refresh
|
|
1752
|
+
* recovery against actual reported movement, and preserve the original
|
|
1753
|
+
* refresh error after recovery. Batch moved owners through the existing rearm
|
|
1754
|
+
* operation before notifying, because a POSIX per-file handle still owns the
|
|
1755
|
+
* old inode. Unchanged snapshots stay quiet unless the existing
|
|
1756
|
+
* failed-refresh selector explicitly names or cannot find the reload input.
|
|
1757
|
+
*/
|
|
1758
|
+
private refreshFromDirectories(refreshes: readonly DirectoryRefresh[]): void {
|
|
1759
|
+
for (const { changed } of refreshes) {
|
|
1760
|
+
if (
|
|
1761
|
+
changed !== undefined &&
|
|
1762
|
+
WatchPaths.isDirectory(changed) &&
|
|
1763
|
+
this.isCompilerOutputDirectory(changed) === false &&
|
|
1764
|
+
this.isProjectInputDirectory(changed) === false
|
|
1765
|
+
) {
|
|
1766
|
+
this.observedDirectories.set(this.pathKey(changed), changed);
|
|
1767
|
+
}
|
|
1520
1768
|
}
|
|
1769
|
+
let refreshFailure: { error: unknown } | undefined;
|
|
1521
1770
|
try {
|
|
1522
1771
|
this.refresh(true);
|
|
1523
1772
|
} catch (error) {
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1773
|
+
refreshFailure = { error };
|
|
1774
|
+
}
|
|
1775
|
+
try {
|
|
1776
|
+
if (this.closed) return;
|
|
1777
|
+
const changed: string[] = [];
|
|
1778
|
+
const rearm: string[] = [];
|
|
1779
|
+
const reported = new Set<string>();
|
|
1780
|
+
for (const reload of this.reloadFiles.values()) {
|
|
1781
|
+
if (this.closed) return;
|
|
1782
|
+
const movement = this.compilerFileMovement(reload);
|
|
1783
|
+
if (movement.owner) rearm.push(reload);
|
|
1784
|
+
if (this.compilerMovementReports(reload, movement))
|
|
1785
|
+
changed.push(reload);
|
|
1530
1786
|
}
|
|
1531
|
-
this.
|
|
1787
|
+
if (rearm.length !== 0) this.rearmFileWatchers(rearm, true);
|
|
1788
|
+
for (const reload of changed) {
|
|
1789
|
+
if (this.closed) return;
|
|
1790
|
+
reported.add(this.pathKey(reload));
|
|
1791
|
+
this.reportCompilerFileChange(reload);
|
|
1792
|
+
}
|
|
1793
|
+
if (refreshFailure !== undefined) {
|
|
1794
|
+
for (const { changed } of refreshes) {
|
|
1795
|
+
for (const reload of reloadInputsForFailedTopologyRefresh(
|
|
1796
|
+
this.reloadFiles.values(),
|
|
1797
|
+
changed,
|
|
1798
|
+
this.lexicalIdentities,
|
|
1799
|
+
)) {
|
|
1800
|
+
if (this.closed) return;
|
|
1801
|
+
const key = this.pathKey(reload);
|
|
1802
|
+
if (reported.has(key)) continue;
|
|
1803
|
+
reported.add(key);
|
|
1804
|
+
this.callbacks.onInputChange({ kind: "config", path: reload });
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
this.callbacks.onError(refreshes[0]!.location, error);
|
|
1810
|
+
} finally {
|
|
1811
|
+
if (refreshFailure !== undefined)
|
|
1812
|
+
this.callbacks.onError(refreshes[0]!.location, refreshFailure.error);
|
|
1532
1813
|
}
|
|
1533
1814
|
}
|
|
1534
1815
|
|
|
@@ -1591,7 +1872,7 @@ export class WatchTopology {
|
|
|
1591
1872
|
|
|
1592
1873
|
private isProjectInputDirectory(location: string): boolean {
|
|
1593
1874
|
const resolved = path.resolve(location);
|
|
1594
|
-
const identities =
|
|
1875
|
+
const identities = this.projectInputIdentities();
|
|
1595
1876
|
return (
|
|
1596
1877
|
this.projectInputs.files.some(
|
|
1597
1878
|
(file) =>
|
|
@@ -1764,6 +2045,46 @@ type CompilerFileSnapshot = {
|
|
|
1764
2045
|
owner: string;
|
|
1765
2046
|
};
|
|
1766
2047
|
|
|
2048
|
+
type PendingNotification = {
|
|
2049
|
+
changed: string | undefined;
|
|
2050
|
+
gap: boolean;
|
|
2051
|
+
lane: "compiler" | "project-input";
|
|
2052
|
+
location: string;
|
|
2053
|
+
rename: boolean;
|
|
2054
|
+
};
|
|
2055
|
+
|
|
2056
|
+
type DirectoryRefresh = {
|
|
2057
|
+
changed: string | undefined;
|
|
2058
|
+
location: string;
|
|
2059
|
+
};
|
|
2060
|
+
|
|
2061
|
+
type CompilerListing = {
|
|
2062
|
+
/** The reader's own array, which the topology never mutates. */
|
|
2063
|
+
inputs: string[];
|
|
2064
|
+
question: string;
|
|
2065
|
+
};
|
|
2066
|
+
|
|
2067
|
+
/**
|
|
2068
|
+
* Everything a compiler listing of `project` is asked with: the project, and
|
|
2069
|
+
* each option `listCompilerInputs` passes to the compiler. Two refreshes with
|
|
2070
|
+
* the same question ask the compiler the same thing; only the disk can answer
|
|
2071
|
+
* them differently.
|
|
2072
|
+
*/
|
|
2073
|
+
function listingQuestion(
|
|
2074
|
+
project: ITtscParsedProjectConfig,
|
|
2075
|
+
options: WatchTopologyOptions,
|
|
2076
|
+
): string {
|
|
2077
|
+
return JSON.stringify([
|
|
2078
|
+
project.path,
|
|
2079
|
+
project.root,
|
|
2080
|
+
options.binary ?? null,
|
|
2081
|
+
options.compilerArgsCwd ?? null,
|
|
2082
|
+
options.env ?? null,
|
|
2083
|
+
options.passthrough ?? null,
|
|
2084
|
+
options.pinCompilerProject ?? null,
|
|
2085
|
+
]);
|
|
2086
|
+
}
|
|
2087
|
+
|
|
1767
2088
|
type CompilerFileMovement = {
|
|
1768
2089
|
content: boolean;
|
|
1769
2090
|
owner: boolean;
|
|
@@ -1836,7 +2157,10 @@ function resolveWatchTopology(
|
|
|
1836
2157
|
addPaths(reloadFiles, inspected.responseFiles, identities);
|
|
1837
2158
|
const compilerInputs = getCompilerInputs(project, options);
|
|
1838
2159
|
CompilerProjectSelection.assertCurrent(selection);
|
|
1839
|
-
const compilerOutputs = resolveCompilerOutputs(
|
|
2160
|
+
const compilerOutputs = resolveCompilerOutputs(
|
|
2161
|
+
project,
|
|
2162
|
+
inspected.options,
|
|
2163
|
+
);
|
|
1840
2164
|
addPaths(outputFiles, compilerOutputs.files, identities);
|
|
1841
2165
|
addPaths(
|
|
1842
2166
|
outputFiles,
|
|
@@ -1949,10 +2273,11 @@ function listCompilerInputs(
|
|
|
1949
2273
|
project: ITtscParsedProjectConfig,
|
|
1950
2274
|
options: WatchTopologyOptions,
|
|
1951
2275
|
): string[] {
|
|
2276
|
+
const env = SidecarEnvironment.merge(process.env, options.env);
|
|
1952
2277
|
const tsgo = resolveTsgo({
|
|
1953
2278
|
binary: options.binary,
|
|
1954
2279
|
cwd: project.root,
|
|
1955
|
-
env
|
|
2280
|
+
env,
|
|
1956
2281
|
});
|
|
1957
2282
|
const result = spawnNative(
|
|
1958
2283
|
tsgo.binary,
|
|
@@ -1967,7 +2292,7 @@ function listCompilerInputs(
|
|
|
1967
2292
|
],
|
|
1968
2293
|
{
|
|
1969
2294
|
cwd: options.compilerArgsCwd ?? project.root,
|
|
1970
|
-
env
|
|
2295
|
+
env,
|
|
1971
2296
|
encoding: "utf8",
|
|
1972
2297
|
},
|
|
1973
2298
|
);
|
|
@@ -2355,8 +2680,8 @@ function uniqueExistingPaths(
|
|
|
2355
2680
|
|
|
2356
2681
|
function normalizeProjectInputSnapshot(
|
|
2357
2682
|
snapshot: ITtscProjectInputSnapshot,
|
|
2683
|
+
identities: ProjectInputPathIdentityContext,
|
|
2358
2684
|
): ITtscProjectInputSnapshot {
|
|
2359
|
-
const identities = createProjectInputPathIdentityContext();
|
|
2360
2685
|
const files = new Map<string, string>();
|
|
2361
2686
|
const globs = new Map<string, string>();
|
|
2362
2687
|
const reloadDirectories = new Map<string, string>();
|
|
@@ -2389,8 +2714,8 @@ function normalizeProjectInputSnapshot(
|
|
|
2389
2714
|
function projectInputSnapshotsEqual(
|
|
2390
2715
|
left: ITtscProjectInputSnapshot,
|
|
2391
2716
|
right: ITtscProjectInputSnapshot,
|
|
2717
|
+
identities: ProjectInputPathIdentityContext,
|
|
2392
2718
|
): boolean {
|
|
2393
|
-
const identities = createProjectInputPathIdentityContext();
|
|
2394
2719
|
return (
|
|
2395
2720
|
// Every sibling comparison in this class decides on identity, and this one
|
|
2396
2721
|
// is only safe today because both operands come from the same normalizer.
|
|
@@ -2445,7 +2770,7 @@ function isSymbolicLink(location: string): boolean {
|
|
|
2445
2770
|
function matchesProjectInput(
|
|
2446
2771
|
snapshot: ITtscProjectInputSnapshot,
|
|
2447
2772
|
location: string,
|
|
2448
|
-
identities
|
|
2773
|
+
identities: ProjectInputPathIdentityContext,
|
|
2449
2774
|
): boolean {
|
|
2450
2775
|
const key = identities.resolve(location).key;
|
|
2451
2776
|
return (
|
|
@@ -2572,8 +2897,8 @@ function compilerMembershipChange(
|
|
|
2572
2897
|
function projectInputCompilerMembershipChange(
|
|
2573
2898
|
snapshot: ITtscProjectInputSnapshot,
|
|
2574
2899
|
changed: readonly string[],
|
|
2900
|
+
identities: ProjectInputPathIdentityContext,
|
|
2575
2901
|
): string[] {
|
|
2576
|
-
const identities = createProjectInputPathIdentityContext();
|
|
2577
2902
|
return changed.filter(
|
|
2578
2903
|
(location) =>
|
|
2579
2904
|
matchesProjectInput(snapshot, location, identities) &&
|
|
@@ -2593,8 +2918,8 @@ function projectInputCompilerMembershipChange(
|
|
|
2593
2918
|
function projectInputCompilerMembershipProjectChanges(
|
|
2594
2919
|
locations: readonly string[],
|
|
2595
2920
|
globs: readonly string[],
|
|
2921
|
+
identities: ProjectInputPathIdentityContext,
|
|
2596
2922
|
): string[] {
|
|
2597
|
-
const identities = createProjectInputPathIdentityContext();
|
|
2598
2923
|
const roots = globs.map((glob) => identities.resolve(literalGlobRoot(glob)));
|
|
2599
2924
|
const changes = new Map<string, string>();
|
|
2600
2925
|
for (const location of locations) {
|
|
@@ -2614,20 +2939,54 @@ function projectInputCompilerMembershipProjectChanges(
|
|
|
2614
2939
|
return [...changes.values()].sort();
|
|
2615
2940
|
}
|
|
2616
2941
|
|
|
2942
|
+
/**
|
|
2943
|
+
* Fingerprint every matched project input: a directory by its entries, a file
|
|
2944
|
+
* by its bytes.
|
|
2945
|
+
*
|
|
2946
|
+
* Without `previous`, every file is read. With it, a file keeps its previous
|
|
2947
|
+
* digest while its metadata and physical owner hold, unless it is the `named`
|
|
2948
|
+
* path, whose bytes are always read: a notification that names a file is the
|
|
2949
|
+
* one whose same-size rewrite within a timestamp tick metadata cannot show.
|
|
2950
|
+
*/
|
|
2617
2951
|
function fingerprintProjectInputMatches(
|
|
2618
2952
|
matches: ReadonlyMap<string, string>,
|
|
2619
2953
|
readDirectory: typeof fs.readdirSync,
|
|
2620
|
-
|
|
2954
|
+
previous?: {
|
|
2955
|
+
fingerprints: ReadonlyMap<string, string>;
|
|
2956
|
+
named: string;
|
|
2957
|
+
snapshots: ReadonlyMap<string, CompilerFileSnapshot>;
|
|
2958
|
+
},
|
|
2959
|
+
): {
|
|
2960
|
+
fingerprints: Map<string, string>;
|
|
2961
|
+
snapshots: Map<string, CompilerFileSnapshot>;
|
|
2962
|
+
} {
|
|
2621
2963
|
const fingerprints = new Map<string, string>();
|
|
2964
|
+
const snapshots = new Map<string, CompilerFileSnapshot>();
|
|
2622
2965
|
for (const [key, location] of matches) {
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2966
|
+
if (WatchPaths.isDirectory(location)) {
|
|
2967
|
+
fingerprints.set(
|
|
2968
|
+
key,
|
|
2969
|
+
fingerprintProjectInputDirectory(location, readDirectory),
|
|
2970
|
+
);
|
|
2971
|
+
continue;
|
|
2972
|
+
}
|
|
2973
|
+
const metadata = compilerFileSnapshot(location, "");
|
|
2974
|
+
const before = previous?.snapshots.get(key);
|
|
2975
|
+
const kept = previous?.fingerprints.get(key);
|
|
2976
|
+
const fingerprint =
|
|
2977
|
+
previous !== undefined &&
|
|
2978
|
+
key !== previous.named &&
|
|
2979
|
+
before !== undefined &&
|
|
2980
|
+
kept !== undefined &&
|
|
2981
|
+
metadata.content !== "" &&
|
|
2982
|
+
before.content === metadata.content &&
|
|
2983
|
+
before.owner === metadata.owner
|
|
2984
|
+
? kept
|
|
2985
|
+
: fingerprintProjectInputFile(location);
|
|
2986
|
+
fingerprints.set(key, fingerprint);
|
|
2987
|
+
snapshots.set(key, { ...metadata, fingerprint });
|
|
2988
|
+
}
|
|
2989
|
+
return { fingerprints, snapshots };
|
|
2631
2990
|
}
|
|
2632
2991
|
|
|
2633
2992
|
function fingerprintProjectInputFile(location: string): string {
|
|
@@ -2675,7 +3034,7 @@ function fingerprintProjectInputDirectory(
|
|
|
2675
3034
|
function matchesProjectInputGlob(
|
|
2676
3035
|
pattern: string,
|
|
2677
3036
|
location: string,
|
|
2678
|
-
identities
|
|
3037
|
+
identities: ProjectInputPathIdentityContext,
|
|
2679
3038
|
): boolean {
|
|
2680
3039
|
const root = identities.resolve(literalGlobRoot(pattern));
|
|
2681
3040
|
const candidate = identities.resolve(location);
|