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.
- package/README.md +7 -5
- package/cmd/graphdump/main.go +2 -4
- package/cmd/ttsc/filter.go +12 -4
- package/cmd/ttsc/filter_resolves_flag_names_case_insensitively_test.go +53 -0
- package/cmd/ttsc/flags_gen.go +3 -3
- package/cmd/ttscgraph/dump.go +5 -7
- package/cmd/ttscgraph/run_dump_emits_only_checker_verified_member_relations_test.go +118 -0
- package/cmd/ttscgraph/serve.go +58 -308
- package/cmd/ttscgraph/serve_session_keeps_base_url_fallback_unchanged_test.go +49 -0
- package/cmd/ttscgraph/serve_session_keeps_esm_directory_candidate_unchanged_test.go +61 -0
- package/cmd/ttscgraph/serve_session_keeps_exports_blocked_file_candidates_unchanged_test.go +64 -0
- package/cmd/ttscgraph/serve_session_keeps_inactive_conditional_export_unchanged_test.go +66 -0
- package/cmd/ttscgraph/serve_session_keeps_lower_priority_module_candidate_unchanged_test.go +67 -0
- package/cmd/ttscgraph/serve_session_keeps_lower_priority_paths_pattern_unchanged_test.go +58 -0
- package/cmd/ttscgraph/serve_session_keeps_nonlongest_root_dirs_candidate_unchanged_test.go +54 -0
- package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +50 -0
- package/cmd/ttscgraph/serve_session_reloads_module_suffix_candidate_test.go +41 -0
- package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +216 -0
- package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +103 -0
- package/driver/graph.go +12 -6
- package/driver/lsp.go +30 -0
- package/driver/program.go +37 -1
- package/driver/resolution_candidates.go +819 -0
- package/driver/windowsjunction/windowsjunction.go +40 -0
- package/go.mod +2 -0
- package/internal/graph/build.go +45 -11
- package/internal/graph/dump.go +312 -108
- package/internal/graph/dump_checkout_paths_are_stable_test.go +210 -0
- package/internal/graph/dump_diagnostics_ride_the_generation_that_built_it_test.go +9 -3
- package/internal/graph/dump_paths.go +172 -0
- package/internal/graph/dump_paths_are_portable_and_injective_test.go +93 -0
- package/internal/graph/dump_schema_version_matches_the_typescript_client_test.go +1 -1
- package/internal/graph/dump_uses_ecma_line_terminators_test.go +87 -0
- package/internal/graph/duplicate_variable_object_members_keep_first_declaration_test.go +61 -0
- package/internal/graph/edges.go +220 -5
- package/internal/graph/enum_members_carry_their_names_on_the_enum_node_test.go +131 -0
- package/internal/graph/graph.go +177 -5
- package/internal/graph/line_model.go +122 -0
- package/internal/graph/literals.go +175 -0
- package/internal/graph/literals_are_absent_when_the_type_admits_more_than_it_can_name_test.go +74 -0
- package/internal/graph/literals_cover_enum_members_the_graph_has_no_nodes_for_test.go +79 -0
- package/internal/graph/literals_do_not_depend_on_declaration_line_wrapping_test.go +84 -0
- package/internal/graph/literals_render_every_value_in_typescript_source_form_test.go +77 -0
- package/internal/graph/literals_resolve_through_alias_indirection_test.go +64 -0
- package/internal/graph/member_relation_edges_follow_checker_validity_test.go +85 -0
- package/internal/graph/member_relation_edges_preserve_checker_boundaries_test.go +191 -0
- package/internal/graph/member_relation_edges_reject_checker_invalid_pairs_test.go +119 -0
- package/internal/graph/node_ids_preserve_hash_bearing_components_test.go +41 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +4 -1
- package/internal/graph/object_literal_members_ride_on_the_snapshot_test.go +192 -0
- package/internal/graph/object_member_signatures_preserve_literal_whitespace_test.go +67 -0
- package/internal/graph/object_members.go +229 -0
- package/internal/graph/provenance.go +88 -30
- package/internal/graph/source_texts_cover_every_resident_program_source_test.go +73 -0
- package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +66 -0
- package/internal/graphsymbols/provider.go +26 -91
- package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +144 -0
- package/internal/lspserver/lsp_completion.go +109 -0
- package/internal/lspserver/lsp_completion_gate_never_refuses_a_matching_line_test.go +50 -0
- package/internal/lspserver/lsp_completion_hints_longest_trigger_wins_test.go +149 -0
- package/internal/lspserver/lsp_completion_hints_nearest_trigger_wins_test.go +37 -0
- package/internal/lspserver/lsp_completion_merges_into_upstream_response_test.go +149 -0
- package/internal/lspserver/lsp_completion_nearest_hint_replaces_nearest_filter_test.go +43 -0
- package/internal/lspserver/lsp_completion_preserves_completion_list_extensions_test.go +99 -0
- package/internal/lspserver/lsp_completion_refuses_hints_inside_a_string_literal_test.go +69 -0
- package/internal/lspserver/lsp_completion_replaces_full_filter_in_rank_order_test.go +85 -0
- package/internal/lspserver/lsp_completion_request.go +455 -0
- package/internal/lspserver/lsp_completion_scope.go +530 -0
- package/internal/lspserver/lsp_completion_scope_bench_test.go +41 -0
- package/internal/lspserver/lsp_completion_scope_classifies_regex_statement_and_operator_boundaries_test.go +39 -0
- package/internal/lspserver/lsp_completion_scope_ignores_jsdoc_bytes_in_literals_test.go +108 -0
- package/internal/lspserver/lsp_completion_scope_refuses_regex_hints_test.go +52 -0
- package/internal/lspserver/lsp_completion_trigger_uses_last_rune_test.go +24 -0
- package/internal/lspserver/lsp_deleted_watched_file_withdraws_plugin_diagnostics_test.go +110 -0
- package/internal/lspserver/lsp_didsave_keeps_the_incremental_changed_uri_path_test.go +78 -0
- package/internal/lspserver/lsp_hints_absence_is_not_an_error_test.go +46 -0
- package/internal/lspserver/lsp_hints_refresh_coalesces_concurrent_requests_test.go +82 -0
- package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +40 -0
- package/internal/lspserver/lsp_hints_refresh_keeps_each_producer_isolated_test.go +64 -0
- package/internal/lspserver/lsp_hints_refresh_replaces_the_session_snapshot_test.go +66 -0
- package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +90 -0
- package/internal/lspserver/lsp_hints_refresh_serves_completion_while_running_test.go +91 -0
- package/internal/lspserver/lsp_index_byte_from_bounds_its_search_start_test.go +38 -0
- package/internal/lspserver/lsp_initialize_constrains_position_encoding_to_utf16_test.go +205 -0
- package/internal/lspserver/lsp_late_completion_trigger_is_reported_once_test.go +105 -0
- package/internal/lspserver/lsp_native_completion_hints_decode_flat_lint_wire_test.go +66 -0
- package/internal/lspserver/lsp_native_completion_hints_grouped_compatibility_boundary_test.go +60 -0
- package/internal/lspserver/lsp_native_plugin_source.go +315 -2
- package/internal/lspserver/lsp_native_resident.go +304 -0
- package/internal/lspserver/lsp_plugin.go +85 -8
- package/internal/lspserver/lsp_plugin_completion_resolve_stays_local_test.go +82 -0
- package/internal/lspserver/lsp_position_conversions_span_astral_and_combining_runes_test.go +119 -0
- package/internal/lspserver/lsp_proxy.go +601 -9
- package/internal/lspserver/lsp_refresh.go +70 -0
- package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +43 -0
- package/internal/lspserver/lsp_resident_maps_verb_args_test.go +34 -0
- package/internal/lspserver/lsp_server.go +13 -1
- package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +172 -0
- package/lib/TtscCompiler.d.ts +3 -1
- package/lib/TtscCompiler.js +5 -1
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/TtscService.d.ts +13 -3
- package/lib/TtscService.js +6 -6
- package/lib/TtscService.js.map +1 -1
- package/lib/compiler/internal/project/readConfigJson.d.ts +30 -0
- package/lib/compiler/internal/project/readConfigJson.js +208 -0
- package/lib/compiler/internal/project/readConfigJson.js.map +1 -0
- package/lib/compiler/internal/project/readProjectConfig.js +53 -149
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/residentTransformProcess.d.ts +15 -2
- package/lib/compiler/internal/residentTransformProcess.js +159 -39
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +6 -0
- package/lib/compiler/internal/runBuild.js +143 -44
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.d.ts +5 -1
- package/lib/compiler/internal/startResidentTransform.js +3 -1
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +12 -2
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/parser.d.ts +19 -0
- package/lib/flags/parser.js +78 -36
- package/lib/flags/parser.js.map +1 -1
- package/lib/flags/schema.d.ts +49 -11
- package/lib/flags/schema.js +94 -22
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +5 -0
- package/lib/internal/assertSafeExplicitCacheDirectory.js +26 -0
- package/lib/internal/assertSafeExplicitCacheDirectory.js.map +1 -0
- package/lib/launcher/internal/runTtsc.js +107 -69
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +28 -41
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +15 -0
- package/lib/launcher/internal/runtimeHooks.js +18 -1
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/launcher/internal/watchTopology.d.ts +43 -0
- package/lib/launcher/internal/watchTopology.js +360 -0
- package/lib/launcher/internal/watchTopology.js.map +1 -0
- package/lib/plugin/internal/buildSourcePlugin.d.ts +14 -3
- package/lib/plugin/internal/buildSourcePlugin.js +247 -61
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.js +63 -2
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerTransformation.d.ts +16 -0
- package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +2 -0
- package/package.json +8 -8
- package/shim/ast/shim.go +48 -3
- package/shim/astnav/go.mod +14 -0
- package/shim/astnav/go.sum +20 -0
- package/shim/astnav/shim.go +15 -0
- package/shim/checker/shim.go +113 -0
- package/shim/core/shim.go +47 -0
- package/shim/diagnosticwriter/lint.go +50 -0
- package/src/TtscCompiler.ts +5 -1
- package/src/TtscService.ts +37 -10
- package/src/compiler/internal/project/readConfigJson.ts +209 -0
- package/src/compiler/internal/project/readProjectConfig.ts +62 -158
- package/src/compiler/internal/residentTransformProcess.ts +197 -39
- package/src/compiler/internal/runBuild.ts +169 -47
- package/src/compiler/internal/startResidentTransform.ts +12 -1
- package/src/compiler/internal/transformProjectInMemory.ts +12 -1
- package/src/flags/parser.ts +113 -42
- package/src/flags/schema.ts +113 -31
- package/src/internal/assertSafeExplicitCacheDirectory.ts +29 -0
- package/src/launcher/internal/runTtsc.ts +117 -68
- package/src/launcher/internal/runTtsx.ts +29 -44
- package/src/launcher/internal/runtimeHooks.ts +30 -2
- package/src/launcher/internal/watchTopology.ts +466 -0
- package/src/plugin/internal/buildSourcePlugin.ts +326 -64
- package/src/plugin/internal/loadProjectPlugins.ts +80 -2
- package/src/structures/ITtscCompilerTransformation.ts +17 -0
- package/src/structures/internal/ITtscParsedProjectConfig.ts +2 -0
- package/tools/shim_audit/baseline.json +0 -3
- package/tools/shim_audit/main.go +42 -14
- package/tools/shim_audit/shim_dir_coverage_test.go +25 -0
- package/utility/flags_gen.go +3 -3
- 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
|
+
}
|