ttsc 0.19.3 → 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 +276 -100
- 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_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 +208 -0
- package/internal/graph/graph.go +143 -5
- package/internal/graph/line_model.go +122 -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 +100 -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,209 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Attributed JSON / JSONC readers for every configuration file ttsc owns.
|
|
5
|
+
*
|
|
6
|
+
* A bare `JSON.parse` failure reports a byte offset in an unnamed file, which
|
|
7
|
+
* is unusable exactly where attribution matters most: an `extends` chain, where
|
|
8
|
+
* any ancestor could be the source, and a watch session, which reprints the
|
|
9
|
+
* same line on every save. Every ttsc-owned configuration read goes through
|
|
10
|
+
* these helpers so the failure names its file and position in ttsc's own
|
|
11
|
+
* diagnostic voice, like the neighbouring `ttsc: extended tsconfig not found:
|
|
12
|
+
* …`.
|
|
13
|
+
*
|
|
14
|
+
* Comment, trailing-comma, and BOM removal is length-preserving (each removed
|
|
15
|
+
* character becomes a space, each removed newline stays a newline), so the line
|
|
16
|
+
* and column the JSON parser reports are the position in the file the user
|
|
17
|
+
* actually edited rather than in a shortened intermediate string.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Read and parse a strict-JSON configuration file (`package.json`), naming it
|
|
22
|
+
* on failure.
|
|
23
|
+
*
|
|
24
|
+
* A leading UTF-8 BOM is accepted, matching the tsconfig reader that closed
|
|
25
|
+
* issue #216: the two readers are consulted for the same project and
|
|
26
|
+
* disagreeing about a byte order mark would only surprise the user who hit it.
|
|
27
|
+
*/
|
|
28
|
+
export function readJsonFile(file: string): unknown {
|
|
29
|
+
const text = stripLeadingBom(fs.readFileSync(file, "utf8"));
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(text);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new Error(`ttsc: failed to parse ${file}: ${describe(error)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Read and parse a JSONC (JSON with comments and trailing commas) configuration
|
|
39
|
+
* file — tsconfig.json, jsconfig.json — naming it on failure.
|
|
40
|
+
*/
|
|
41
|
+
export function readJsoncFile(file: string): unknown {
|
|
42
|
+
const text = fs.readFileSync(file, "utf8");
|
|
43
|
+
try {
|
|
44
|
+
return parseJsonc(text);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new Error(`ttsc: failed to parse ${file}: ${describe(error)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Parse a JSONC string by blanking comments and trailing commas before handing
|
|
52
|
+
* off to `JSON.parse`. Callers hold a path, so they go through
|
|
53
|
+
* {@link readJsoncFile} and their failures are attributed.
|
|
54
|
+
*/
|
|
55
|
+
function parseJsonc(input: string): unknown {
|
|
56
|
+
return JSON.parse(stripTrailingCommas(stripComments(stripLeadingBom(input))));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Render a parse failure's message without the `Error:` noise around it. */
|
|
60
|
+
function describe(error: unknown): string {
|
|
61
|
+
return error instanceof Error ? error.message : String(error);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Replace a leading UTF-8 BOM with a space. JSON ignores leading whitespace, so
|
|
66
|
+
* blanking rather than removing keeps every later offset equal to the offset in
|
|
67
|
+
* the original file. A BOM anywhere else is left in place and still rejected,
|
|
68
|
+
* which is the behaviour issue #216 pinned.
|
|
69
|
+
*/
|
|
70
|
+
function stripLeadingBom(input: string): string {
|
|
71
|
+
return input.charCodeAt(0) === 0xfeff ? ` ${input.slice(1)}` : input;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Blank `//` line comments and `/* block comments *\/` in a JSONC string,
|
|
76
|
+
* writing a space for every removed character and preserving newlines.
|
|
77
|
+
* Correctly handles strings that contain comment-like character sequences by
|
|
78
|
+
* tracking string boundaries and escape characters.
|
|
79
|
+
*/
|
|
80
|
+
function stripComments(input: string): string {
|
|
81
|
+
let output = "";
|
|
82
|
+
let inBlockComment = false;
|
|
83
|
+
let inLineComment = false;
|
|
84
|
+
let inString = false;
|
|
85
|
+
let quote = "";
|
|
86
|
+
let escape = false;
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
89
|
+
const current = input[i]!;
|
|
90
|
+
const next = input[i + 1];
|
|
91
|
+
|
|
92
|
+
if (inBlockComment) {
|
|
93
|
+
if (current === "*" && next === "/") {
|
|
94
|
+
inBlockComment = false;
|
|
95
|
+
output += " ";
|
|
96
|
+
i += 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
output += blank(current);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (inLineComment) {
|
|
103
|
+
if (current === "\n") {
|
|
104
|
+
inLineComment = false;
|
|
105
|
+
output += current;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
output += blank(current);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (inString) {
|
|
112
|
+
output += current;
|
|
113
|
+
if (escape) {
|
|
114
|
+
escape = false;
|
|
115
|
+
} else if (current === "\\") {
|
|
116
|
+
escape = true;
|
|
117
|
+
} else if (current === quote) {
|
|
118
|
+
inString = false;
|
|
119
|
+
quote = "";
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (current === '"' || current === "'") {
|
|
125
|
+
inString = true;
|
|
126
|
+
quote = current;
|
|
127
|
+
output += current;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (current === "/" && next === "/") {
|
|
131
|
+
inLineComment = true;
|
|
132
|
+
output += " ";
|
|
133
|
+
i += 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (current === "/" && next === "*") {
|
|
137
|
+
inBlockComment = true;
|
|
138
|
+
output += " ";
|
|
139
|
+
i += 1;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
output += current;
|
|
143
|
+
}
|
|
144
|
+
return output;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Keep a newline as a newline; every other removed character becomes a space. */
|
|
148
|
+
function blank(current: string): string {
|
|
149
|
+
return current === "\n" ? "\n" : " ";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Blank trailing commas before `}` or `]` in a JSON string (after comments have
|
|
154
|
+
* already been blanked). Handles string boundaries and escape characters to
|
|
155
|
+
* avoid touching commas inside string values.
|
|
156
|
+
*/
|
|
157
|
+
function stripTrailingCommas(input: string): string {
|
|
158
|
+
let output = "";
|
|
159
|
+
let inString = false;
|
|
160
|
+
let quote = "";
|
|
161
|
+
let escape = false;
|
|
162
|
+
|
|
163
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
164
|
+
const current = input[i]!;
|
|
165
|
+
if (inString) {
|
|
166
|
+
output += current;
|
|
167
|
+
if (escape) {
|
|
168
|
+
escape = false;
|
|
169
|
+
} else if (current === "\\") {
|
|
170
|
+
escape = true;
|
|
171
|
+
} else if (current === quote) {
|
|
172
|
+
inString = false;
|
|
173
|
+
quote = "";
|
|
174
|
+
}
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (current === '"' || current === "'") {
|
|
179
|
+
inString = true;
|
|
180
|
+
quote = current;
|
|
181
|
+
output += current;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (current === ",") {
|
|
185
|
+
const next = nextNonWhitespace(input, i + 1);
|
|
186
|
+
if (next === "}" || next === "]") {
|
|
187
|
+
output += " ";
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
output += current;
|
|
192
|
+
}
|
|
193
|
+
return output;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Return the first non-whitespace character at or after position `from` in
|
|
198
|
+
* `input`, or `undefined` when only whitespace remains. Used by
|
|
199
|
+
* `stripTrailingCommas` to detect whether a comma is trailing.
|
|
200
|
+
*/
|
|
201
|
+
function nextNonWhitespace(input: string, from: number): string | undefined {
|
|
202
|
+
for (let i = from; i < input.length; i += 1) {
|
|
203
|
+
const current = input[i]!;
|
|
204
|
+
if (/\s/.test(current) === false) {
|
|
205
|
+
return current;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import type { ITtscProjectPluginConfig } from "../../../structures/ITtscProjectPluginConfig";
|
|
6
6
|
import type { ITtscParsedProjectConfig } from "../../../structures/internal/ITtscParsedProjectConfig";
|
|
7
7
|
import type { ITtscProjectLocatorOptions } from "../../../structures/internal/ITtscProjectLocatorOptions";
|
|
8
|
+
import { readJsonFile, readJsoncFile } from "./readConfigJson";
|
|
8
9
|
import { resolveProjectIdentity } from "./resolveProjectConfig";
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -18,6 +19,7 @@ const PATH_OPTIONS = new Set(["baseUrl", "declarationDir", "rootDir"]);
|
|
|
18
19
|
* projected into `ITtscParsedProjectConfig`.
|
|
19
20
|
*/
|
|
20
21
|
type ResolvedCompilerOptions = {
|
|
22
|
+
configPaths: string[];
|
|
21
23
|
options: Record<string, unknown>;
|
|
22
24
|
/** Directory of the tsconfig that last declared each option key. */
|
|
23
25
|
optionBaseDirs: Record<string, string>;
|
|
@@ -45,6 +47,7 @@ export function readProjectConfig(
|
|
|
45
47
|
const root = identity.physicalProjectRoot;
|
|
46
48
|
const compilerOptions = readResolvedCompilerOptions(tsconfig);
|
|
47
49
|
return {
|
|
50
|
+
configPaths: compilerOptions.configPaths,
|
|
48
51
|
compilerOptions: {
|
|
49
52
|
...compilerOptions.options,
|
|
50
53
|
outDir: compilerOptions.outDir,
|
|
@@ -101,7 +104,7 @@ function readResolvedCompilerOptions(
|
|
|
101
104
|
}
|
|
102
105
|
seen.add(canonical);
|
|
103
106
|
try {
|
|
104
|
-
const parsed =
|
|
107
|
+
const parsed = readJsoncFile(canonical) as {
|
|
105
108
|
extends?: unknown;
|
|
106
109
|
compilerOptions?: Record<string, unknown> & {
|
|
107
110
|
outDir?: unknown;
|
|
@@ -134,6 +137,7 @@ function readResolvedCompilerOptions(
|
|
|
134
137
|
? ownPlugins.filter(isProjectPluginConfig)
|
|
135
138
|
: base.plugins;
|
|
136
139
|
return {
|
|
140
|
+
configPaths: uniquePaths([...base.configPaths, canonical]),
|
|
137
141
|
optionBaseDirs,
|
|
138
142
|
options,
|
|
139
143
|
outDir:
|
|
@@ -174,6 +178,7 @@ function resolveBaseCompilerOptions(
|
|
|
174
178
|
}
|
|
175
179
|
if (!Array.isArray(extended)) {
|
|
176
180
|
return {
|
|
181
|
+
configPaths: [],
|
|
177
182
|
optionBaseDirs: {},
|
|
178
183
|
options: {},
|
|
179
184
|
pluginBaseDirs: [],
|
|
@@ -182,6 +187,7 @@ function resolveBaseCompilerOptions(
|
|
|
182
187
|
};
|
|
183
188
|
}
|
|
184
189
|
let merged: ResolvedCompilerOptions = {
|
|
190
|
+
configPaths: [],
|
|
185
191
|
optionBaseDirs: {},
|
|
186
192
|
options: {},
|
|
187
193
|
pluginBaseDirs: [],
|
|
@@ -197,6 +203,7 @@ function resolveBaseCompilerOptions(
|
|
|
197
203
|
seen,
|
|
198
204
|
);
|
|
199
205
|
merged = {
|
|
206
|
+
configPaths: uniquePaths([...merged.configPaths, ...current.configPaths]),
|
|
200
207
|
optionBaseDirs: {
|
|
201
208
|
...merged.optionBaseDirs,
|
|
202
209
|
...current.optionBaseDirs,
|
|
@@ -216,6 +223,10 @@ function resolveBaseCompilerOptions(
|
|
|
216
223
|
return merged;
|
|
217
224
|
}
|
|
218
225
|
|
|
226
|
+
function uniquePaths(paths: readonly string[]): string[] {
|
|
227
|
+
return [...new Set(paths)];
|
|
228
|
+
}
|
|
229
|
+
|
|
219
230
|
/**
|
|
220
231
|
* Resolve path-typed compiler options to absolute paths using the directory of
|
|
221
232
|
* the tsconfig that declared each option (`baseDirs`).
|
|
@@ -272,9 +283,10 @@ function resolveExtendsConfig(tsconfig: string, specifier: string): string {
|
|
|
272
283
|
/**
|
|
273
284
|
* When `specifier` names a bare package root, resolve the config file its
|
|
274
285
|
* `package.json#tsconfig` field selects (anchored at the package directory).
|
|
275
|
-
* Returns `undefined` when the specifier is a subpath, the manifest
|
|
276
|
-
*
|
|
277
|
-
*
|
|
286
|
+
* Returns `undefined` when the specifier is a subpath, the manifest cannot be
|
|
287
|
+
* resolved at all, or it declares no `tsconfig` field, so the caller falls back
|
|
288
|
+
* to Node entrypoint resolution. A manifest that resolves but does not parse
|
|
289
|
+
* throws, naming the file.
|
|
278
290
|
*/
|
|
279
291
|
function resolvePackageManifestTsconfig(
|
|
280
292
|
resolver: NodeRequire,
|
|
@@ -286,19 +298,38 @@ function resolvePackageManifestTsconfig(
|
|
|
286
298
|
let manifestPath: string;
|
|
287
299
|
try {
|
|
288
300
|
manifestPath = resolver.resolve(`${specifier}/package.json`);
|
|
289
|
-
} catch {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
301
|
+
} catch (error) {
|
|
302
|
+
// Node parses a package's manifest while resolving into it, so a malformed
|
|
303
|
+
// preset manifest fails here rather than at the read below. Swallowing it
|
|
304
|
+
// is what turned a broken manifest into the confusing downstream
|
|
305
|
+
// "Cannot find module 'example-preset.json'" from the `extends` fallback,
|
|
306
|
+
// which names a file that was never the problem. Node's own message names
|
|
307
|
+
// the real one, so report it in ttsc's voice instead of continuing.
|
|
308
|
+
if (
|
|
309
|
+
(error as NodeJS.ErrnoException | undefined)?.code ===
|
|
310
|
+
"ERR_INVALID_PACKAGE_CONFIG"
|
|
311
|
+
) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`ttsc: failed to parse the package manifest of ${JSON.stringify(specifier)}: ${
|
|
314
|
+
error instanceof Error ? error.message : String(error)
|
|
315
|
+
}`,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
300
318
|
return undefined;
|
|
301
319
|
}
|
|
320
|
+
// A manifest that exists but does not parse is a real configuration error,
|
|
321
|
+
// and it used to be swallowed here: the read returned `undefined`, the caller
|
|
322
|
+
// fell back to Node entrypoint resolution, and the user learned nothing about
|
|
323
|
+
// the file that actually broke. Report it instead.
|
|
324
|
+
//
|
|
325
|
+
// A manifest that parses to something other than an object carries no
|
|
326
|
+
// `tsconfig` field to read, so it falls back rather than throwing on a
|
|
327
|
+
// property access.
|
|
328
|
+
const manifest = readJsonFile(manifestPath);
|
|
329
|
+
const field =
|
|
330
|
+
typeof manifest === "object" && manifest !== null
|
|
331
|
+
? (manifest as { tsconfig?: unknown }).tsconfig
|
|
332
|
+
: undefined;
|
|
302
333
|
if (typeof field !== "string" || field.length === 0) {
|
|
303
334
|
return undefined;
|
|
304
335
|
}
|
|
@@ -318,23 +349,31 @@ function isBarePackageRoot(specifier: string): boolean {
|
|
|
318
349
|
}
|
|
319
350
|
|
|
320
351
|
/**
|
|
321
|
-
* Given an on-disk location, try the path as-is,
|
|
322
|
-
*
|
|
352
|
+
* Given an on-disk location, try the path as-is and, unless it already ends in
|
|
353
|
+
* `.json`, with that extension appended. Only regular files are valid `extends`
|
|
354
|
+
* targets; TypeScript neither expands a directory into `tsconfig.json` nor
|
|
355
|
+
* probes a double `.json` suffix.
|
|
323
356
|
*/
|
|
324
357
|
function resolveExistingExtendsPath(location: string): string {
|
|
325
|
-
const candidates =
|
|
326
|
-
location
|
|
327
|
-
`${location}.json
|
|
328
|
-
path.join(location, "tsconfig.json"),
|
|
329
|
-
]);
|
|
358
|
+
const candidates = location.endsWith(".json")
|
|
359
|
+
? [location]
|
|
360
|
+
: [location, `${location}.json`];
|
|
330
361
|
for (const candidate of candidates) {
|
|
331
|
-
if (
|
|
362
|
+
if (isFile(candidate)) {
|
|
332
363
|
return resolveRealPath(candidate);
|
|
333
364
|
}
|
|
334
365
|
}
|
|
335
366
|
throw new Error(`ttsc: extended tsconfig not found: ${location}`);
|
|
336
367
|
}
|
|
337
368
|
|
|
369
|
+
function isFile(location: string): boolean {
|
|
370
|
+
try {
|
|
371
|
+
return fs.statSync(location).isFile();
|
|
372
|
+
} catch {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
338
377
|
/**
|
|
339
378
|
* Return true when `specifier` is a relative path reference: `.`, `..`, or a
|
|
340
379
|
* string starting with `./`, `../`, `.\\`, or `..\\`.
|
|
@@ -349,138 +388,3 @@ function isRelativeSpecifier(specifier: string): boolean {
|
|
|
349
388
|
specifier.startsWith("..\\")
|
|
350
389
|
);
|
|
351
390
|
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Parse a JSONC (JSON with Comments) string by stripping comments and trailing
|
|
355
|
-
* commas before handing off to `JSON.parse`.
|
|
356
|
-
*/
|
|
357
|
-
function parseJsonc(input: string): unknown {
|
|
358
|
-
return JSON.parse(stripTrailingCommas(stripComments(stripLeadingBom(input))));
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function stripLeadingBom(input: string): string {
|
|
362
|
-
return input.charCodeAt(0) === 0xfeff ? input.slice(1) : input;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Remove `//` line comments and `/* block comments *\/` from a JSONC string.
|
|
367
|
-
* Correctly handles strings that contain comment-like character sequences by
|
|
368
|
-
* tracking string boundaries and escape characters.
|
|
369
|
-
*/
|
|
370
|
-
function stripComments(input: string): string {
|
|
371
|
-
let output = "";
|
|
372
|
-
let inBlockComment = false;
|
|
373
|
-
let inLineComment = false;
|
|
374
|
-
let inString = false;
|
|
375
|
-
let quote = "";
|
|
376
|
-
let escape = false;
|
|
377
|
-
|
|
378
|
-
for (let i = 0; i < input.length; i += 1) {
|
|
379
|
-
const current = input[i]!;
|
|
380
|
-
const next = input[i + 1];
|
|
381
|
-
|
|
382
|
-
if (inBlockComment) {
|
|
383
|
-
if (current === "*" && next === "/") {
|
|
384
|
-
inBlockComment = false;
|
|
385
|
-
i += 1;
|
|
386
|
-
}
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
if (inLineComment) {
|
|
390
|
-
if (current === "\n") {
|
|
391
|
-
inLineComment = false;
|
|
392
|
-
output += current;
|
|
393
|
-
}
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
if (inString) {
|
|
397
|
-
output += current;
|
|
398
|
-
if (escape) {
|
|
399
|
-
escape = false;
|
|
400
|
-
} else if (current === "\\") {
|
|
401
|
-
escape = true;
|
|
402
|
-
} else if (current === quote) {
|
|
403
|
-
inString = false;
|
|
404
|
-
quote = "";
|
|
405
|
-
}
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (current === '"' || current === "'") {
|
|
410
|
-
inString = true;
|
|
411
|
-
quote = current;
|
|
412
|
-
output += current;
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
if (current === "/" && next === "/") {
|
|
416
|
-
inLineComment = true;
|
|
417
|
-
i += 1;
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
if (current === "/" && next === "*") {
|
|
421
|
-
inBlockComment = true;
|
|
422
|
-
i += 1;
|
|
423
|
-
continue;
|
|
424
|
-
}
|
|
425
|
-
output += current;
|
|
426
|
-
}
|
|
427
|
-
return output;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Remove trailing commas before `}` or `]` from a JSON string (after comments
|
|
432
|
-
* have already been stripped). Handles string boundaries and escape characters
|
|
433
|
-
* to avoid removing commas inside string values.
|
|
434
|
-
*/
|
|
435
|
-
function stripTrailingCommas(input: string): string {
|
|
436
|
-
let output = "";
|
|
437
|
-
let inString = false;
|
|
438
|
-
let quote = "";
|
|
439
|
-
let escape = false;
|
|
440
|
-
|
|
441
|
-
for (let i = 0; i < input.length; i += 1) {
|
|
442
|
-
const current = input[i]!;
|
|
443
|
-
if (inString) {
|
|
444
|
-
output += current;
|
|
445
|
-
if (escape) {
|
|
446
|
-
escape = false;
|
|
447
|
-
} else if (current === "\\") {
|
|
448
|
-
escape = true;
|
|
449
|
-
} else if (current === quote) {
|
|
450
|
-
inString = false;
|
|
451
|
-
quote = "";
|
|
452
|
-
}
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (current === '"' || current === "'") {
|
|
457
|
-
inString = true;
|
|
458
|
-
quote = current;
|
|
459
|
-
output += current;
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
if (current === ",") {
|
|
463
|
-
const next = nextNonWhitespace(input, i + 1);
|
|
464
|
-
if (next === "}" || next === "]") {
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
output += current;
|
|
469
|
-
}
|
|
470
|
-
return output;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Return the first non-whitespace character at or after position `from` in
|
|
475
|
-
* `input`, or `undefined` when only whitespace remains. Used by
|
|
476
|
-
* `stripTrailingCommas` to detect whether a comma is trailing.
|
|
477
|
-
*/
|
|
478
|
-
function nextNonWhitespace(input: string, from: number): string | undefined {
|
|
479
|
-
for (let i = from; i < input.length; i += 1) {
|
|
480
|
-
const current = input[i]!;
|
|
481
|
-
if (/\s/.test(current) === false) {
|
|
482
|
-
return current;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
return undefined;
|
|
486
|
-
}
|