ttsc 0.19.2 → 0.20.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.
Files changed (178) hide show
  1. package/README.md +7 -5
  2. package/cmd/graphdump/main.go +2 -4
  3. package/cmd/ttsc/filter.go +12 -4
  4. package/cmd/ttsc/filter_resolves_flag_names_case_insensitively_test.go +53 -0
  5. package/cmd/ttsc/flags_gen.go +3 -3
  6. package/cmd/ttscgraph/dump.go +5 -7
  7. package/cmd/ttscgraph/run_dump_emits_only_checker_verified_member_relations_test.go +118 -0
  8. package/cmd/ttscgraph/serve.go +58 -308
  9. package/cmd/ttscgraph/serve_session_keeps_base_url_fallback_unchanged_test.go +49 -0
  10. package/cmd/ttscgraph/serve_session_keeps_esm_directory_candidate_unchanged_test.go +61 -0
  11. package/cmd/ttscgraph/serve_session_keeps_exports_blocked_file_candidates_unchanged_test.go +64 -0
  12. package/cmd/ttscgraph/serve_session_keeps_inactive_conditional_export_unchanged_test.go +66 -0
  13. package/cmd/ttscgraph/serve_session_keeps_lower_priority_module_candidate_unchanged_test.go +67 -0
  14. package/cmd/ttscgraph/serve_session_keeps_lower_priority_paths_pattern_unchanged_test.go +58 -0
  15. package/cmd/ttscgraph/serve_session_keeps_nonlongest_root_dirs_candidate_unchanged_test.go +54 -0
  16. package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +50 -0
  17. package/cmd/ttscgraph/serve_session_reloads_module_suffix_candidate_test.go +41 -0
  18. package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +216 -0
  19. package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +103 -0
  20. package/driver/graph.go +12 -6
  21. package/driver/lsp.go +30 -0
  22. package/driver/program.go +37 -1
  23. package/driver/resolution_candidates.go +819 -0
  24. package/driver/windowsjunction/windowsjunction.go +40 -0
  25. package/go.mod +2 -0
  26. package/internal/graph/build.go +45 -11
  27. package/internal/graph/dump.go +312 -108
  28. package/internal/graph/dump_checkout_paths_are_stable_test.go +210 -0
  29. package/internal/graph/dump_diagnostics_ride_the_generation_that_built_it_test.go +9 -3
  30. package/internal/graph/dump_paths.go +172 -0
  31. package/internal/graph/dump_paths_are_portable_and_injective_test.go +93 -0
  32. package/internal/graph/dump_schema_version_matches_the_typescript_client_test.go +1 -1
  33. package/internal/graph/dump_uses_ecma_line_terminators_test.go +87 -0
  34. package/internal/graph/duplicate_variable_object_members_keep_first_declaration_test.go +61 -0
  35. package/internal/graph/edges.go +220 -5
  36. package/internal/graph/enum_members_carry_their_names_on_the_enum_node_test.go +131 -0
  37. package/internal/graph/graph.go +177 -5
  38. package/internal/graph/line_model.go +122 -0
  39. package/internal/graph/literals.go +175 -0
  40. package/internal/graph/literals_are_absent_when_the_type_admits_more_than_it_can_name_test.go +74 -0
  41. package/internal/graph/literals_cover_enum_members_the_graph_has_no_nodes_for_test.go +79 -0
  42. package/internal/graph/literals_do_not_depend_on_declaration_line_wrapping_test.go +84 -0
  43. package/internal/graph/literals_render_every_value_in_typescript_source_form_test.go +77 -0
  44. package/internal/graph/literals_resolve_through_alias_indirection_test.go +64 -0
  45. package/internal/graph/member_relation_edges_follow_checker_validity_test.go +85 -0
  46. package/internal/graph/member_relation_edges_preserve_checker_boundaries_test.go +191 -0
  47. package/internal/graph/member_relation_edges_reject_checker_invalid_pairs_test.go +119 -0
  48. package/internal/graph/node_ids_preserve_hash_bearing_components_test.go +41 -0
  49. package/internal/graph/node_modifiers_emit_union_strings_test.go +4 -1
  50. package/internal/graph/object_literal_members_ride_on_the_snapshot_test.go +192 -0
  51. package/internal/graph/object_member_signatures_preserve_literal_whitespace_test.go +67 -0
  52. package/internal/graph/object_members.go +229 -0
  53. package/internal/graph/provenance.go +88 -30
  54. package/internal/graph/source_texts_cover_every_resident_program_source_test.go +73 -0
  55. package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +66 -0
  56. package/internal/graphsymbols/provider.go +26 -91
  57. package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +144 -0
  58. package/internal/lspserver/lsp_completion.go +109 -0
  59. package/internal/lspserver/lsp_completion_gate_never_refuses_a_matching_line_test.go +50 -0
  60. package/internal/lspserver/lsp_completion_hints_longest_trigger_wins_test.go +149 -0
  61. package/internal/lspserver/lsp_completion_hints_nearest_trigger_wins_test.go +37 -0
  62. package/internal/lspserver/lsp_completion_merges_into_upstream_response_test.go +149 -0
  63. package/internal/lspserver/lsp_completion_nearest_hint_replaces_nearest_filter_test.go +43 -0
  64. package/internal/lspserver/lsp_completion_preserves_completion_list_extensions_test.go +99 -0
  65. package/internal/lspserver/lsp_completion_refuses_hints_inside_a_string_literal_test.go +69 -0
  66. package/internal/lspserver/lsp_completion_replaces_full_filter_in_rank_order_test.go +85 -0
  67. package/internal/lspserver/lsp_completion_request.go +455 -0
  68. package/internal/lspserver/lsp_completion_scope.go +530 -0
  69. package/internal/lspserver/lsp_completion_scope_bench_test.go +41 -0
  70. package/internal/lspserver/lsp_completion_scope_classifies_regex_statement_and_operator_boundaries_test.go +39 -0
  71. package/internal/lspserver/lsp_completion_scope_ignores_jsdoc_bytes_in_literals_test.go +108 -0
  72. package/internal/lspserver/lsp_completion_scope_refuses_regex_hints_test.go +52 -0
  73. package/internal/lspserver/lsp_completion_trigger_uses_last_rune_test.go +24 -0
  74. package/internal/lspserver/lsp_deleted_watched_file_withdraws_plugin_diagnostics_test.go +110 -0
  75. package/internal/lspserver/lsp_didsave_keeps_the_incremental_changed_uri_path_test.go +78 -0
  76. package/internal/lspserver/lsp_hints_absence_is_not_an_error_test.go +46 -0
  77. package/internal/lspserver/lsp_hints_refresh_coalesces_concurrent_requests_test.go +82 -0
  78. package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +40 -0
  79. package/internal/lspserver/lsp_hints_refresh_keeps_each_producer_isolated_test.go +64 -0
  80. package/internal/lspserver/lsp_hints_refresh_replaces_the_session_snapshot_test.go +66 -0
  81. package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +90 -0
  82. package/internal/lspserver/lsp_hints_refresh_serves_completion_while_running_test.go +91 -0
  83. package/internal/lspserver/lsp_index_byte_from_bounds_its_search_start_test.go +38 -0
  84. package/internal/lspserver/lsp_initialize_constrains_position_encoding_to_utf16_test.go +205 -0
  85. package/internal/lspserver/lsp_late_completion_trigger_is_reported_once_test.go +105 -0
  86. package/internal/lspserver/lsp_native_completion_hints_decode_flat_lint_wire_test.go +66 -0
  87. package/internal/lspserver/lsp_native_completion_hints_grouped_compatibility_boundary_test.go +60 -0
  88. package/internal/lspserver/lsp_native_plugin_source.go +315 -2
  89. package/internal/lspserver/lsp_native_resident.go +304 -0
  90. package/internal/lspserver/lsp_plugin.go +85 -8
  91. package/internal/lspserver/lsp_plugin_completion_resolve_stays_local_test.go +82 -0
  92. package/internal/lspserver/lsp_position_conversions_span_astral_and_combining_runes_test.go +119 -0
  93. package/internal/lspserver/lsp_proxy.go +601 -9
  94. package/internal/lspserver/lsp_refresh.go +70 -0
  95. package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +43 -0
  96. package/internal/lspserver/lsp_resident_maps_verb_args_test.go +34 -0
  97. package/internal/lspserver/lsp_server.go +13 -1
  98. package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +172 -0
  99. package/lib/TtscCompiler.d.ts +3 -1
  100. package/lib/TtscCompiler.js +5 -1
  101. package/lib/TtscCompiler.js.map +1 -1
  102. package/lib/TtscService.d.ts +13 -3
  103. package/lib/TtscService.js +6 -6
  104. package/lib/TtscService.js.map +1 -1
  105. package/lib/compiler/internal/project/readConfigJson.d.ts +30 -0
  106. package/lib/compiler/internal/project/readConfigJson.js +208 -0
  107. package/lib/compiler/internal/project/readConfigJson.js.map +1 -0
  108. package/lib/compiler/internal/project/readProjectConfig.js +53 -149
  109. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  110. package/lib/compiler/internal/residentTransformProcess.d.ts +15 -2
  111. package/lib/compiler/internal/residentTransformProcess.js +159 -39
  112. package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
  113. package/lib/compiler/internal/runBuild.d.ts +6 -0
  114. package/lib/compiler/internal/runBuild.js +143 -44
  115. package/lib/compiler/internal/runBuild.js.map +1 -1
  116. package/lib/compiler/internal/startResidentTransform.d.ts +5 -1
  117. package/lib/compiler/internal/startResidentTransform.js +3 -1
  118. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  119. package/lib/compiler/internal/transformProjectInMemory.js +12 -2
  120. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  121. package/lib/flags/parser.d.ts +19 -0
  122. package/lib/flags/parser.js +78 -36
  123. package/lib/flags/parser.js.map +1 -1
  124. package/lib/flags/schema.d.ts +49 -11
  125. package/lib/flags/schema.js +94 -22
  126. package/lib/flags/schema.js.map +1 -1
  127. package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +5 -0
  128. package/lib/internal/assertSafeExplicitCacheDirectory.js +26 -0
  129. package/lib/internal/assertSafeExplicitCacheDirectory.js.map +1 -0
  130. package/lib/launcher/internal/runTtsc.js +107 -69
  131. package/lib/launcher/internal/runTtsc.js.map +1 -1
  132. package/lib/launcher/internal/runTtsx.js +28 -41
  133. package/lib/launcher/internal/runTtsx.js.map +1 -1
  134. package/lib/launcher/internal/runtimeHooks.d.ts +15 -0
  135. package/lib/launcher/internal/runtimeHooks.js +18 -1
  136. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  137. package/lib/launcher/internal/watchTopology.d.ts +43 -0
  138. package/lib/launcher/internal/watchTopology.js +360 -0
  139. package/lib/launcher/internal/watchTopology.js.map +1 -0
  140. package/lib/plugin/internal/buildSourcePlugin.d.ts +14 -3
  141. package/lib/plugin/internal/buildSourcePlugin.js +247 -61
  142. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  143. package/lib/plugin/internal/loadProjectPlugins.js +63 -2
  144. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  145. package/lib/structures/ITtscCompilerTransformation.d.ts +16 -0
  146. package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +2 -0
  147. package/package.json +8 -8
  148. package/shim/ast/shim.go +48 -3
  149. package/shim/astnav/go.mod +14 -0
  150. package/shim/astnav/go.sum +20 -0
  151. package/shim/astnav/shim.go +15 -0
  152. package/shim/checker/shim.go +113 -0
  153. package/shim/core/shim.go +47 -0
  154. package/shim/diagnosticwriter/lint.go +50 -0
  155. package/src/TtscCompiler.ts +5 -1
  156. package/src/TtscService.ts +37 -10
  157. package/src/compiler/internal/project/readConfigJson.ts +209 -0
  158. package/src/compiler/internal/project/readProjectConfig.ts +62 -158
  159. package/src/compiler/internal/residentTransformProcess.ts +197 -39
  160. package/src/compiler/internal/runBuild.ts +169 -47
  161. package/src/compiler/internal/startResidentTransform.ts +12 -1
  162. package/src/compiler/internal/transformProjectInMemory.ts +12 -1
  163. package/src/flags/parser.ts +113 -42
  164. package/src/flags/schema.ts +113 -31
  165. package/src/internal/assertSafeExplicitCacheDirectory.ts +29 -0
  166. package/src/launcher/internal/runTtsc.ts +117 -68
  167. package/src/launcher/internal/runTtsx.ts +29 -44
  168. package/src/launcher/internal/runtimeHooks.ts +30 -2
  169. package/src/launcher/internal/watchTopology.ts +466 -0
  170. package/src/plugin/internal/buildSourcePlugin.ts +326 -64
  171. package/src/plugin/internal/loadProjectPlugins.ts +80 -2
  172. package/src/structures/ITtscCompilerTransformation.ts +17 -0
  173. package/src/structures/internal/ITtscParsedProjectConfig.ts +2 -0
  174. package/tools/shim_audit/baseline.json +0 -3
  175. package/tools/shim_audit/main.go +42 -14
  176. package/tools/shim_audit/shim_dir_coverage_test.go +25 -0
  177. package/utility/flags_gen.go +3 -3
  178. package/utility/host.go +9 -3
