ttsc 0.31.4 → 0.32.0
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/compiler/internal/build/BuildExecution.js +11 -3
- package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
- package/lib/compiler/internal/build/TsgoArguments.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 +20 -8
- package/lib/compiler/internal/project/resolveOwningProjectConfig.js +21 -12
- package/lib/compiler/internal/project/resolveOwningProjectConfig.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.d.ts +5 -4
- package/lib/compiler/internal/runSingleFileEmit.js +5 -4
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/flags/ParseOptions.d.ts +2 -2
- package/lib/internal/E2ETrace.d.ts +1 -0
- 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/probeLocalProcess.d.ts +5 -1
- package/lib/internal/probeLocalProcess.js +9 -7
- package/lib/internal/probeLocalProcess.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/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.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 +5 -5
- package/lib/launcher/internal/runTtsx.js +5 -2
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtime/ProcessOwnedDirectory.d.ts +2 -0
- package/lib/launcher/internal/runtime/ProcessOwnedDirectory.js +2 -1
- package/lib/launcher/internal/runtime/ProcessOwnedDirectory.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/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.js +35 -9
- package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
- package/lib/launcher/internal/runtime/isLocalProcessGone.d.ts +1 -1
- package/lib/launcher/internal/runtime/isLocalProcessGone.js +4 -4
- package/lib/launcher/internal/runtime/isLocalProcessGone.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/FseventsStreams.d.ts +2 -0
- package/lib/launcher/internal/watch/FseventsStreams.js +19 -0
- package/lib/launcher/internal/watch/FseventsStreams.js.map +1 -1
- package/lib/launcher/internal/watch/WatchTopology.d.ts +74 -12
- package/lib/launcher/internal/watch/WatchTopology.js +301 -92
- package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
- 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/plugin/internal/load/loadProjectPlugins.js +17 -9
- package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
- package/lib/plugin/internal/load/resolveNativeSource.js +2 -3
- package/lib/plugin/internal/load/resolveNativeSource.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/internal/TtscCommonOptions.d.ts +2 -2
- package/package.json +9 -9
- package/src/compiler/internal/build/BuildExecution.ts +15 -6
- package/src/compiler/internal/build/TsgoArguments.ts +4 -1
- package/src/compiler/internal/project/CompilerProjectSelection.ts +14 -14
- package/src/compiler/internal/project/resolveOwningProjectConfig.ts +29 -14
- package/src/compiler/internal/runSingleFileEmit.ts +11 -7
- package/src/flags/ParseOptions.ts +2 -2
- package/src/internal/E2ETrace.ts +5 -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/probeLocalProcess.ts +13 -8
- package/src/internal/projectInputPathIdentity.ts +1 -0
- package/src/launcher/internal/parseTtsxCLI.ts +5 -4
- package/src/launcher/internal/prepareExecution.ts +4 -1
- package/src/launcher/internal/resolveSingleFileOutput.ts +8 -9
- package/src/launcher/internal/runTtsc.ts +5 -5
- package/src/launcher/internal/runTtsx.ts +17 -8
- package/src/launcher/internal/runtime/ProcessOwnedDirectory.ts +5 -1
- package/src/launcher/internal/runtime/RuntimeEntrySources.ts +57 -0
- package/src/launcher/internal/runtime/RuntimeProjectOwnership.ts +23 -10
- package/src/launcher/internal/runtime/commonJsExportNames.ts +11 -4
- package/src/launcher/internal/runtime/installRuntimeHooks.ts +55 -13
- package/src/launcher/internal/runtime/isLocalProcessGone.ts +5 -3
- package/src/launcher/internal/runtime/resolveRuntimeCleanTargets.ts +17 -5
- package/src/launcher/internal/watch/FseventsStreams.ts +22 -0
- package/src/launcher/internal/watch/WatchTopology.ts +387 -99
- package/src/launcher/internal/watch/projectInputReloadEventShouldNotify.ts +14 -9
- package/src/plugin/internal/load/loadProjectPlugins.ts +17 -3
- package/src/plugin/internal/load/resolveNativeSource.ts +10 -4
- package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +10 -1
- package/src/structures/internal/TtscCommonOptions.ts +2 -2
- 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
|
@@ -15,6 +15,7 @@ const resolveTsgo_1 = require("../../../compiler/internal/resolveTsgo");
|
|
|
15
15
|
const spawnNative_1 = require("../../../compiler/internal/spawnNative");
|
|
16
16
|
const normalizeCompilerEnumValue_1 = require("../../../flags/normalizeCompilerEnumValue");
|
|
17
17
|
const readCompilerOptionValues_1 = require("../../../flags/readCompilerOptionValues");
|
|
18
|
+
const createCaseSensitivityObservations_1 = require("../../../internal/pathIdentity/createCaseSensitivityObservations");
|
|
18
19
|
const createProjectInputPathIdentityContext_1 = require("../../../internal/pathIdentity/createProjectInputPathIdentityContext");
|
|
19
20
|
const isProjectInputPathIdentityWithin_1 = require("../../../internal/pathIdentity/isProjectInputPathIdentityWithin");
|
|
20
21
|
const resolveProjectInputPath_1 = require("../../../internal/pathIdentity/resolveProjectInputPath");
|
|
@@ -44,26 +45,26 @@ const watchDirectory_1 = require("./watchDirectory");
|
|
|
44
45
|
* output for source and declaration membership. An explicit reader supplies the
|
|
45
46
|
* same absolute-path membership and failure contract for each resolved project.
|
|
46
47
|
* Configuration files, project-reference roots, output inference and native
|
|
47
|
-
* filesystem identity remain this topology's responsibility.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
48
|
+
* filesystem identity remain this topology's responsibility. Response frames
|
|
49
|
+
* are observed once per project refresh at its compiler cwd; that option view
|
|
50
|
+
* serves all project-output queries while the original argv reaches compiler
|
|
51
|
+
* membership. A second observation rejects response changes across membership
|
|
52
|
+
* resolution. Response files join the config reload set; these observations do
|
|
53
|
+
* not pin files or detect a change restored in between.
|
|
53
54
|
*
|
|
54
55
|
* `TTSC_WATCH_DEBUG_INPUTS` reports the named event, observed population deltas
|
|
55
56
|
* and reload decision. These diagnostics reuse the decision's existing inputs;
|
|
56
57
|
* they perform no extra filesystem observations.
|
|
57
58
|
*
|
|
58
59
|
* @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.
|
|
60
|
+
* @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
61
|
* @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
62
|
* @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
63
|
* @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
64
|
*
|
|
64
65
|
* @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
|
|
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#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.
|
|
67
|
+
* @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
68
|
*/
|
|
68
69
|
class WatchTopology {
|
|
69
70
|
options;
|
|
@@ -72,6 +73,7 @@ class WatchTopology {
|
|
|
72
73
|
openFileWatch;
|
|
73
74
|
readDirectory;
|
|
74
75
|
getCompilerInputs;
|
|
76
|
+
observeCaseSensitivity;
|
|
75
77
|
analysisOnly = false;
|
|
76
78
|
closed = false;
|
|
77
79
|
compilerPostRegistrationMembershipRefresh = false;
|
|
@@ -102,15 +104,36 @@ class WatchTopology {
|
|
|
102
104
|
/** The plugin inputs whose directories the last sync already watched. */
|
|
103
105
|
watchedExtraInputs = new Set();
|
|
104
106
|
compilerFileSnapshots = new Map();
|
|
107
|
+
/**
|
|
108
|
+
* The last compiler listing of each project the topology resolved, by its
|
|
109
|
+
* config path, with the arguments it was asked under (`listingQuestion`). A
|
|
110
|
+
* change of plugin inputs reuses it (samchon/ttsc#1741).
|
|
111
|
+
*/
|
|
112
|
+
compilerListings = new Map();
|
|
113
|
+
/**
|
|
114
|
+
* Notifications the current poll iteration delivered, decided together in the
|
|
115
|
+
* check phase (`deferNotification`).
|
|
116
|
+
*/
|
|
117
|
+
pendingNotifications = new Map();
|
|
118
|
+
notificationsScheduled = false;
|
|
105
119
|
files = new Map();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
/**
|
|
121
|
+
* The current decision's directory case-policy observations, which every
|
|
122
|
+
* identity context the decision creates shares (samchon/ttsc#1742).
|
|
123
|
+
*/
|
|
124
|
+
caseObservations;
|
|
125
|
+
lexicalIdentities;
|
|
109
126
|
fileWatchers = new Map();
|
|
110
127
|
observedDirectories = new Map();
|
|
111
128
|
outputFiles = new Map();
|
|
112
129
|
outputs = new Map();
|
|
113
130
|
projectInputFingerprints = new Map();
|
|
131
|
+
/**
|
|
132
|
+
* The metadata and physical owner each matched project-input file had when
|
|
133
|
+
* its fingerprint was taken, by match key; a named notification re-reads only
|
|
134
|
+
* the files whose metadata moved (samchon/ttsc#1748).
|
|
135
|
+
*/
|
|
136
|
+
projectInputFileSnapshots = new Map();
|
|
114
137
|
projectInputMatches = new Map();
|
|
115
138
|
projectInputs = {
|
|
116
139
|
files: [],
|
|
@@ -150,14 +173,18 @@ class WatchTopology {
|
|
|
150
173
|
* membership; the default invokes the native compiler. Readers throw
|
|
151
174
|
* listing failures and retain ownership of their returned array, which this
|
|
152
175
|
* topology does not mutate.
|
|
176
|
+
* @param observeCaseSensitivity Create one decision's directory case-policy
|
|
177
|
+
* observations; the default asks the native filesystem.
|
|
153
178
|
*/
|
|
154
|
-
constructor(options, callbacks, openDirectoryWatch = watchDirectory_1.watchDirectory, openFileWatch = node_fs_1.default.watch, readDirectory = node_fs_1.default.readdirSync, getCompilerInputs = listCompilerInputs) {
|
|
179
|
+
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
180
|
this.options = options;
|
|
156
181
|
this.callbacks = callbacks;
|
|
157
182
|
this.openDirectoryWatch = openDirectoryWatch;
|
|
158
183
|
this.openFileWatch = openFileWatch;
|
|
159
184
|
this.readDirectory = readDirectory;
|
|
160
185
|
this.getCompilerInputs = getCompilerInputs;
|
|
186
|
+
this.observeCaseSensitivity = observeCaseSensitivity;
|
|
187
|
+
this.beginLexicalTransaction();
|
|
161
188
|
}
|
|
162
189
|
/**
|
|
163
190
|
* Re-resolve compiler inputs and notify when the resolved topology changes,
|
|
@@ -177,16 +204,31 @@ class WatchTopology {
|
|
|
177
204
|
refresh(notify) {
|
|
178
205
|
this.refreshCompilerInputs(notify, false);
|
|
179
206
|
}
|
|
180
|
-
|
|
207
|
+
/**
|
|
208
|
+
* @param reuseListings Answer each project's membership with its last listing
|
|
209
|
+
* under the same arguments instead of asking the compiler again. Only a
|
|
210
|
+
* refresh whose cause cannot move Program membership passes it.
|
|
211
|
+
*/
|
|
212
|
+
refreshCompilerInputs(notify, skipUnobservedProjectInputWatchRoots, reuseListings = false) {
|
|
181
213
|
this.beginLexicalTransaction();
|
|
182
|
-
const
|
|
214
|
+
const listings = new Map();
|
|
215
|
+
const next = resolveWatchTopology(this.options, this.extraInputs, this.lexicalIdentities, (project, options) => {
|
|
216
|
+
const question = listingQuestion(project, options);
|
|
217
|
+
const last = this.compilerListings.get(project.path);
|
|
218
|
+
const inputs = reuseListings && last?.question === question
|
|
219
|
+
? last.inputs
|
|
220
|
+
: this.getCompilerInputs(project, options);
|
|
221
|
+
listings.set(project.path, { inputs, question });
|
|
222
|
+
return inputs;
|
|
223
|
+
});
|
|
224
|
+
this.compilerListings = listings;
|
|
183
225
|
const compilerProgramMembershipChange = next.analysisOnly &&
|
|
184
226
|
WatchPaths_1.WatchPaths.mapsEqual(this.reloadFiles, next.reloadFiles) &&
|
|
185
227
|
WatchPaths_1.WatchPaths.mapsEqual(this.outputFiles, next.outputFiles) &&
|
|
186
228
|
WatchPaths_1.WatchPaths.mapsEqual(this.outputs, next.outputs)
|
|
187
229
|
? compilerMembershipChange(this.files, next.files)
|
|
188
230
|
: [];
|
|
189
|
-
const projectInputProgramOverlap = projectInputCompilerMembershipChange(this.projectInputs, compilerProgramMembershipChange);
|
|
231
|
+
const projectInputProgramOverlap = projectInputCompilerMembershipChange(this.projectInputs, compilerProgramMembershipChange, this.projectInputIdentities());
|
|
190
232
|
const projectInputProgramChange = compilerProgramMembershipChange.length !== 0 &&
|
|
191
233
|
projectInputProgramOverlap.length ===
|
|
192
234
|
compilerProgramMembershipChange.length
|
|
@@ -260,7 +302,7 @@ class WatchTopology {
|
|
|
260
302
|
*/
|
|
261
303
|
acknowledgeProjectInputCompilerMembership(changed) {
|
|
262
304
|
const matches = this.collectProjectInputMatches();
|
|
263
|
-
const fingerprints = fingerprintProjectInputMatches(matches, this.readDirectory);
|
|
305
|
+
const { fingerprints, snapshots } = fingerprintProjectInputMatches(matches, this.readDirectory);
|
|
264
306
|
const changedInputs = projectInputChangedPaths({
|
|
265
307
|
next: matches,
|
|
266
308
|
nextFingerprints: fingerprints,
|
|
@@ -268,7 +310,7 @@ class WatchTopology {
|
|
|
268
310
|
previousFingerprints: this.projectInputFingerprints,
|
|
269
311
|
});
|
|
270
312
|
const population = this.projectInputPopulation();
|
|
271
|
-
const causedBy = projectInputCompilerMembershipProjectChanges(changed, population.globs);
|
|
313
|
+
const causedBy = projectInputCompilerMembershipProjectChanges(changed, population.globs, this.projectInputIdentities());
|
|
272
314
|
const reloadInput = {
|
|
273
315
|
causedBy,
|
|
274
316
|
changed: causedBy.length === 1 ? causedBy[0] : undefined,
|
|
@@ -277,7 +319,7 @@ class WatchTopology {
|
|
|
277
319
|
reloadDirectories: population.reloadDirectories,
|
|
278
320
|
reloadFiles: population.reloadFiles ?? [],
|
|
279
321
|
};
|
|
280
|
-
const reload = (0, projectInputReloadEventShouldNotify_1.projectInputReloadEventShouldNotify)(reloadInput);
|
|
322
|
+
const reload = (0, projectInputReloadEventShouldNotify_1.projectInputReloadEventShouldNotify)(reloadInput, this.projectInputIdentities());
|
|
281
323
|
reportProjectInputDecision({
|
|
282
324
|
source: "compiler-membership",
|
|
283
325
|
...reloadInput,
|
|
@@ -289,6 +331,7 @@ class WatchTopology {
|
|
|
289
331
|
// instead of disappearing behind the warm compiler-membership handoff.
|
|
290
332
|
this.projectInputMatches = matches;
|
|
291
333
|
this.projectInputFingerprints = fingerprints;
|
|
334
|
+
this.projectInputFileSnapshots = snapshots;
|
|
292
335
|
return reload;
|
|
293
336
|
}
|
|
294
337
|
/**
|
|
@@ -313,8 +356,8 @@ class WatchTopology {
|
|
|
313
356
|
* @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
357
|
* @evidence contracts/common.md#meaningful-documentation Native prose explains baseline timing and why existing inputs are not restamped following the documentation skill.
|
|
315
358
|
* @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;
|
|
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.
|
|
359
|
+
* @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.
|
|
360
|
+
* @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
361
|
* @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
362
|
*/
|
|
320
363
|
setExtraInputs(inputs, identities) {
|
|
@@ -337,7 +380,12 @@ class WatchTopology {
|
|
|
337
380
|
pluginInputState(input, this.pluginInputIdentities));
|
|
338
381
|
}
|
|
339
382
|
this.pluginInputStates = states;
|
|
340
|
-
|
|
383
|
+
// Plugin inputs join the watched files and directories, never Program
|
|
384
|
+
// membership, so the compiler's last answer stands. A load reports up to
|
|
385
|
+
// three different sets, and asking again for each spawned tsgo three times
|
|
386
|
+
// per load (samchon/ttsc#1741). A membership change meanwhile reaches the
|
|
387
|
+
// refresh its own notification starts, which lists again.
|
|
388
|
+
this.refreshCompilerInputs(false, false, true);
|
|
341
389
|
}
|
|
342
390
|
/**
|
|
343
391
|
* Reconcile project-rule dependencies, retaining absent files and empty glob
|
|
@@ -353,7 +401,8 @@ class WatchTopology {
|
|
|
353
401
|
* @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
402
|
*/
|
|
355
403
|
setProjectInputs(inputs) {
|
|
356
|
-
|
|
404
|
+
this.beginLexicalTransaction();
|
|
405
|
+
const next = normalizeProjectInputSnapshot(inputs, this.projectInputIdentities());
|
|
357
406
|
// The declared spellings are recorded even when the normalized snapshot did
|
|
358
407
|
// not move, because a republication can carry a new alias for identities
|
|
359
408
|
// that already matched, and anchoring the spelling that was retired would
|
|
@@ -362,7 +411,7 @@ class WatchTopology {
|
|
|
362
411
|
inputs.declared === undefined
|
|
363
412
|
? inputs
|
|
364
413
|
: { ...inputs.declared, root: inputs.root };
|
|
365
|
-
if (projectInputSnapshotsEqual(this.projectInputs, next)) {
|
|
414
|
+
if (projectInputSnapshotsEqual(this.projectInputs, next, this.projectInputIdentities())) {
|
|
366
415
|
this.syncProjectInputWatchers();
|
|
367
416
|
return;
|
|
368
417
|
}
|
|
@@ -374,7 +423,9 @@ class WatchTopology {
|
|
|
374
423
|
{ ...(inputs.declared ?? inputs), root: inputs.root },
|
|
375
424
|
]);
|
|
376
425
|
this.projectInputMatches = this.collectProjectInputMatches();
|
|
377
|
-
|
|
426
|
+
const baseline = fingerprintProjectInputMatches(this.projectInputMatches, this.readDirectory);
|
|
427
|
+
this.projectInputFingerprints = baseline.fingerprints;
|
|
428
|
+
this.projectInputFileSnapshots = baseline.snapshots;
|
|
378
429
|
this.syncProjectInputWatchers();
|
|
379
430
|
}
|
|
380
431
|
/**
|
|
@@ -406,10 +457,24 @@ class WatchTopology {
|
|
|
406
457
|
}
|
|
407
458
|
/** Start fresh native case observations for one synchronous watch decision. */
|
|
408
459
|
beginLexicalTransaction() {
|
|
460
|
+
this.caseObservations = this.observeCaseSensitivity();
|
|
409
461
|
this.lexicalIdentities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)({
|
|
462
|
+
caseSensitive: this.caseObservations,
|
|
410
463
|
throwOnRealpathError: false,
|
|
411
464
|
});
|
|
412
465
|
}
|
|
466
|
+
/**
|
|
467
|
+
* A strict project-input identity context for the current decision. Its
|
|
468
|
+
* physical paths are resolved afresh, and its case answers are the
|
|
469
|
+
* decision's: one decision builds such contexts per declaration and per
|
|
470
|
+
* candidate, and each asked every directory again, which on Windows is an
|
|
471
|
+
* `fsutil` process per question (samchon/ttsc#1742).
|
|
472
|
+
*/
|
|
473
|
+
projectInputIdentities() {
|
|
474
|
+
return (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)({
|
|
475
|
+
caseSensitive: this.caseObservations,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
413
478
|
/** Keep lexical alias ownership while sharing this decision's case probes. */
|
|
414
479
|
pathKey(location) {
|
|
415
480
|
return WatchPaths_1.WatchPaths.pathKey(location, this.lexicalIdentities);
|
|
@@ -487,32 +552,93 @@ class WatchTopology {
|
|
|
487
552
|
}
|
|
488
553
|
}
|
|
489
554
|
}
|
|
490
|
-
(0, syncWatchers_1.syncWatchers)(this.directoryWatchers, desired, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), process.platform === "win32", (event, filename, gap) => {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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);
|
|
555
|
+
(0, syncWatchers_1.syncWatchers)(this.directoryWatchers, desired, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), process.platform === "win32", (event, filename, gap) => this.deferNotification({
|
|
556
|
+
gap: gap === true,
|
|
557
|
+
lane: "compiler",
|
|
558
|
+
location,
|
|
559
|
+
changed: filename === null
|
|
560
|
+
? undefined
|
|
561
|
+
: node_path_1.default.resolve(location, filename),
|
|
562
|
+
rename: event === "rename",
|
|
563
|
+
})), (location, error) => this.callbacks.onError(location, error), () => this.closed === false);
|
|
514
564
|
return [...this.directoryWatchers].some(([key, watcher]) => previous.get(key) !== watcher);
|
|
515
565
|
}
|
|
566
|
+
/**
|
|
567
|
+
* Decide a notification in the event loop's check phase, together with every
|
|
568
|
+
* other notification the same poll iteration delivered (samchon/ttsc#1741).
|
|
569
|
+
*
|
|
570
|
+
* One filesystem change usually arrives as several notifications, rename and
|
|
571
|
+
* change or create and modify, and each listed the compiler inputs or
|
|
572
|
+
* rescanned the project inputs again. Identical notifications of one lane,
|
|
573
|
+
* location and path merge, keeping a rename or a gap any of them carried, and
|
|
574
|
+
* the batch refreshes compiler membership once after deciding them all. The
|
|
575
|
+
* refresh therefore starts after every notification it absorbs, and one
|
|
576
|
+
* delivered later schedules another batch.
|
|
577
|
+
*/
|
|
578
|
+
deferNotification(notification) {
|
|
579
|
+
if (this.closed)
|
|
580
|
+
return;
|
|
581
|
+
const key = [
|
|
582
|
+
notification.lane,
|
|
583
|
+
notification.location,
|
|
584
|
+
notification.changed ?? "",
|
|
585
|
+
].join("\0");
|
|
586
|
+
const pending = this.pendingNotifications.get(key);
|
|
587
|
+
if (pending !== undefined) {
|
|
588
|
+
pending.rename ||= notification.rename;
|
|
589
|
+
pending.gap ||= notification.gap;
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
this.pendingNotifications.set(key, { ...notification });
|
|
593
|
+
if (this.notificationsScheduled)
|
|
594
|
+
return;
|
|
595
|
+
this.notificationsScheduled = true;
|
|
596
|
+
setImmediate(() => this.decideNotifications());
|
|
597
|
+
}
|
|
598
|
+
decideNotifications() {
|
|
599
|
+
this.notificationsScheduled = false;
|
|
600
|
+
const pending = [...this.pendingNotifications.values()];
|
|
601
|
+
this.pendingNotifications.clear();
|
|
602
|
+
if (this.closed)
|
|
603
|
+
return;
|
|
604
|
+
const refreshes = [];
|
|
605
|
+
for (const notification of pending) {
|
|
606
|
+
if (notification.lane === "project-input") {
|
|
607
|
+
this.refreshProjectInputs(notification.location, notification.changed);
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
this.decideCompilerDirectoryNotification(notification, refreshes);
|
|
611
|
+
}
|
|
612
|
+
if (refreshes.length === 0 || this.closed)
|
|
613
|
+
return;
|
|
614
|
+
this.beginLexicalTransaction();
|
|
615
|
+
this.refreshFromDirectories(refreshes);
|
|
616
|
+
}
|
|
617
|
+
/** Decide one merged directory notification of the compiler lane. */
|
|
618
|
+
decideCompilerDirectoryNotification(notification, refreshes) {
|
|
619
|
+
this.beginLexicalTransaction();
|
|
620
|
+
const { changed, location } = notification;
|
|
621
|
+
const pluginInput = changed ?? location;
|
|
622
|
+
if (this.isPluginInput(pluginInput)) {
|
|
623
|
+
this.notePluginNotification(pluginInput);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const plan = (0, planCompilerDirectoryWatchEvent_1.planCompilerDirectoryWatchEvent)({
|
|
627
|
+
changed,
|
|
628
|
+
event: notification.rename ? "rename" : "change",
|
|
629
|
+
exists: node_fs_1.default.existsSync,
|
|
630
|
+
location,
|
|
631
|
+
identities: this.lexicalIdentities,
|
|
632
|
+
platform: process.platform,
|
|
633
|
+
trackedFiles: this.files,
|
|
634
|
+
});
|
|
635
|
+
this.rearmFileWatchers(plan.rearm);
|
|
636
|
+
for (const file of this.compilerChangesToReport(plan.changes, changed, notification.gap)) {
|
|
637
|
+
this.reportCompilerFileChange(file);
|
|
638
|
+
}
|
|
639
|
+
if (plan.refresh)
|
|
640
|
+
refreshes.push({ changed, location });
|
|
641
|
+
}
|
|
516
642
|
/**
|
|
517
643
|
* Reconcile tracked compiler files after a newly registered watcher returns.
|
|
518
644
|
*
|
|
@@ -607,6 +733,7 @@ class WatchTopology {
|
|
|
607
733
|
if (node_fs_1.default.existsSync(location)) {
|
|
608
734
|
if (this.projectInputMatches.size !== 0) {
|
|
609
735
|
const key = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)({
|
|
736
|
+
caseSensitive: this.caseObservations,
|
|
610
737
|
throwOnRealpathError: false,
|
|
611
738
|
}).resolve(location).key;
|
|
612
739
|
if (this.projectInputMatches.has(key) &&
|
|
@@ -701,7 +828,7 @@ class WatchTopology {
|
|
|
701
828
|
return;
|
|
702
829
|
const previous = new Map(this.projectInputWatchers);
|
|
703
830
|
const previousLinks = new Map(this.projectInputLinkWatchers);
|
|
704
|
-
const identities =
|
|
831
|
+
const identities = this.projectInputIdentities();
|
|
705
832
|
const desired = new Map();
|
|
706
833
|
const required = new Map();
|
|
707
834
|
for (const file of this.projectInputDeclarations("file")) {
|
|
@@ -737,10 +864,15 @@ class WatchTopology {
|
|
|
737
864
|
active.set(identity.key, identity.path);
|
|
738
865
|
}
|
|
739
866
|
this.projectInputRequiredWatchRoots = required;
|
|
740
|
-
(0, syncWatchers_1.syncWatchers)(this.projectInputWatchers, active, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), true, (_event, filename) => {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
867
|
+
(0, syncWatchers_1.syncWatchers)(this.projectInputWatchers, active, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), true, (_event, filename) => this.deferNotification({
|
|
868
|
+
gap: false,
|
|
869
|
+
lane: "project-input",
|
|
870
|
+
location,
|
|
871
|
+
changed: filename === null
|
|
872
|
+
? undefined
|
|
873
|
+
: node_path_1.default.resolve(location, filename),
|
|
874
|
+
rename: false,
|
|
875
|
+
})), (location, error) => {
|
|
744
876
|
const key = identities.resolve(location).key;
|
|
745
877
|
const firstFailure = !this.projectInputRejectedWatchRoots.has(key);
|
|
746
878
|
this.projectInputRejectedWatchRoots.add(key);
|
|
@@ -796,10 +928,15 @@ class WatchTopology {
|
|
|
796
928
|
desired.set(identities.resolve(parent).key, parent);
|
|
797
929
|
}
|
|
798
930
|
}
|
|
799
|
-
(0, syncWatchers_1.syncWatchers)(this.projectInputLinkWatchers, desired, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), false, (_event, filename) => {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
931
|
+
(0, syncWatchers_1.syncWatchers)(this.projectInputLinkWatchers, desired, (location) => this.openDirectoryWatch(watcherRegistrationPath(location), false, (_event, filename) => this.deferNotification({
|
|
932
|
+
gap: false,
|
|
933
|
+
lane: "project-input",
|
|
934
|
+
location,
|
|
935
|
+
changed: filename === null
|
|
936
|
+
? undefined
|
|
937
|
+
: node_path_1.default.resolve(location, filename),
|
|
938
|
+
rename: false,
|
|
939
|
+
})), (location, error) => this.callbacks.onError(location, error), () => this.closed === false);
|
|
803
940
|
}
|
|
804
941
|
/** Drop the watcher that just reported a directory replacement. */
|
|
805
942
|
retireProjectInputWatcher(location, identities) {
|
|
@@ -857,6 +994,7 @@ class WatchTopology {
|
|
|
857
994
|
return;
|
|
858
995
|
this.projectInputRecoveryScheduled = true;
|
|
859
996
|
queueMicrotask(() => {
|
|
997
|
+
this.beginLexicalTransaction();
|
|
860
998
|
try {
|
|
861
999
|
let previousRejectionCount = -1;
|
|
862
1000
|
while (this.closed === false &&
|
|
@@ -895,6 +1033,7 @@ class WatchTopology {
|
|
|
895
1033
|
this.projectInputPostRegistrationReconciliationScheduled = false;
|
|
896
1034
|
if (this.closed)
|
|
897
1035
|
return;
|
|
1036
|
+
this.beginLexicalTransaction();
|
|
898
1037
|
this.refreshPublishedProjectInputIdentities();
|
|
899
1038
|
this.refreshProjectInputs(this.projectInputs.root, undefined, true);
|
|
900
1039
|
});
|
|
@@ -908,8 +1047,9 @@ class WatchTopology {
|
|
|
908
1047
|
* that must observe later target changes.
|
|
909
1048
|
*/
|
|
910
1049
|
refreshPublishedProjectInputIdentities() {
|
|
911
|
-
const
|
|
912
|
-
|
|
1050
|
+
const identities = this.projectInputIdentities();
|
|
1051
|
+
const next = normalizeProjectInputSnapshot(this.declaredProjectInputs, identities);
|
|
1052
|
+
if (projectInputSnapshotsEqual(this.projectInputs, next, identities))
|
|
913
1053
|
return;
|
|
914
1054
|
this.projectInputs = next;
|
|
915
1055
|
this.pruneProjectInputWatchRoots([next, this.declaredProjectInputs]);
|
|
@@ -997,7 +1137,7 @@ class WatchTopology {
|
|
|
997
1137
|
const retained = this.projectInputWatchRoots.get(key);
|
|
998
1138
|
if (retained !== undefined && WatchPaths_1.WatchPaths.isDirectory(retained))
|
|
999
1139
|
return retained;
|
|
1000
|
-
const resolved = ProjectInputWatchRules_1.ProjectInputWatchRules.projectInputRecursiveWatchRoot(target, this.projectInputs.root);
|
|
1140
|
+
const resolved = ProjectInputWatchRules_1.ProjectInputWatchRules.projectInputRecursiveWatchRoot(target, this.projectInputs.root, this.projectInputIdentities());
|
|
1001
1141
|
if (resolved !== undefined)
|
|
1002
1142
|
this.projectInputWatchRoots.set(key, resolved);
|
|
1003
1143
|
return resolved;
|
|
@@ -1006,7 +1146,7 @@ class WatchTopology {
|
|
|
1006
1146
|
this.beginLexicalTransaction();
|
|
1007
1147
|
try {
|
|
1008
1148
|
const previous = this.projectInputMatches;
|
|
1009
|
-
const identities =
|
|
1149
|
+
const identities = this.projectInputIdentities();
|
|
1010
1150
|
// One population for the whole decision. Every question below is asked of
|
|
1011
1151
|
// the same declarations, and rebuilding it per question would both cost
|
|
1012
1152
|
// more and let two answers disagree about what was declared.
|
|
@@ -1033,12 +1173,25 @@ class WatchTopology {
|
|
|
1033
1173
|
}
|
|
1034
1174
|
const next = this.collectProjectInputMatches();
|
|
1035
1175
|
const membershipChanged = WatchPaths_1.WatchPaths.mapsEqual(previous, next) === false;
|
|
1036
|
-
|
|
1176
|
+
// A named notification reads the named path's bytes and any file whose
|
|
1177
|
+
// metadata or owner moved; one that names nothing reads every file
|
|
1178
|
+
// (samchon/ttsc#1748).
|
|
1179
|
+
const scanned = changed === undefined ||
|
|
1037
1180
|
membershipChanged ||
|
|
1038
1181
|
directlyMatched ||
|
|
1039
1182
|
topologyMatched
|
|
1040
|
-
? fingerprintProjectInputMatches(next, this.readDirectory
|
|
1041
|
-
|
|
1183
|
+
? fingerprintProjectInputMatches(next, this.readDirectory, changed === undefined
|
|
1184
|
+
? undefined
|
|
1185
|
+
: {
|
|
1186
|
+
fingerprints: this.projectInputFingerprints,
|
|
1187
|
+
named: identities.resolve(changed).key,
|
|
1188
|
+
snapshots: this.projectInputFileSnapshots,
|
|
1189
|
+
})
|
|
1190
|
+
: {
|
|
1191
|
+
fingerprints: this.projectInputFingerprints,
|
|
1192
|
+
snapshots: this.projectInputFileSnapshots,
|
|
1193
|
+
};
|
|
1194
|
+
const nextFingerprints = scanned.fingerprints;
|
|
1042
1195
|
const contentChanged = WatchPaths_1.WatchPaths.mapsEqual(this.projectInputFingerprints, nextFingerprints) === false;
|
|
1043
1196
|
const changedInputs = projectInputChangedPaths({
|
|
1044
1197
|
next,
|
|
@@ -1065,7 +1218,7 @@ class WatchTopology {
|
|
|
1065
1218
|
reloadDirectories: population.reloadDirectories ?? [],
|
|
1066
1219
|
reloadFiles: population.reloadFiles ?? [],
|
|
1067
1220
|
};
|
|
1068
|
-
const reload = (0, projectInputReloadEventShouldNotify_1.projectInputReloadEventShouldNotify)(reloadInput);
|
|
1221
|
+
const reload = (0, projectInputReloadEventShouldNotify_1.projectInputReloadEventShouldNotify)(reloadInput, identities);
|
|
1069
1222
|
const invalidate = (0, projectInputMembershipInvalidatesProgram_1.projectInputMembershipInvalidatesProgram)({
|
|
1070
1223
|
changed: reconciledChange,
|
|
1071
1224
|
changedInputs,
|
|
@@ -1075,6 +1228,7 @@ class WatchTopology {
|
|
|
1075
1228
|
});
|
|
1076
1229
|
this.projectInputMatches = next;
|
|
1077
1230
|
this.projectInputFingerprints = nextFingerprints;
|
|
1231
|
+
this.projectInputFileSnapshots = scanned.snapshots;
|
|
1078
1232
|
this.syncProjectInputWatchers(skipUnobservedProjectInputWatchRoots);
|
|
1079
1233
|
// A JSON/TS/JS project-input member can simultaneously enter or leave
|
|
1080
1234
|
// the compiler Program. Reconcile the compiler watch snapshot before
|
|
@@ -1126,7 +1280,7 @@ class WatchTopology {
|
|
|
1126
1280
|
}
|
|
1127
1281
|
}
|
|
1128
1282
|
collectProjectInputMatches() {
|
|
1129
|
-
const identities =
|
|
1283
|
+
const identities = this.projectInputIdentities();
|
|
1130
1284
|
const matches = new Map();
|
|
1131
1285
|
// Both spellings are scanned, and each is resolved here rather than when
|
|
1132
1286
|
// the snapshot arrived. A declaration reached through a symlink otherwise
|
|
@@ -1207,20 +1361,37 @@ class WatchTopology {
|
|
|
1207
1361
|
return matches;
|
|
1208
1362
|
}
|
|
1209
1363
|
refreshFromDirectory(location, changed) {
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1364
|
+
this.refreshFromDirectories([{ changed, location }]);
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Refresh compiler membership once for every directory a decision asked
|
|
1368
|
+
* about. A failed refresh still reports, once each, the reload inputs every
|
|
1369
|
+
* requested change names.
|
|
1370
|
+
*/
|
|
1371
|
+
refreshFromDirectories(refreshes) {
|
|
1372
|
+
for (const { changed } of refreshes) {
|
|
1373
|
+
if (changed !== undefined &&
|
|
1374
|
+
WatchPaths_1.WatchPaths.isDirectory(changed) &&
|
|
1375
|
+
this.isCompilerOutputDirectory(changed) === false &&
|
|
1376
|
+
this.isProjectInputDirectory(changed) === false) {
|
|
1377
|
+
this.observedDirectories.set(this.pathKey(changed), changed);
|
|
1378
|
+
}
|
|
1215
1379
|
}
|
|
1216
1380
|
try {
|
|
1217
1381
|
this.refresh(true);
|
|
1218
1382
|
}
|
|
1219
1383
|
catch (error) {
|
|
1220
|
-
|
|
1221
|
-
|
|
1384
|
+
const reported = new Set();
|
|
1385
|
+
for (const { changed } of refreshes) {
|
|
1386
|
+
for (const reload of (0, reloadInputsForFailedTopologyRefresh_1.reloadInputsForFailedTopologyRefresh)(this.reloadFiles.values(), changed, this.lexicalIdentities)) {
|
|
1387
|
+
const key = this.pathKey(reload);
|
|
1388
|
+
if (reported.has(key))
|
|
1389
|
+
continue;
|
|
1390
|
+
reported.add(key);
|
|
1391
|
+
this.callbacks.onInputChange({ kind: "config", path: reload });
|
|
1392
|
+
}
|
|
1222
1393
|
}
|
|
1223
|
-
this.callbacks.onError(location, error);
|
|
1394
|
+
this.callbacks.onError(refreshes[0].location, error);
|
|
1224
1395
|
}
|
|
1225
1396
|
}
|
|
1226
1397
|
isCompilerOutputDirectory(location) {
|
|
@@ -1265,7 +1436,7 @@ class WatchTopology {
|
|
|
1265
1436
|
}
|
|
1266
1437
|
isProjectInputDirectory(location) {
|
|
1267
1438
|
const resolved = node_path_1.default.resolve(location);
|
|
1268
|
-
const identities =
|
|
1439
|
+
const identities = this.projectInputIdentities();
|
|
1269
1440
|
return (this.projectInputs.files.some((file) => identities.isWithin(resolved, file) ||
|
|
1270
1441
|
identities.isWithin(node_path_1.default.dirname(file), resolved)) ||
|
|
1271
1442
|
(this.projectInputs.reloadFiles ?? []).some((file) => identities.isWithin(resolved, file) ||
|
|
@@ -1378,6 +1549,23 @@ class WatchTopology {
|
|
|
1378
1549
|
}
|
|
1379
1550
|
}
|
|
1380
1551
|
exports.WatchTopology = WatchTopology;
|
|
1552
|
+
/**
|
|
1553
|
+
* Everything a compiler listing of `project` is asked with: the project, and
|
|
1554
|
+
* each option `listCompilerInputs` passes to the compiler. Two refreshes with
|
|
1555
|
+
* the same question ask the compiler the same thing; only the disk can answer
|
|
1556
|
+
* them differently.
|
|
1557
|
+
*/
|
|
1558
|
+
function listingQuestion(project, options) {
|
|
1559
|
+
return JSON.stringify([
|
|
1560
|
+
project.path,
|
|
1561
|
+
project.root,
|
|
1562
|
+
options.binary ?? null,
|
|
1563
|
+
options.compilerArgsCwd ?? null,
|
|
1564
|
+
options.env ?? null,
|
|
1565
|
+
options.passthrough ?? null,
|
|
1566
|
+
options.pinCompilerProject ?? null,
|
|
1567
|
+
]);
|
|
1568
|
+
}
|
|
1381
1569
|
function resolveWatchTopology(options, extraInputs, identities, getCompilerInputs) {
|
|
1382
1570
|
const selection = CompilerProjectSelection_1.CompilerProjectSelection.read(options);
|
|
1383
1571
|
if (selection.inspectionError !== undefined)
|
|
@@ -1811,8 +1999,7 @@ function uniqueExistingPaths(paths, identities) {
|
|
|
1811
1999
|
}
|
|
1812
2000
|
return [...unique.values()];
|
|
1813
2001
|
}
|
|
1814
|
-
function normalizeProjectInputSnapshot(snapshot) {
|
|
1815
|
-
const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
|
|
2002
|
+
function normalizeProjectInputSnapshot(snapshot, identities) {
|
|
1816
2003
|
const files = new Map();
|
|
1817
2004
|
const globs = new Map();
|
|
1818
2005
|
const reloadDirectories = new Map();
|
|
@@ -1841,8 +2028,7 @@ function normalizeProjectInputSnapshot(snapshot) {
|
|
|
1841
2028
|
root: identities.resolve(snapshot.root).path,
|
|
1842
2029
|
};
|
|
1843
2030
|
}
|
|
1844
|
-
function projectInputSnapshotsEqual(left, right) {
|
|
1845
|
-
const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
|
|
2031
|
+
function projectInputSnapshotsEqual(left, right, identities) {
|
|
1846
2032
|
return (
|
|
1847
2033
|
// Every sibling comparison in this class decides on identity, and this one
|
|
1848
2034
|
// is only safe today because both operands come from the same normalizer.
|
|
@@ -1885,7 +2071,7 @@ function isSymbolicLink(location) {
|
|
|
1885
2071
|
return false;
|
|
1886
2072
|
}
|
|
1887
2073
|
}
|
|
1888
|
-
function matchesProjectInput(snapshot, location, identities
|
|
2074
|
+
function matchesProjectInput(snapshot, location, identities) {
|
|
1889
2075
|
const key = identities.resolve(location).key;
|
|
1890
2076
|
return (snapshot.files.some((file) => identities.resolve(file).key === key) ||
|
|
1891
2077
|
(snapshot.reloadFiles ?? []).some((file) => identities.resolve(file).key === key) ||
|
|
@@ -1971,8 +2157,7 @@ function compilerMembershipChange(previous, next) {
|
|
|
1971
2157
|
}
|
|
1972
2158
|
return [...changed.values()].sort();
|
|
1973
2159
|
}
|
|
1974
|
-
function projectInputCompilerMembershipChange(snapshot, changed) {
|
|
1975
|
-
const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
|
|
2160
|
+
function projectInputCompilerMembershipChange(snapshot, changed, identities) {
|
|
1976
2161
|
return changed.filter((location) => matchesProjectInput(snapshot, location, identities) &&
|
|
1977
2162
|
ProjectInputWatchRules_1.ProjectInputWatchRules.projectInputPathMayAffectProgram(location));
|
|
1978
2163
|
}
|
|
@@ -1985,8 +2170,7 @@ function projectInputCompilerMembershipChange(snapshot, changed) {
|
|
|
1985
2170
|
* immediate-entry delta instead of turning ordinary project data into a cold
|
|
1986
2171
|
* selection reload. The deepest matching glob owns the most specific delivery.
|
|
1987
2172
|
*/
|
|
1988
|
-
function projectInputCompilerMembershipProjectChanges(locations, globs) {
|
|
1989
|
-
const identities = (0, createProjectInputPathIdentityContext_1.createProjectInputPathIdentityContext)();
|
|
2173
|
+
function projectInputCompilerMembershipProjectChanges(locations, globs, identities) {
|
|
1990
2174
|
const roots = globs.map((glob) => identities.resolve((0, literalGlobRoot_1.literalGlobRoot)(glob)));
|
|
1991
2175
|
const changes = new Map();
|
|
1992
2176
|
for (const location of locations) {
|
|
@@ -2003,14 +2187,39 @@ function projectInputCompilerMembershipProjectChanges(locations, globs) {
|
|
|
2003
2187
|
}
|
|
2004
2188
|
return [...changes.values()].sort();
|
|
2005
2189
|
}
|
|
2006
|
-
|
|
2190
|
+
/**
|
|
2191
|
+
* Fingerprint every matched project input: a directory by its entries, a file
|
|
2192
|
+
* by its bytes.
|
|
2193
|
+
*
|
|
2194
|
+
* Without `previous`, every file is read. With it, a file keeps its previous
|
|
2195
|
+
* digest while its metadata and physical owner hold, unless it is the `named`
|
|
2196
|
+
* path, whose bytes are always read: a notification that names a file is the
|
|
2197
|
+
* one whose same-size rewrite within a timestamp tick metadata cannot show.
|
|
2198
|
+
*/
|
|
2199
|
+
function fingerprintProjectInputMatches(matches, readDirectory, previous) {
|
|
2007
2200
|
const fingerprints = new Map();
|
|
2201
|
+
const snapshots = new Map();
|
|
2008
2202
|
for (const [key, location] of matches) {
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2203
|
+
if (WatchPaths_1.WatchPaths.isDirectory(location)) {
|
|
2204
|
+
fingerprints.set(key, fingerprintProjectInputDirectory(location, readDirectory));
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
2207
|
+
const metadata = compilerFileSnapshot(location, "");
|
|
2208
|
+
const before = previous?.snapshots.get(key);
|
|
2209
|
+
const kept = previous?.fingerprints.get(key);
|
|
2210
|
+
const fingerprint = previous !== undefined &&
|
|
2211
|
+
key !== previous.named &&
|
|
2212
|
+
before !== undefined &&
|
|
2213
|
+
kept !== undefined &&
|
|
2214
|
+
metadata.content !== "" &&
|
|
2215
|
+
before.content === metadata.content &&
|
|
2216
|
+
before.owner === metadata.owner
|
|
2217
|
+
? kept
|
|
2218
|
+
: fingerprintProjectInputFile(location);
|
|
2219
|
+
fingerprints.set(key, fingerprint);
|
|
2220
|
+
snapshots.set(key, { ...metadata, fingerprint });
|
|
2221
|
+
}
|
|
2222
|
+
return { fingerprints, snapshots };
|
|
2014
2223
|
}
|
|
2015
2224
|
function fingerprintProjectInputFile(location) {
|
|
2016
2225
|
try {
|
|
@@ -2052,7 +2261,7 @@ function fingerprintProjectInputDirectory(location, readDirectory) {
|
|
|
2052
2261
|
return "";
|
|
2053
2262
|
}
|
|
2054
2263
|
}
|
|
2055
|
-
function matchesProjectInputGlob(pattern, location, identities
|
|
2264
|
+
function matchesProjectInputGlob(pattern, location, identities) {
|
|
2056
2265
|
const root = identities.resolve((0, literalGlobRoot_1.literalGlobRoot)(pattern));
|
|
2057
2266
|
const candidate = identities.resolve(location);
|
|
2058
2267
|
if (!(0, isProjectInputPathIdentityWithin_1.isProjectInputPathIdentityWithin)(root.key, candidate.key))
|