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
|
@@ -11,6 +11,7 @@ import { spawnNative } from "../../../compiler/internal/spawnNative";
|
|
|
11
11
|
import { normalizeCompilerEnumValue } from "../../../flags/normalizeCompilerEnumValue";
|
|
12
12
|
import { readCompilerOptionValues } from "../../../flags/readCompilerOptionValues";
|
|
13
13
|
import { type ProjectInputPathIdentityContext } from "../../../internal/pathIdentity/ProjectInputPathIdentityContext";
|
|
14
|
+
import { createCaseSensitivityObservations } from "../../../internal/pathIdentity/createCaseSensitivityObservations";
|
|
14
15
|
import { createProjectInputPathIdentityContext } from "../../../internal/pathIdentity/createProjectInputPathIdentityContext";
|
|
15
16
|
import { isProjectInputPathIdentityWithin } from "../../../internal/pathIdentity/isProjectInputPathIdentityWithin";
|
|
16
17
|
import { resolveProjectInputPath } from "../../../internal/pathIdentity/resolveProjectInputPath";
|
|
@@ -46,26 +47,26 @@ import { watchDirectory } from "./watchDirectory";
|
|
|
46
47
|
* output for source and declaration membership. An explicit reader supplies the
|
|
47
48
|
* same absolute-path membership and failure contract for each resolved project.
|
|
48
49
|
* Configuration files, project-reference roots, output inference and native
|
|
49
|
-
* filesystem identity remain this topology's responsibility.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
50
|
+
* filesystem identity remain this topology's responsibility. Response frames
|
|
51
|
+
* are observed once per project refresh at its compiler cwd; that option view
|
|
52
|
+
* serves all project-output queries while the original argv reaches compiler
|
|
53
|
+
* membership. A second observation rejects response changes across membership
|
|
54
|
+
* resolution. Response files join the config reload set; these observations do
|
|
55
|
+
* not pin files or detect a change restored in between.
|
|
55
56
|
*
|
|
56
57
|
* `TTSC_WATCH_DEBUG_INPUTS` reports the named event, observed population deltas
|
|
57
58
|
* and reload decision. These diagnostics reuse the decision's existing inputs;
|
|
58
59
|
* they perform no extra filesystem observations.
|
|
59
60
|
*
|
|
60
61
|
* @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.
|
|
62
|
+
* @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
63
|
* @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
64
|
* @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
65
|
* @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
66
|
*
|
|
66
67
|
* @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#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.
|
|
69
|
+
* @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
70
|
*/
|
|
70
71
|
export class WatchTopology {
|
|
71
72
|
private analysisOnly = false;
|
|
@@ -102,15 +103,42 @@ export class WatchTopology {
|
|
|
102
103
|
/** The plugin inputs whose directories the last sync already watched. */
|
|
103
104
|
private watchedExtraInputs = new Set<string>();
|
|
104
105
|
private compilerFileSnapshots = new Map<string, CompilerFileSnapshot>();
|
|
106
|
+
|
|
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
|
+
private compilerListings = new Map<string, CompilerListing>();
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Notifications the current poll iteration delivered, decided together in the
|
|
116
|
+
* check phase (`deferNotification`).
|
|
117
|
+
*/
|
|
118
|
+
private pendingNotifications = new Map<string, PendingNotification>();
|
|
119
|
+
private notificationsScheduled = false;
|
|
105
120
|
private files = new Map<string, string>();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The current decision's directory case-policy observations, which every
|
|
124
|
+
* identity context the decision creates shares (samchon/ttsc#1742).
|
|
125
|
+
*/
|
|
126
|
+
private caseObservations!: ReturnType<
|
|
127
|
+
typeof createCaseSensitivityObservations
|
|
128
|
+
>;
|
|
129
|
+
private lexicalIdentities!: ProjectInputPathIdentityContext;
|
|
109
130
|
private fileWatchers = new Map<string, fs.FSWatcher>();
|
|
110
131
|
private observedDirectories = new Map<string, string>();
|
|
111
132
|
private outputFiles = new Map<string, string>();
|
|
112
133
|
private outputs = new Map<string, string>();
|
|
113
134
|
private projectInputFingerprints = new Map<string, string>();
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The metadata and physical owner each matched project-input file had when
|
|
138
|
+
* its fingerprint was taken, by match key; a named notification re-reads only
|
|
139
|
+
* the files whose metadata moved (samchon/ttsc#1748).
|
|
140
|
+
*/
|
|
141
|
+
private projectInputFileSnapshots = new Map<string, CompilerFileSnapshot>();
|
|
114
142
|
private projectInputMatches = new Map<string, string>();
|
|
115
143
|
private projectInputs: ITtscProjectInputSnapshot = {
|
|
116
144
|
files: [],
|
|
@@ -154,6 +182,8 @@ export class WatchTopology {
|
|
|
154
182
|
* membership; the default invokes the native compiler. Readers throw
|
|
155
183
|
* listing failures and retain ownership of their returned array, which this
|
|
156
184
|
* topology does not mutate.
|
|
185
|
+
* @param observeCaseSensitivity Create one decision's directory case-policy
|
|
186
|
+
* observations; the default asks the native filesystem.
|
|
157
187
|
*/
|
|
158
188
|
public constructor(
|
|
159
189
|
private readonly options: WatchTopologyOptions,
|
|
@@ -162,7 +192,10 @@ export class WatchTopology {
|
|
|
162
192
|
private readonly openFileWatch: typeof fs.watch = fs.watch,
|
|
163
193
|
private readonly readDirectory: typeof fs.readdirSync = fs.readdirSync,
|
|
164
194
|
private readonly getCompilerInputs: typeof listCompilerInputs = listCompilerInputs,
|
|
165
|
-
|
|
195
|
+
private readonly observeCaseSensitivity: typeof createCaseSensitivityObservations = createCaseSensitivityObservations,
|
|
196
|
+
) {
|
|
197
|
+
this.beginLexicalTransaction();
|
|
198
|
+
}
|
|
166
199
|
|
|
167
200
|
/**
|
|
168
201
|
* Re-resolve compiler inputs and notify when the resolved topology changes,
|
|
@@ -183,17 +216,34 @@ export class WatchTopology {
|
|
|
183
216
|
this.refreshCompilerInputs(notify, false);
|
|
184
217
|
}
|
|
185
218
|
|
|
219
|
+
/**
|
|
220
|
+
* @param reuseListings Answer each project's membership with its last listing
|
|
221
|
+
* under the same arguments instead of asking the compiler again. Only a
|
|
222
|
+
* refresh whose cause cannot move Program membership passes it.
|
|
223
|
+
*/
|
|
186
224
|
private refreshCompilerInputs(
|
|
187
225
|
notify: boolean,
|
|
188
226
|
skipUnobservedProjectInputWatchRoots: boolean,
|
|
227
|
+
reuseListings: boolean = false,
|
|
189
228
|
): void {
|
|
190
229
|
this.beginLexicalTransaction();
|
|
230
|
+
const listings = new Map<string, CompilerListing>();
|
|
191
231
|
const next = resolveWatchTopology(
|
|
192
232
|
this.options,
|
|
193
233
|
this.extraInputs,
|
|
194
234
|
this.lexicalIdentities,
|
|
195
|
-
|
|
235
|
+
(project, options) => {
|
|
236
|
+
const question = listingQuestion(project, options);
|
|
237
|
+
const last = this.compilerListings.get(project.path);
|
|
238
|
+
const inputs =
|
|
239
|
+
reuseListings && last?.question === question
|
|
240
|
+
? last.inputs
|
|
241
|
+
: this.getCompilerInputs(project, options);
|
|
242
|
+
listings.set(project.path, { inputs, question });
|
|
243
|
+
return inputs;
|
|
244
|
+
},
|
|
196
245
|
);
|
|
246
|
+
this.compilerListings = listings;
|
|
197
247
|
const compilerProgramMembershipChange =
|
|
198
248
|
next.analysisOnly &&
|
|
199
249
|
WatchPaths.mapsEqual(this.reloadFiles, next.reloadFiles) &&
|
|
@@ -204,6 +254,7 @@ export class WatchTopology {
|
|
|
204
254
|
const projectInputProgramOverlap = projectInputCompilerMembershipChange(
|
|
205
255
|
this.projectInputs,
|
|
206
256
|
compilerProgramMembershipChange,
|
|
257
|
+
this.projectInputIdentities(),
|
|
207
258
|
);
|
|
208
259
|
const projectInputProgramChange =
|
|
209
260
|
compilerProgramMembershipChange.length !== 0 &&
|
|
@@ -293,7 +344,7 @@ export class WatchTopology {
|
|
|
293
344
|
changed: readonly string[],
|
|
294
345
|
): boolean {
|
|
295
346
|
const matches = this.collectProjectInputMatches();
|
|
296
|
-
const fingerprints = fingerprintProjectInputMatches(
|
|
347
|
+
const { fingerprints, snapshots } = fingerprintProjectInputMatches(
|
|
297
348
|
matches,
|
|
298
349
|
this.readDirectory,
|
|
299
350
|
);
|
|
@@ -307,6 +358,7 @@ export class WatchTopology {
|
|
|
307
358
|
const causedBy = projectInputCompilerMembershipProjectChanges(
|
|
308
359
|
changed,
|
|
309
360
|
population.globs,
|
|
361
|
+
this.projectInputIdentities(),
|
|
310
362
|
);
|
|
311
363
|
const reloadInput = {
|
|
312
364
|
causedBy,
|
|
@@ -316,7 +368,10 @@ export class WatchTopology {
|
|
|
316
368
|
reloadDirectories: population.reloadDirectories,
|
|
317
369
|
reloadFiles: population.reloadFiles ?? [],
|
|
318
370
|
};
|
|
319
|
-
const reload = projectInputReloadEventShouldNotify(
|
|
371
|
+
const reload = projectInputReloadEventShouldNotify(
|
|
372
|
+
reloadInput,
|
|
373
|
+
this.projectInputIdentities(),
|
|
374
|
+
);
|
|
320
375
|
reportProjectInputDecision({
|
|
321
376
|
source: "compiler-membership",
|
|
322
377
|
...reloadInput,
|
|
@@ -328,6 +383,7 @@ export class WatchTopology {
|
|
|
328
383
|
// instead of disappearing behind the warm compiler-membership handoff.
|
|
329
384
|
this.projectInputMatches = matches;
|
|
330
385
|
this.projectInputFingerprints = fingerprints;
|
|
386
|
+
this.projectInputFileSnapshots = snapshots;
|
|
331
387
|
return reload;
|
|
332
388
|
}
|
|
333
389
|
|
|
@@ -353,8 +409,8 @@ export class WatchTopology {
|
|
|
353
409
|
* @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
410
|
* @evidence contracts/common.md#meaningful-documentation Native prose explains baseline timing and why existing inputs are not restamped following the documentation skill.
|
|
355
411
|
* @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.
|
|
412
|
+
* @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.
|
|
413
|
+
* @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
414
|
* @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
415
|
*/
|
|
360
416
|
public setExtraInputs(
|
|
@@ -382,7 +438,12 @@ export class WatchTopology {
|
|
|
382
438
|
);
|
|
383
439
|
}
|
|
384
440
|
this.pluginInputStates = states;
|
|
385
|
-
|
|
441
|
+
// Plugin inputs join the watched files and directories, never Program
|
|
442
|
+
// membership, so the compiler's last answer stands. A load reports up to
|
|
443
|
+
// three different sets, and asking again for each spawned tsgo three times
|
|
444
|
+
// per load (samchon/ttsc#1741). A membership change meanwhile reaches the
|
|
445
|
+
// refresh its own notification starts, which lists again.
|
|
446
|
+
this.refreshCompilerInputs(false, false, true);
|
|
386
447
|
}
|
|
387
448
|
|
|
388
449
|
/**
|
|
@@ -399,7 +460,11 @@ export class WatchTopology {
|
|
|
399
460
|
* @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
461
|
*/
|
|
401
462
|
public setProjectInputs(inputs: ITtscProjectInputSnapshot): void {
|
|
402
|
-
|
|
463
|
+
this.beginLexicalTransaction();
|
|
464
|
+
const next = normalizeProjectInputSnapshot(
|
|
465
|
+
inputs,
|
|
466
|
+
this.projectInputIdentities(),
|
|
467
|
+
);
|
|
403
468
|
// The declared spellings are recorded even when the normalized snapshot did
|
|
404
469
|
// not move, because a republication can carry a new alias for identities
|
|
405
470
|
// that already matched, and anchoring the spelling that was retired would
|
|
@@ -408,7 +473,13 @@ export class WatchTopology {
|
|
|
408
473
|
inputs.declared === undefined
|
|
409
474
|
? inputs
|
|
410
475
|
: { ...inputs.declared, root: inputs.root };
|
|
411
|
-
if (
|
|
476
|
+
if (
|
|
477
|
+
projectInputSnapshotsEqual(
|
|
478
|
+
this.projectInputs,
|
|
479
|
+
next,
|
|
480
|
+
this.projectInputIdentities(),
|
|
481
|
+
)
|
|
482
|
+
) {
|
|
412
483
|
this.syncProjectInputWatchers();
|
|
413
484
|
return;
|
|
414
485
|
}
|
|
@@ -420,10 +491,12 @@ export class WatchTopology {
|
|
|
420
491
|
{ ...(inputs.declared ?? inputs), root: inputs.root },
|
|
421
492
|
]);
|
|
422
493
|
this.projectInputMatches = this.collectProjectInputMatches();
|
|
423
|
-
|
|
494
|
+
const baseline = fingerprintProjectInputMatches(
|
|
424
495
|
this.projectInputMatches,
|
|
425
496
|
this.readDirectory,
|
|
426
497
|
);
|
|
498
|
+
this.projectInputFingerprints = baseline.fingerprints;
|
|
499
|
+
this.projectInputFileSnapshots = baseline.snapshots;
|
|
427
500
|
this.syncProjectInputWatchers();
|
|
428
501
|
}
|
|
429
502
|
|
|
@@ -457,11 +530,26 @@ export class WatchTopology {
|
|
|
457
530
|
|
|
458
531
|
/** Start fresh native case observations for one synchronous watch decision. */
|
|
459
532
|
private beginLexicalTransaction(): void {
|
|
533
|
+
this.caseObservations = this.observeCaseSensitivity();
|
|
460
534
|
this.lexicalIdentities = createProjectInputPathIdentityContext({
|
|
535
|
+
caseSensitive: this.caseObservations,
|
|
461
536
|
throwOnRealpathError: false,
|
|
462
537
|
});
|
|
463
538
|
}
|
|
464
539
|
|
|
540
|
+
/**
|
|
541
|
+
* A strict project-input identity context for the current decision. Its
|
|
542
|
+
* physical paths are resolved afresh, and its case answers are the
|
|
543
|
+
* decision's: one decision builds such contexts per declaration and per
|
|
544
|
+
* candidate, and each asked every directory again, which on Windows is an
|
|
545
|
+
* `fsutil` process per question (samchon/ttsc#1742).
|
|
546
|
+
*/
|
|
547
|
+
private projectInputIdentities(): ProjectInputPathIdentityContext {
|
|
548
|
+
return createProjectInputPathIdentityContext({
|
|
549
|
+
caseSensitive: this.caseObservations,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
465
553
|
/** Keep lexical alias ownership while sharing this decision's case probes. */
|
|
466
554
|
private pathKey(location: string): string {
|
|
467
555
|
return WatchPaths.pathKey(location, this.lexicalIdentities);
|
|
@@ -585,34 +673,17 @@ export class WatchTopology {
|
|
|
585
673
|
this.openDirectoryWatch(
|
|
586
674
|
watcherRegistrationPath(location),
|
|
587
675
|
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,
|
|
676
|
+
(event, filename, gap) =>
|
|
677
|
+
this.deferNotification({
|
|
678
|
+
gap: gap === true,
|
|
679
|
+
lane: "compiler",
|
|
601
680
|
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
|
-
},
|
|
681
|
+
changed:
|
|
682
|
+
filename === null
|
|
683
|
+
? undefined
|
|
684
|
+
: path.resolve(location, filename),
|
|
685
|
+
rename: event === "rename",
|
|
686
|
+
}),
|
|
616
687
|
),
|
|
617
688
|
(location, error) => this.callbacks.onError(location, error),
|
|
618
689
|
() => this.closed === false,
|
|
@@ -622,6 +693,87 @@ export class WatchTopology {
|
|
|
622
693
|
);
|
|
623
694
|
}
|
|
624
695
|
|
|
696
|
+
/**
|
|
697
|
+
* Decide a notification in the event loop's check phase, together with every
|
|
698
|
+
* other notification the same poll iteration delivered (samchon/ttsc#1741).
|
|
699
|
+
*
|
|
700
|
+
* One filesystem change usually arrives as several notifications, rename and
|
|
701
|
+
* change or create and modify, and each listed the compiler inputs or
|
|
702
|
+
* rescanned the project inputs again. Identical notifications of one lane,
|
|
703
|
+
* location and path merge, keeping a rename or a gap any of them carried, and
|
|
704
|
+
* the batch refreshes compiler membership once after deciding them all. The
|
|
705
|
+
* refresh therefore starts after every notification it absorbs, and one
|
|
706
|
+
* delivered later schedules another batch.
|
|
707
|
+
*/
|
|
708
|
+
private deferNotification(notification: PendingNotification): void {
|
|
709
|
+
if (this.closed) return;
|
|
710
|
+
const key = [
|
|
711
|
+
notification.lane,
|
|
712
|
+
notification.location,
|
|
713
|
+
notification.changed ?? "",
|
|
714
|
+
].join("\0");
|
|
715
|
+
const pending = this.pendingNotifications.get(key);
|
|
716
|
+
if (pending !== undefined) {
|
|
717
|
+
pending.rename ||= notification.rename;
|
|
718
|
+
pending.gap ||= notification.gap;
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
this.pendingNotifications.set(key, { ...notification });
|
|
722
|
+
if (this.notificationsScheduled) return;
|
|
723
|
+
this.notificationsScheduled = true;
|
|
724
|
+
setImmediate(() => this.decideNotifications());
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
private decideNotifications(): void {
|
|
728
|
+
this.notificationsScheduled = false;
|
|
729
|
+
const pending = [...this.pendingNotifications.values()];
|
|
730
|
+
this.pendingNotifications.clear();
|
|
731
|
+
if (this.closed) return;
|
|
732
|
+
const refreshes: DirectoryRefresh[] = [];
|
|
733
|
+
for (const notification of pending) {
|
|
734
|
+
if (notification.lane === "project-input") {
|
|
735
|
+
this.refreshProjectInputs(notification.location, notification.changed);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
this.decideCompilerDirectoryNotification(notification, refreshes);
|
|
739
|
+
}
|
|
740
|
+
if (refreshes.length === 0 || this.closed) return;
|
|
741
|
+
this.beginLexicalTransaction();
|
|
742
|
+
this.refreshFromDirectories(refreshes);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/** Decide one merged directory notification of the compiler lane. */
|
|
746
|
+
private decideCompilerDirectoryNotification(
|
|
747
|
+
notification: PendingNotification,
|
|
748
|
+
refreshes: DirectoryRefresh[],
|
|
749
|
+
): void {
|
|
750
|
+
this.beginLexicalTransaction();
|
|
751
|
+
const { changed, location } = notification;
|
|
752
|
+
const pluginInput = changed ?? location;
|
|
753
|
+
if (this.isPluginInput(pluginInput)) {
|
|
754
|
+
this.notePluginNotification(pluginInput);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const plan = planCompilerDirectoryWatchEvent({
|
|
758
|
+
changed,
|
|
759
|
+
event: notification.rename ? "rename" : "change",
|
|
760
|
+
exists: fs.existsSync,
|
|
761
|
+
location,
|
|
762
|
+
identities: this.lexicalIdentities,
|
|
763
|
+
platform: process.platform,
|
|
764
|
+
trackedFiles: this.files,
|
|
765
|
+
});
|
|
766
|
+
this.rearmFileWatchers(plan.rearm);
|
|
767
|
+
for (const file of this.compilerChangesToReport(
|
|
768
|
+
plan.changes,
|
|
769
|
+
changed,
|
|
770
|
+
notification.gap,
|
|
771
|
+
)) {
|
|
772
|
+
this.reportCompilerFileChange(file);
|
|
773
|
+
}
|
|
774
|
+
if (plan.refresh) refreshes.push({ changed, location });
|
|
775
|
+
}
|
|
776
|
+
|
|
625
777
|
/**
|
|
626
778
|
* Reconcile tracked compiler files after a newly registered watcher returns.
|
|
627
779
|
*
|
|
@@ -730,6 +882,7 @@ export class WatchTopology {
|
|
|
730
882
|
if (fs.existsSync(location)) {
|
|
731
883
|
if (this.projectInputMatches.size !== 0) {
|
|
732
884
|
const key = createProjectInputPathIdentityContext({
|
|
885
|
+
caseSensitive: this.caseObservations,
|
|
733
886
|
throwOnRealpathError: false,
|
|
734
887
|
}).resolve(location).key;
|
|
735
888
|
if (
|
|
@@ -840,7 +993,7 @@ export class WatchTopology {
|
|
|
840
993
|
if (this.closed) return;
|
|
841
994
|
const previous = new Map(this.projectInputWatchers);
|
|
842
995
|
const previousLinks = new Map(this.projectInputLinkWatchers);
|
|
843
|
-
const identities =
|
|
996
|
+
const identities = this.projectInputIdentities();
|
|
844
997
|
const desired = new Map<string, string>();
|
|
845
998
|
const required = new Map<string, string>();
|
|
846
999
|
for (const file of this.projectInputDeclarations("file")) {
|
|
@@ -924,11 +1077,17 @@ export class WatchTopology {
|
|
|
924
1077
|
this.openDirectoryWatch(
|
|
925
1078
|
watcherRegistrationPath(location),
|
|
926
1079
|
true,
|
|
927
|
-
(_event, filename) =>
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1080
|
+
(_event, filename) =>
|
|
1081
|
+
this.deferNotification({
|
|
1082
|
+
gap: false,
|
|
1083
|
+
lane: "project-input",
|
|
1084
|
+
location,
|
|
1085
|
+
changed:
|
|
1086
|
+
filename === null
|
|
1087
|
+
? undefined
|
|
1088
|
+
: path.resolve(location, filename),
|
|
1089
|
+
rename: false,
|
|
1090
|
+
}),
|
|
932
1091
|
),
|
|
933
1092
|
(location, error) => {
|
|
934
1093
|
const key = identities.resolve(location).key;
|
|
@@ -1003,11 +1162,17 @@ export class WatchTopology {
|
|
|
1003
1162
|
this.openDirectoryWatch(
|
|
1004
1163
|
watcherRegistrationPath(location),
|
|
1005
1164
|
false,
|
|
1006
|
-
(_event, filename) =>
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1165
|
+
(_event, filename) =>
|
|
1166
|
+
this.deferNotification({
|
|
1167
|
+
gap: false,
|
|
1168
|
+
lane: "project-input",
|
|
1169
|
+
location,
|
|
1170
|
+
changed:
|
|
1171
|
+
filename === null
|
|
1172
|
+
? undefined
|
|
1173
|
+
: path.resolve(location, filename),
|
|
1174
|
+
rename: false,
|
|
1175
|
+
}),
|
|
1011
1176
|
),
|
|
1012
1177
|
(location, error) => this.callbacks.onError(location, error),
|
|
1013
1178
|
() => this.closed === false,
|
|
@@ -1088,6 +1253,7 @@ export class WatchTopology {
|
|
|
1088
1253
|
if (this.projectInputRecoveryScheduled) return;
|
|
1089
1254
|
this.projectInputRecoveryScheduled = true;
|
|
1090
1255
|
queueMicrotask(() => {
|
|
1256
|
+
this.beginLexicalTransaction();
|
|
1091
1257
|
try {
|
|
1092
1258
|
let previousRejectionCount = -1;
|
|
1093
1259
|
while (
|
|
@@ -1131,6 +1297,7 @@ export class WatchTopology {
|
|
|
1131
1297
|
queueMicrotask(() => {
|
|
1132
1298
|
this.projectInputPostRegistrationReconciliationScheduled = false;
|
|
1133
1299
|
if (this.closed) return;
|
|
1300
|
+
this.beginLexicalTransaction();
|
|
1134
1301
|
this.refreshPublishedProjectInputIdentities();
|
|
1135
1302
|
this.refreshProjectInputs(this.projectInputs.root, undefined, true);
|
|
1136
1303
|
});
|
|
@@ -1145,8 +1312,13 @@ export class WatchTopology {
|
|
|
1145
1312
|
* that must observe later target changes.
|
|
1146
1313
|
*/
|
|
1147
1314
|
private refreshPublishedProjectInputIdentities(): void {
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1315
|
+
const identities = this.projectInputIdentities();
|
|
1316
|
+
const next = normalizeProjectInputSnapshot(
|
|
1317
|
+
this.declaredProjectInputs,
|
|
1318
|
+
identities,
|
|
1319
|
+
);
|
|
1320
|
+
if (projectInputSnapshotsEqual(this.projectInputs, next, identities))
|
|
1321
|
+
return;
|
|
1150
1322
|
this.projectInputs = next;
|
|
1151
1323
|
this.pruneProjectInputWatchRoots([next, this.declaredProjectInputs]);
|
|
1152
1324
|
}
|
|
@@ -1264,6 +1436,7 @@ export class WatchTopology {
|
|
|
1264
1436
|
const resolved = ProjectInputWatchRules.projectInputRecursiveWatchRoot(
|
|
1265
1437
|
target,
|
|
1266
1438
|
this.projectInputs.root,
|
|
1439
|
+
this.projectInputIdentities(),
|
|
1267
1440
|
);
|
|
1268
1441
|
if (resolved !== undefined) this.projectInputWatchRoots.set(key, resolved);
|
|
1269
1442
|
return resolved;
|
|
@@ -1277,7 +1450,7 @@ export class WatchTopology {
|
|
|
1277
1450
|
this.beginLexicalTransaction();
|
|
1278
1451
|
try {
|
|
1279
1452
|
const previous = this.projectInputMatches;
|
|
1280
|
-
const identities =
|
|
1453
|
+
const identities = this.projectInputIdentities();
|
|
1281
1454
|
// One population for the whole decision. Every question below is asked of
|
|
1282
1455
|
// the same declarations, and rebuilding it per question would both cost
|
|
1283
1456
|
// more and let two answers disagree about what was declared.
|
|
@@ -1315,13 +1488,30 @@ export class WatchTopology {
|
|
|
1315
1488
|
}
|
|
1316
1489
|
const next = this.collectProjectInputMatches();
|
|
1317
1490
|
const membershipChanged = WatchPaths.mapsEqual(previous, next) === false;
|
|
1318
|
-
|
|
1491
|
+
// A named notification reads the named path's bytes and any file whose
|
|
1492
|
+
// metadata or owner moved; one that names nothing reads every file
|
|
1493
|
+
// (samchon/ttsc#1748).
|
|
1494
|
+
const scanned =
|
|
1319
1495
|
changed === undefined ||
|
|
1320
1496
|
membershipChanged ||
|
|
1321
1497
|
directlyMatched ||
|
|
1322
1498
|
topologyMatched
|
|
1323
|
-
? fingerprintProjectInputMatches(
|
|
1324
|
-
|
|
1499
|
+
? fingerprintProjectInputMatches(
|
|
1500
|
+
next,
|
|
1501
|
+
this.readDirectory,
|
|
1502
|
+
changed === undefined
|
|
1503
|
+
? undefined
|
|
1504
|
+
: {
|
|
1505
|
+
fingerprints: this.projectInputFingerprints,
|
|
1506
|
+
named: identities.resolve(changed).key,
|
|
1507
|
+
snapshots: this.projectInputFileSnapshots,
|
|
1508
|
+
},
|
|
1509
|
+
)
|
|
1510
|
+
: {
|
|
1511
|
+
fingerprints: this.projectInputFingerprints,
|
|
1512
|
+
snapshots: this.projectInputFileSnapshots,
|
|
1513
|
+
};
|
|
1514
|
+
const nextFingerprints = scanned.fingerprints;
|
|
1325
1515
|
const contentChanged =
|
|
1326
1516
|
WatchPaths.mapsEqual(
|
|
1327
1517
|
this.projectInputFingerprints,
|
|
@@ -1352,7 +1542,10 @@ export class WatchTopology {
|
|
|
1352
1542
|
reloadDirectories: population.reloadDirectories ?? [],
|
|
1353
1543
|
reloadFiles: population.reloadFiles ?? [],
|
|
1354
1544
|
};
|
|
1355
|
-
const reload = projectInputReloadEventShouldNotify(
|
|
1545
|
+
const reload = projectInputReloadEventShouldNotify(
|
|
1546
|
+
reloadInput,
|
|
1547
|
+
identities,
|
|
1548
|
+
);
|
|
1356
1549
|
const invalidate = projectInputMembershipInvalidatesProgram({
|
|
1357
1550
|
changed: reconciledChange,
|
|
1358
1551
|
changedInputs,
|
|
@@ -1362,6 +1555,7 @@ export class WatchTopology {
|
|
|
1362
1555
|
});
|
|
1363
1556
|
this.projectInputMatches = next;
|
|
1364
1557
|
this.projectInputFingerprints = nextFingerprints;
|
|
1558
|
+
this.projectInputFileSnapshots = scanned.snapshots;
|
|
1365
1559
|
this.syncProjectInputWatchers(skipUnobservedProjectInputWatchRoots);
|
|
1366
1560
|
// A JSON/TS/JS project-input member can simultaneously enter or leave
|
|
1367
1561
|
// the compiler Program. Reconcile the compiler watch snapshot before
|
|
@@ -1416,7 +1610,7 @@ export class WatchTopology {
|
|
|
1416
1610
|
}
|
|
1417
1611
|
|
|
1418
1612
|
private collectProjectInputMatches(): Map<string, string> {
|
|
1419
|
-
const identities =
|
|
1613
|
+
const identities = this.projectInputIdentities();
|
|
1420
1614
|
const matches = new Map<string, string>();
|
|
1421
1615
|
// Both spellings are scanned, and each is resolved here rather than when
|
|
1422
1616
|
// the snapshot arrived. A declaration reached through a symlink otherwise
|
|
@@ -1510,25 +1704,42 @@ export class WatchTopology {
|
|
|
1510
1704
|
}
|
|
1511
1705
|
|
|
1512
1706
|
private refreshFromDirectory(location: string, changed?: string): void {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1707
|
+
this.refreshFromDirectories([{ changed, location }]);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
/**
|
|
1711
|
+
* Refresh compiler membership once for every directory a decision asked
|
|
1712
|
+
* about. A failed refresh still reports, once each, the reload inputs every
|
|
1713
|
+
* requested change names.
|
|
1714
|
+
*/
|
|
1715
|
+
private refreshFromDirectories(refreshes: readonly DirectoryRefresh[]): void {
|
|
1716
|
+
for (const { changed } of refreshes) {
|
|
1717
|
+
if (
|
|
1718
|
+
changed !== undefined &&
|
|
1719
|
+
WatchPaths.isDirectory(changed) &&
|
|
1720
|
+
this.isCompilerOutputDirectory(changed) === false &&
|
|
1721
|
+
this.isProjectInputDirectory(changed) === false
|
|
1722
|
+
) {
|
|
1723
|
+
this.observedDirectories.set(this.pathKey(changed), changed);
|
|
1724
|
+
}
|
|
1520
1725
|
}
|
|
1521
1726
|
try {
|
|
1522
1727
|
this.refresh(true);
|
|
1523
1728
|
} catch (error) {
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1729
|
+
const reported = new Set<string>();
|
|
1730
|
+
for (const { changed } of refreshes) {
|
|
1731
|
+
for (const reload of reloadInputsForFailedTopologyRefresh(
|
|
1732
|
+
this.reloadFiles.values(),
|
|
1733
|
+
changed,
|
|
1734
|
+
this.lexicalIdentities,
|
|
1735
|
+
)) {
|
|
1736
|
+
const key = this.pathKey(reload);
|
|
1737
|
+
if (reported.has(key)) continue;
|
|
1738
|
+
reported.add(key);
|
|
1739
|
+
this.callbacks.onInputChange({ kind: "config", path: reload });
|
|
1740
|
+
}
|
|
1530
1741
|
}
|
|
1531
|
-
this.callbacks.onError(location, error);
|
|
1742
|
+
this.callbacks.onError(refreshes[0]!.location, error);
|
|
1532
1743
|
}
|
|
1533
1744
|
}
|
|
1534
1745
|
|
|
@@ -1591,7 +1802,7 @@ export class WatchTopology {
|
|
|
1591
1802
|
|
|
1592
1803
|
private isProjectInputDirectory(location: string): boolean {
|
|
1593
1804
|
const resolved = path.resolve(location);
|
|
1594
|
-
const identities =
|
|
1805
|
+
const identities = this.projectInputIdentities();
|
|
1595
1806
|
return (
|
|
1596
1807
|
this.projectInputs.files.some(
|
|
1597
1808
|
(file) =>
|
|
@@ -1764,6 +1975,46 @@ type CompilerFileSnapshot = {
|
|
|
1764
1975
|
owner: string;
|
|
1765
1976
|
};
|
|
1766
1977
|
|
|
1978
|
+
type PendingNotification = {
|
|
1979
|
+
changed: string | undefined;
|
|
1980
|
+
gap: boolean;
|
|
1981
|
+
lane: "compiler" | "project-input";
|
|
1982
|
+
location: string;
|
|
1983
|
+
rename: boolean;
|
|
1984
|
+
};
|
|
1985
|
+
|
|
1986
|
+
type DirectoryRefresh = {
|
|
1987
|
+
changed: string | undefined;
|
|
1988
|
+
location: string;
|
|
1989
|
+
};
|
|
1990
|
+
|
|
1991
|
+
type CompilerListing = {
|
|
1992
|
+
/** The reader's own array, which the topology never mutates. */
|
|
1993
|
+
inputs: string[];
|
|
1994
|
+
question: string;
|
|
1995
|
+
};
|
|
1996
|
+
|
|
1997
|
+
/**
|
|
1998
|
+
* Everything a compiler listing of `project` is asked with: the project, and
|
|
1999
|
+
* each option `listCompilerInputs` passes to the compiler. Two refreshes with
|
|
2000
|
+
* the same question ask the compiler the same thing; only the disk can answer
|
|
2001
|
+
* them differently.
|
|
2002
|
+
*/
|
|
2003
|
+
function listingQuestion(
|
|
2004
|
+
project: ITtscParsedProjectConfig,
|
|
2005
|
+
options: WatchTopologyOptions,
|
|
2006
|
+
): string {
|
|
2007
|
+
return JSON.stringify([
|
|
2008
|
+
project.path,
|
|
2009
|
+
project.root,
|
|
2010
|
+
options.binary ?? null,
|
|
2011
|
+
options.compilerArgsCwd ?? null,
|
|
2012
|
+
options.env ?? null,
|
|
2013
|
+
options.passthrough ?? null,
|
|
2014
|
+
options.pinCompilerProject ?? null,
|
|
2015
|
+
]);
|
|
2016
|
+
}
|
|
2017
|
+
|
|
1767
2018
|
type CompilerFileMovement = {
|
|
1768
2019
|
content: boolean;
|
|
1769
2020
|
owner: boolean;
|
|
@@ -1836,7 +2087,10 @@ function resolveWatchTopology(
|
|
|
1836
2087
|
addPaths(reloadFiles, inspected.responseFiles, identities);
|
|
1837
2088
|
const compilerInputs = getCompilerInputs(project, options);
|
|
1838
2089
|
CompilerProjectSelection.assertCurrent(selection);
|
|
1839
|
-
const compilerOutputs = resolveCompilerOutputs(
|
|
2090
|
+
const compilerOutputs = resolveCompilerOutputs(
|
|
2091
|
+
project,
|
|
2092
|
+
inspected.options,
|
|
2093
|
+
);
|
|
1840
2094
|
addPaths(outputFiles, compilerOutputs.files, identities);
|
|
1841
2095
|
addPaths(
|
|
1842
2096
|
outputFiles,
|
|
@@ -2355,8 +2609,8 @@ function uniqueExistingPaths(
|
|
|
2355
2609
|
|
|
2356
2610
|
function normalizeProjectInputSnapshot(
|
|
2357
2611
|
snapshot: ITtscProjectInputSnapshot,
|
|
2612
|
+
identities: ProjectInputPathIdentityContext,
|
|
2358
2613
|
): ITtscProjectInputSnapshot {
|
|
2359
|
-
const identities = createProjectInputPathIdentityContext();
|
|
2360
2614
|
const files = new Map<string, string>();
|
|
2361
2615
|
const globs = new Map<string, string>();
|
|
2362
2616
|
const reloadDirectories = new Map<string, string>();
|
|
@@ -2389,8 +2643,8 @@ function normalizeProjectInputSnapshot(
|
|
|
2389
2643
|
function projectInputSnapshotsEqual(
|
|
2390
2644
|
left: ITtscProjectInputSnapshot,
|
|
2391
2645
|
right: ITtscProjectInputSnapshot,
|
|
2646
|
+
identities: ProjectInputPathIdentityContext,
|
|
2392
2647
|
): boolean {
|
|
2393
|
-
const identities = createProjectInputPathIdentityContext();
|
|
2394
2648
|
return (
|
|
2395
2649
|
// Every sibling comparison in this class decides on identity, and this one
|
|
2396
2650
|
// is only safe today because both operands come from the same normalizer.
|
|
@@ -2445,7 +2699,7 @@ function isSymbolicLink(location: string): boolean {
|
|
|
2445
2699
|
function matchesProjectInput(
|
|
2446
2700
|
snapshot: ITtscProjectInputSnapshot,
|
|
2447
2701
|
location: string,
|
|
2448
|
-
identities
|
|
2702
|
+
identities: ProjectInputPathIdentityContext,
|
|
2449
2703
|
): boolean {
|
|
2450
2704
|
const key = identities.resolve(location).key;
|
|
2451
2705
|
return (
|
|
@@ -2572,8 +2826,8 @@ function compilerMembershipChange(
|
|
|
2572
2826
|
function projectInputCompilerMembershipChange(
|
|
2573
2827
|
snapshot: ITtscProjectInputSnapshot,
|
|
2574
2828
|
changed: readonly string[],
|
|
2829
|
+
identities: ProjectInputPathIdentityContext,
|
|
2575
2830
|
): string[] {
|
|
2576
|
-
const identities = createProjectInputPathIdentityContext();
|
|
2577
2831
|
return changed.filter(
|
|
2578
2832
|
(location) =>
|
|
2579
2833
|
matchesProjectInput(snapshot, location, identities) &&
|
|
@@ -2593,8 +2847,8 @@ function projectInputCompilerMembershipChange(
|
|
|
2593
2847
|
function projectInputCompilerMembershipProjectChanges(
|
|
2594
2848
|
locations: readonly string[],
|
|
2595
2849
|
globs: readonly string[],
|
|
2850
|
+
identities: ProjectInputPathIdentityContext,
|
|
2596
2851
|
): string[] {
|
|
2597
|
-
const identities = createProjectInputPathIdentityContext();
|
|
2598
2852
|
const roots = globs.map((glob) => identities.resolve(literalGlobRoot(glob)));
|
|
2599
2853
|
const changes = new Map<string, string>();
|
|
2600
2854
|
for (const location of locations) {
|
|
@@ -2614,20 +2868,54 @@ function projectInputCompilerMembershipProjectChanges(
|
|
|
2614
2868
|
return [...changes.values()].sort();
|
|
2615
2869
|
}
|
|
2616
2870
|
|
|
2871
|
+
/**
|
|
2872
|
+
* Fingerprint every matched project input: a directory by its entries, a file
|
|
2873
|
+
* by its bytes.
|
|
2874
|
+
*
|
|
2875
|
+
* Without `previous`, every file is read. With it, a file keeps its previous
|
|
2876
|
+
* digest while its metadata and physical owner hold, unless it is the `named`
|
|
2877
|
+
* path, whose bytes are always read: a notification that names a file is the
|
|
2878
|
+
* one whose same-size rewrite within a timestamp tick metadata cannot show.
|
|
2879
|
+
*/
|
|
2617
2880
|
function fingerprintProjectInputMatches(
|
|
2618
2881
|
matches: ReadonlyMap<string, string>,
|
|
2619
2882
|
readDirectory: typeof fs.readdirSync,
|
|
2620
|
-
|
|
2883
|
+
previous?: {
|
|
2884
|
+
fingerprints: ReadonlyMap<string, string>;
|
|
2885
|
+
named: string;
|
|
2886
|
+
snapshots: ReadonlyMap<string, CompilerFileSnapshot>;
|
|
2887
|
+
},
|
|
2888
|
+
): {
|
|
2889
|
+
fingerprints: Map<string, string>;
|
|
2890
|
+
snapshots: Map<string, CompilerFileSnapshot>;
|
|
2891
|
+
} {
|
|
2621
2892
|
const fingerprints = new Map<string, string>();
|
|
2893
|
+
const snapshots = new Map<string, CompilerFileSnapshot>();
|
|
2622
2894
|
for (const [key, location] of matches) {
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2895
|
+
if (WatchPaths.isDirectory(location)) {
|
|
2896
|
+
fingerprints.set(
|
|
2897
|
+
key,
|
|
2898
|
+
fingerprintProjectInputDirectory(location, readDirectory),
|
|
2899
|
+
);
|
|
2900
|
+
continue;
|
|
2901
|
+
}
|
|
2902
|
+
const metadata = compilerFileSnapshot(location, "");
|
|
2903
|
+
const before = previous?.snapshots.get(key);
|
|
2904
|
+
const kept = previous?.fingerprints.get(key);
|
|
2905
|
+
const fingerprint =
|
|
2906
|
+
previous !== undefined &&
|
|
2907
|
+
key !== previous.named &&
|
|
2908
|
+
before !== undefined &&
|
|
2909
|
+
kept !== undefined &&
|
|
2910
|
+
metadata.content !== "" &&
|
|
2911
|
+
before.content === metadata.content &&
|
|
2912
|
+
before.owner === metadata.owner
|
|
2913
|
+
? kept
|
|
2914
|
+
: fingerprintProjectInputFile(location);
|
|
2915
|
+
fingerprints.set(key, fingerprint);
|
|
2916
|
+
snapshots.set(key, { ...metadata, fingerprint });
|
|
2917
|
+
}
|
|
2918
|
+
return { fingerprints, snapshots };
|
|
2631
2919
|
}
|
|
2632
2920
|
|
|
2633
2921
|
function fingerprintProjectInputFile(location: string): string {
|
|
@@ -2675,7 +2963,7 @@ function fingerprintProjectInputDirectory(
|
|
|
2675
2963
|
function matchesProjectInputGlob(
|
|
2676
2964
|
pattern: string,
|
|
2677
2965
|
location: string,
|
|
2678
|
-
identities
|
|
2966
|
+
identities: ProjectInputPathIdentityContext,
|
|
2679
2967
|
): boolean {
|
|
2680
2968
|
const root = identities.resolve(literalGlobRoot(pattern));
|
|
2681
2969
|
const candidate = identities.resolve(location);
|