@@ -0,0 +1,466 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { readJsoncFile } from "../../compiler/internal/project/readConfigJson";
5
+ import { readProjectConfig } from "../../compiler/internal/project/readProjectConfig";
6
+ import { resolveTsgo } from "../../compiler/internal/resolveTsgo";
7
+ import { outputText, spawnNative } from "../../compiler/internal/spawnNative";
8
+ import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscParsedProjectConfig";
9
+ import type { TtscBuildOptions } from "../../structures/internal/TtscBuildOptions";
10
+
11
+ type WatchTopologyOptions = Pick<
12
+ TtscBuildOptions,
13
+ "binary" | "env" | "outDir" | "passthrough" | "projectRoot" | "tsconfig"
14
+ > & {
15
+ cwd: string;
16
+ files: readonly string[];
17
+ };
18
+
19
+ type WatchTopologyCallbacks = {
20
+ onError(location: string, error: unknown): void;
21
+ onInputChange(): void;
22
+ onTopologyChange(): void;
23
+ };
24
+
25
+ type ResolvedWatchTopology = {
26
+ directories: Map<string, string>;
27
+ files: Map<string, string>;
28
+ outputs: Map<string, string>;
29
+ };
30
+
31
+ /**
32
+ * Keeps the launcher watch set aligned with the compiler's current program.
33
+ *
34
+ * TypeScript-Go's `--listFilesOnly` output is the authority for source and
35
+ * declaration inputs. Configuration files, project-reference roots, and the
36
+ * source trees of selected native plugins supplement that list, while compiler
37
+ * outputs are filtered before any watcher is installed.
38
+ */
39
+ export class WatchTopology {
40
+ private directories = new Map<string, string>();
41
+ private directoryWatchers = new Map<string, fs.FSWatcher>();
42
+ private extraInputs: readonly string[] = [];
43
+ private extraWatchers = new Map<string, fs.FSWatcher>();
44
+ private files = new Map<string, string>();
45
+ private fileWatchers = new Map<string, fs.FSWatcher>();
46
+ private observedDirectories = new Map<string, string>();
47
+ private outputs = new Map<string, string>();
48
+
49
+ public constructor(
50
+ private readonly options: WatchTopologyOptions,
51
+ private readonly callbacks: WatchTopologyCallbacks,
52
+ ) {}
53
+
54
+ /** Re-resolve compiler inputs and notify only when their membership changed. */
55
+ public refresh(notify: boolean): void {
56
+ const next = resolveWatchTopology(this.options, this.extraInputs);
57
+ const changed =
58
+ mapsEqual(this.files, next.files) === false ||
59
+ mapsEqual(this.directories, next.directories) === false ||
60
+ mapsEqual(this.outputs, next.outputs) === false;
61
+ this.files = next.files;
62
+ this.directories = next.directories;
63
+ this.outputs = next.outputs;
64
+ this.syncFileWatchers();
65
+ this.syncDirectoryWatchers();
66
+ this.syncExtraWatchers();
67
+ if (notify && changed) {
68
+ this.callbacks.onTopologyChange();
69
+ }
70
+ }
71
+
72
+ /** Add Go plugin source trees discovered by the real build lane. */
73
+ public setExtraInputs(inputs: readonly string[]): void {
74
+ const next = uniqueExistingPaths(inputs);
75
+ if (arraysEqual(this.extraInputs, next)) return;
76
+ this.extraInputs = next;
77
+ this.refresh(false);
78
+ }
79
+
80
+ /** Close every watcher so SIGINT/SIGTERM can drain the event loop. */
81
+ public close(): void {
82
+ closeWatchers(this.fileWatchers);
83
+ closeWatchers(this.directoryWatchers);
84
+ closeWatchers(this.extraWatchers);
85
+ }
86
+
87
+ private syncFileWatchers(): void {
88
+ syncWatchers(
89
+ this.fileWatchers,
90
+ this.files,
91
+ (location) =>
92
+ fs.watch(location, { persistent: true }, () => {
93
+ this.callbacks.onInputChange();
94
+ }),
95
+ (location, error) => this.callbacks.onError(location, error),
96
+ );
97
+ }
98
+
99
+ private syncDirectoryWatchers(): void {
100
+ const desired = new Map(this.directories);
101
+ for (const [key, location] of this.observedDirectories) {
102
+ if (
103
+ isDirectory(location) === false ||
104
+ this.isCompilerOutputDirectory(location)
105
+ ) {
106
+ this.observedDirectories.delete(key);
107
+ continue;
108
+ }
109
+ desired.set(key, location);
110
+ }
111
+ syncWatchers(
112
+ this.directoryWatchers,
113
+ desired,
114
+ (location) =>
115
+ fs.watch(location, { persistent: true }, (_event, filename) => {
116
+ const changed =
117
+ filename === null
118
+ ? undefined
119
+ : path.resolve(location, filename.toString());
120
+ // File watchers own ordinary source/config edits. Directory watchers
121
+ // only reconcile membership, so an emit in an unrelated output folder
122
+ // cannot schedule another build.
123
+ if (
124
+ changed !== undefined &&
125
+ this.files.has(pathKey(changed)) &&
126
+ fs.existsSync(changed)
127
+ ) {
128
+ return;
129
+ }
130
+ this.refreshFromDirectory(location, changed);
131
+ }),
132
+ (location, error) => this.callbacks.onError(location, error),
133
+ );
134
+ }
135
+
136
+ private syncExtraWatchers(): void {
137
+ const directories = new Map<string, string>();
138
+ for (const input of this.extraInputs) {
139
+ for (const directory of collectInputDirectories(input)) {
140
+ directories.set(pathKey(directory), directory);
141
+ }
142
+ }
143
+ syncWatchers(
144
+ this.extraWatchers,
145
+ directories,
146
+ (location) =>
147
+ fs.watch(location, { persistent: true }, () => {
148
+ this.callbacks.onInputChange();
149
+ }),
150
+ (location, error) => this.callbacks.onError(location, error),
151
+ );
152
+ }
153
+
154
+ private refreshFromDirectory(location: string, changed?: string): void {
155
+ if (
156
+ changed !== undefined &&
157
+ isDirectory(changed) &&
158
+ this.isCompilerOutputDirectory(changed) === false
159
+ ) {
160
+ this.observedDirectories.set(pathKey(changed), changed);
161
+ }
162
+ try {
163
+ this.refresh(true);
164
+ } catch (error) {
165
+ this.callbacks.onError(location, error);
166
+ }
167
+ }
168
+
169
+ private isCompilerOutputDirectory(location: string): boolean {
170
+ return [...this.outputs.values()].some((output) =>
171
+ isPathWithin(output, location),
172
+ );
173
+ }
174
+ }
175
+
176
+ function resolveWatchTopology(
177
+ options: WatchTopologyOptions,
178
+ extraInputs: readonly string[],
179
+ ): ResolvedWatchTopology {
180
+ const files = new Map<string, string>();
181
+ const outputs = new Map<string, string>();
182
+ const roots: string[] = [];
183
+ if (options.files.length !== 0) {
184
+ const project = readProjectConfig({
185
+ cwd: options.cwd,
186
+ projectRoot: options.projectRoot,
187
+ tsconfig: options.tsconfig,
188
+ });
189
+ roots.push(project.root);
190
+ addPaths(files, project.configPaths);
191
+ addPaths(outputs, resolveCompilerOutputs(project, options).directories);
192
+ addPaths(
193
+ files,
194
+ options.files.map((file) => path.resolve(options.cwd, file)),
195
+ );
196
+ } else {
197
+ for (const project of readReferencedProjects(options)) {
198
+ roots.push(project.root);
199
+ addPaths(files, project.configPaths);
200
+ addPaths(outputs, resolveCompilerOutputs(project, options).directories);
201
+ addPaths(files, listCompilerInputs(project, options));
202
+ }
203
+ }
204
+ addPaths(files, extraInputs);
205
+ return {
206
+ directories: collectTopologyDirectories(files.values(), roots),
207
+ files,
208
+ outputs,
209
+ };
210
+ }
211
+
212
+ function readReferencedProjects(
213
+ options: WatchTopologyOptions,
214
+ ): ITtscParsedProjectConfig[] {
215
+ const root = readProjectConfig({
216
+ cwd: options.cwd,
217
+ projectRoot: options.projectRoot,
218
+ tsconfig: options.tsconfig,
219
+ });
220
+ const projects: ITtscParsedProjectConfig[] = [];
221
+ const queue = [root];
222
+ const seen = new Set<string>();
223
+ while (queue.length !== 0) {
224
+ const project = queue.shift()!;
225
+ if (seen.has(pathKey(project.path))) continue;
226
+ seen.add(pathKey(project.path));
227
+ projects.push(project);
228
+ for (const reference of readProjectReferences(project.path)) {
229
+ queue.push(
230
+ readProjectConfig({
231
+ cwd: path.dirname(project.path),
232
+ tsconfig: reference,
233
+ }),
234
+ );
235
+ }
236
+ }
237
+ return projects;
238
+ }
239
+
240
+ function readProjectReferences(tsconfig: string): string[] {
241
+ const parsed = readJsoncFile(tsconfig);
242
+ if (
243
+ isRecord(parsed) === false ||
244
+ Array.isArray(parsed.references) === false
245
+ ) {
246
+ return [];
247
+ }
248
+ const base = path.dirname(tsconfig);
249
+ const references: string[] = [];
250
+ for (const reference of parsed.references) {
251
+ if (
252
+ isRecord(reference) === false ||
253
+ typeof reference.path !== "string" ||
254
+ reference.path.length === 0
255
+ ) {
256
+ continue;
257
+ }
258
+ references.push(path.resolve(base, reference.path));
259
+ }
260
+ return references;
261
+ }
262
+
263
+ function listCompilerInputs(
264
+ project: ITtscParsedProjectConfig,
265
+ options: WatchTopologyOptions,
266
+ ): string[] {
267
+ const tsgo = resolveTsgo({
268
+ binary: options.binary,
269
+ cwd: project.root,
270
+ env: options.env,
271
+ });
272
+ const result = spawnNative(
273
+ tsgo.binary,
274
+ [
275
+ "-p",
276
+ project.path,
277
+ "--listFilesOnly",
278
+ "--pretty",
279
+ "false",
280
+ ...(options.passthrough ?? []),
281
+ ],
282
+ {
283
+ cwd: project.root,
284
+ env: { ...process.env, ...options.env },
285
+ encoding: "utf8",
286
+ },
287
+ );
288
+ if (result.error) {
289
+ throw new Error(
290
+ `ttsc: failed to list compiler inputs: ${result.error.message}`,
291
+ );
292
+ }
293
+ const outputs = resolveCompilerOutputs(project, options);
294
+ const inputs = outputText(result.stdout)
295
+ .split(/\r?\n/)
296
+ .map((line) => line.trim())
297
+ .filter((line) => path.isAbsolute(line))
298
+ .map((line) => path.resolve(line))
299
+ .filter((file) => isCompilerOutput(file, outputs) === false);
300
+ if (result.status !== 0 && inputs.length === 0) {
301
+ throw new Error(
302
+ `ttsc: failed to list compiler inputs:\n${outputText(result.stderr) || outputText(result.stdout)}`,
303
+ );
304
+ }
305
+ return inputs;
306
+ }
307
+
308
+ function resolveCompilerOutputs(
309
+ project: ITtscParsedProjectConfig,
310
+ options: WatchTopologyOptions,
311
+ ): { directories: string[]; files: string[] } {
312
+ const compilerOptions = project.compilerOptions;
313
+ const outDir = options.outDir
314
+ ? path.resolve(options.cwd, options.outDir)
315
+ : compilerOptions.outDir;
316
+ return {
317
+ directories: [outDir, compilerOptions.declarationDir]
318
+ .filter((value): value is string => typeof value === "string")
319
+ .map((value) => path.resolve(value)),
320
+ files: [compilerOptions.outFile, compilerOptions.tsBuildInfoFile]
321
+ .filter((value): value is string => typeof value === "string")
322
+ .map((value) => path.resolve(project.root, value)),
323
+ };
324
+ }
325
+
326
+ function isCompilerOutput(
327
+ file: string,
328
+ outputs: { directories: readonly string[]; files: readonly string[] },
329
+ ): boolean {
330
+ return (
331
+ outputs.files.some((output) => pathKey(output) === pathKey(file)) ||
332
+ outputs.directories.some((directory) => isPathWithin(directory, file))
333
+ );
334
+ }
335
+
336
+ function collectTopologyDirectories(
337
+ files: Iterable<string>,
338
+ roots: readonly string[],
339
+ ): Map<string, string> {
340
+ const directories = new Map<string, string>();
341
+ for (const root of roots) {
342
+ directories.set(pathKey(root), root);
343
+ }
344
+ for (const file of files) {
345
+ const directory = path.dirname(file);
346
+ const root = roots.find((candidate) => isPathWithin(candidate, directory));
347
+ if (root === undefined) {
348
+ directories.set(pathKey(directory), directory);
349
+ continue;
350
+ }
351
+ let current = directory;
352
+ while (true) {
353
+ directories.set(pathKey(current), current);
354
+ if (pathKey(current) === pathKey(root)) break;
355
+ const parent = path.dirname(current);
356
+ if (parent === current) break;
357
+ current = parent;
358
+ }
359
+ }
360
+ return directories;
361
+ }
362
+
363
+ function collectInputDirectories(input: string): string[] {
364
+ if (isDirectory(input) === false) return [];
365
+ const directories: string[] = [];
366
+ const stack = [input];
367
+ while (stack.length !== 0) {
368
+ const current = stack.pop()!;
369
+ directories.push(current);
370
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
371
+ if (entry.isDirectory()) {
372
+ stack.push(path.join(current, entry.name));
373
+ }
374
+ }
375
+ }
376
+ return directories;
377
+ }
378
+
379
+ function isDirectory(location: string): boolean {
380
+ try {
381
+ return fs.statSync(location).isDirectory();
382
+ } catch {
383
+ return false;
384
+ }
385
+ }
386
+
387
+ function syncWatchers(
388
+ watchers: Map<string, fs.FSWatcher>,
389
+ desired: ReadonlyMap<string, string>,
390
+ create: (location: string) => fs.FSWatcher,
391
+ onError: (location: string, error: unknown) => void,
392
+ ): void {
393
+ for (const [key, watcher] of watchers) {
394
+ if (desired.has(key)) continue;
395
+ watcher.close();
396
+ watchers.delete(key);
397
+ }
398
+ for (const [key, location] of desired) {
399
+ if (watchers.has(key)) continue;
400
+ try {
401
+ const watcher = create(location);
402
+ watcher.on("error", (error) => onError(location, error));
403
+ watchers.set(key, watcher);
404
+ } catch (error) {
405
+ onError(location, error);
406
+ }
407
+ }
408
+ }
409
+
410
+ function closeWatchers(watchers: Map<string, fs.FSWatcher>): void {
411
+ for (const watcher of watchers.values()) watcher.close();
412
+ watchers.clear();
413
+ }
414
+
415
+ function addPaths(target: Map<string, string>, paths: Iterable<string>): void {
416
+ for (const location of paths) {
417
+ const resolved = path.resolve(location);
418
+ target.set(pathKey(resolved), resolved);
419
+ }
420
+ }
421
+
422
+ function uniqueExistingPaths(paths: readonly string[]): string[] {
423
+ const unique = new Map<string, string>();
424
+ for (const location of paths) {
425
+ if (location.length === 0) continue;
426
+ const resolved = path.resolve(location);
427
+ unique.set(pathKey(resolved), resolved);
428
+ }
429
+ return [...unique.values()];
430
+ }
431
+
432
+ function mapsEqual(
433
+ left: ReadonlyMap<string, string>,
434
+ right: ReadonlyMap<string, string>,
435
+ ): boolean {
436
+ if (left.size !== right.size) return false;
437
+ return [...left].every(([key, value]) => right.get(key) === value);
438
+ }
439
+
440
+ function arraysEqual(
441
+ left: readonly string[],
442
+ right: readonly string[],
443
+ ): boolean {
444
+ return (
445
+ left.length === right.length && left.every((value, i) => value === right[i])
446
+ );
447
+ }
448
+
449
+ function isPathWithin(root: string, candidate: string): boolean {
450
+ const relative = path.relative(root, candidate);
451
+ return (
452
+ relative === "" ||
453
+ (relative !== ".." &&
454
+ relative.startsWith(`..${path.sep}`) === false &&
455
+ !path.isAbsolute(relative))
456
+ );
457
+ }
458
+
459
+ function isRecord(value: unknown): value is Record<string, unknown> {
460
+ return typeof value === "object" && value !== null;
461
+ }
462
+
463
+ function pathKey(location: string): string {
464
+ const resolved = path.resolve(location);
465
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
466
+ }