ttsc 0.12.3 → 0.13.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 (151) hide show
  1. package/cmd/platform/main.go +9 -0
  2. package/cmd/ttsc/api_compile.go +122 -101
  3. package/cmd/ttsc/api_compile_branches_test.go +55 -57
  4. package/cmd/ttsc/api_transform.go +67 -49
  5. package/cmd/ttsc/api_transform_branches_test.go +30 -32
  6. package/cmd/ttsc/build.go +141 -108
  7. package/cmd/ttsc/build_branches_test.go +95 -97
  8. package/cmd/ttsc/filter.go +70 -0
  9. package/cmd/ttsc/filter_strips_unknown_forwarded_flag_test.go +61 -0
  10. package/cmd/ttsc/flags_gen.go +30 -0
  11. package/cmd/ttsc/helpers_test.go +39 -28
  12. package/cmd/ttsc/main.go +111 -104
  13. package/cmd/ttsc/main_alias_branches_test.go +21 -23
  14. package/cmd/ttscserver/main.go +96 -90
  15. package/cmd/ttscserver/run_lsp_prefers_tsgo_flag_test.go +22 -22
  16. package/cmd/ttscserver/run_lsp_reports_server_error_test.go +22 -22
  17. package/cmd/ttscserver/run_lsp_uses_tsgo_environment_test.go +23 -23
  18. package/cmd/utility-host/main.go +30 -23
  19. package/driver/lsp.go +53 -7
  20. package/driver/plugins.go +88 -72
  21. package/driver/program.go +464 -340
  22. package/driver/rewrite.go +477 -380
  23. package/internal/lspserver/lsp_envelope.go +64 -64
  24. package/internal/lspserver/lsp_frame.go +60 -60
  25. package/internal/lspserver/lsp_plugin.go +57 -57
  26. package/internal/lspserver/lsp_proxy.go +265 -251
  27. package/internal/lspserver/lsp_server.go +155 -144
  28. package/lib/TtscCompiler.d.ts +7 -0
  29. package/lib/TtscCompiler.js +7 -0
  30. package/lib/TtscCompiler.js.map +1 -1
  31. package/lib/compiler/internal/buildNativeCompiler.d.ts +11 -1
  32. package/lib/compiler/internal/buildNativeCompiler.js +23 -3
  33. package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
  34. package/lib/compiler/internal/compileProjectInMemory.d.ts +9 -0
  35. package/lib/compiler/internal/compileProjectInMemory.js +36 -21
  36. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  37. package/lib/compiler/internal/paths.d.ts +27 -0
  38. package/lib/compiler/internal/paths.js +70 -0
  39. package/lib/compiler/internal/paths.js.map +1 -0
  40. package/lib/compiler/internal/project/readProjectConfig.d.ts +9 -1
  41. package/lib/compiler/internal/project/readProjectConfig.js +80 -1
  42. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  43. package/lib/compiler/internal/project/resolveProjectConfig.d.ts +13 -1
  44. package/lib/compiler/internal/project/resolveProjectConfig.js +42 -1
  45. package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
  46. package/lib/compiler/internal/resolveBinary.d.ts +9 -1
  47. package/lib/compiler/internal/resolveBinary.js +17 -1
  48. package/lib/compiler/internal/resolveBinary.js.map +1 -1
  49. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +16 -1
  50. package/lib/compiler/internal/resolveEmittedJavaScript.js +42 -10
  51. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  52. package/lib/compiler/internal/resolveTsgo.d.ts +21 -1
  53. package/lib/compiler/internal/resolveTsgo.js +23 -1
  54. package/lib/compiler/internal/resolveTsgo.js.map +1 -1
  55. package/lib/compiler/internal/runBuild.d.ts +24 -4
  56. package/lib/compiler/internal/runBuild.js +355 -93
  57. package/lib/compiler/internal/runBuild.js.map +1 -1
  58. package/lib/compiler/internal/runSingleFileEmit.d.ts +10 -1
  59. package/lib/compiler/internal/runSingleFileEmit.js +14 -1
  60. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  61. package/lib/compiler/internal/sharedHostHelpers.d.ts +5 -0
  62. package/lib/compiler/internal/sharedHostHelpers.js +6 -2
  63. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  64. package/lib/compiler/internal/spawnNative.d.ts +21 -0
  65. package/lib/compiler/internal/spawnNative.js +59 -0
  66. package/lib/compiler/internal/spawnNative.js.map +1 -0
  67. package/lib/compiler/internal/transformProjectInMemory.d.ts +15 -1
  68. package/lib/compiler/internal/transformProjectInMemory.js +57 -61
  69. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  70. package/lib/flags/parser.d.ts +63 -0
  71. package/lib/flags/parser.js +254 -0
  72. package/lib/flags/parser.js.map +1 -0
  73. package/lib/flags/schema.d.ts +147 -0
  74. package/lib/flags/schema.js +405 -0
  75. package/lib/flags/schema.js.map +1 -0
  76. package/lib/launcher/internal/getCompilerVersionText.js +7 -0
  77. package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
  78. package/lib/launcher/internal/prepareExecution.js +35 -0
  79. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  80. package/lib/launcher/internal/resolveCacheDir.d.ts +9 -0
  81. package/lib/launcher/internal/resolveCacheDir.js +22 -0
  82. package/lib/launcher/internal/resolveCacheDir.js.map +1 -0
  83. package/lib/launcher/internal/resolveTtscserverBinary.js +2 -0
  84. package/lib/launcher/internal/resolveTtscserverBinary.js.map +1 -1
  85. package/lib/launcher/internal/runTtsc.d.ts +10 -0
  86. package/lib/launcher/internal/runTtsc.js +90 -153
  87. package/lib/launcher/internal/runTtsc.js.map +1 -1
  88. package/lib/launcher/internal/runTtscserver.js +10 -35
  89. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  90. package/lib/launcher/internal/runTtsx.d.ts +8 -0
  91. package/lib/launcher/internal/runTtsx.js +137 -79
  92. package/lib/launcher/internal/runTtsx.js.map +1 -1
  93. package/lib/plugin/internal/buildSourcePlugin.d.ts +38 -0
  94. package/lib/plugin/internal/buildSourcePlugin.js +40 -17
  95. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  96. package/lib/plugin/internal/loadProjectPlugins.d.ts +26 -0
  97. package/lib/plugin/internal/loadProjectPlugins.js +90 -41
  98. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  99. package/lib/structures/ITtscCompilerTransformation.d.ts +15 -2
  100. package/lib/structures/ITtscPlugin.d.ts +24 -0
  101. package/lib/structures/ITtscPluginCapabilities.d.ts +29 -0
  102. package/lib/structures/ITtscPluginCapabilities.js +3 -0
  103. package/lib/structures/ITtscPluginCapabilities.js.map +1 -0
  104. package/lib/structures/index.d.ts +1 -0
  105. package/lib/structures/index.js +1 -0
  106. package/lib/structures/index.js.map +1 -1
  107. package/lib/structures/internal/ITtscLoadedNativePlugin.d.ts +5 -0
  108. package/lib/structures/internal/TtscCommonOptions.d.ts +19 -0
  109. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +0 -2
  110. package/package.json +9 -9
  111. package/shim/ast/shim.go +24 -0
  112. package/shim/checker/shim.go +35 -0
  113. package/shim/core/shim.go +17 -0
  114. package/shim/diagnosticwriter/shim.go +4 -0
  115. package/shim/lsp/shim.go +3 -3
  116. package/shim/printer/shim.go +20 -0
  117. package/src/TtscCompiler.ts +7 -0
  118. package/src/compiler/internal/buildNativeCompiler.ts +23 -3
  119. package/src/compiler/internal/compileProjectInMemory.ts +34 -25
  120. package/src/compiler/internal/paths.ts +68 -0
  121. package/src/compiler/internal/project/readProjectConfig.ts +86 -1
  122. package/src/compiler/internal/project/resolveProjectConfig.ts +44 -1
  123. package/src/compiler/internal/resolveBinary.ts +17 -1
  124. package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -12
  125. package/src/compiler/internal/resolveTsgo.ts +29 -1
  126. package/src/compiler/internal/runBuild.ts +398 -112
  127. package/src/compiler/internal/runSingleFileEmit.ts +14 -1
  128. package/src/compiler/internal/sharedHostHelpers.ts +7 -7
  129. package/src/compiler/internal/spawnNative.ts +64 -0
  130. package/src/compiler/internal/transformProjectInMemory.ts +47 -67
  131. package/src/flags/parser.ts +367 -0
  132. package/src/flags/schema.ts +570 -0
  133. package/src/launcher/internal/getCompilerVersionText.ts +7 -0
  134. package/src/launcher/internal/prepareExecution.ts +35 -0
  135. package/src/launcher/internal/resolveCacheDir.ts +19 -0
  136. package/src/launcher/internal/resolveTtscserverBinary.ts +2 -0
  137. package/src/launcher/internal/runTtsc.ts +97 -143
  138. package/src/launcher/internal/runTtscserver.ts +9 -1
  139. package/src/launcher/internal/runTtsx.ts +149 -82
  140. package/src/plugin/internal/buildSourcePlugin.ts +40 -14
  141. package/src/plugin/internal/loadProjectPlugins.ts +116 -45
  142. package/src/structures/ITtscCompilerTransformation.ts +15 -2
  143. package/src/structures/ITtscPlugin.ts +26 -1
  144. package/src/structures/ITtscPluginCapabilities.ts +29 -0
  145. package/src/structures/index.ts +1 -0
  146. package/src/structures/internal/ITtscLoadedNativePlugin.ts +5 -0
  147. package/src/structures/internal/TtscCommonOptions.ts +19 -0
  148. package/src/structures/internal/TtscSingleFileEmitOptions.ts +0 -2
  149. package/tools/gen_shims/main.go +43 -8
  150. package/utility/flags_gen.go +30 -0
  151. package/utility/host.go +304 -245
@@ -7,18 +7,36 @@ import type { ITtscParsedProjectConfig } from "../../../structures/internal/ITts
7
7
  import type { ITtscProjectLocatorOptions } from "../../../structures/internal/ITtscProjectLocatorOptions";
8
8
  import { resolveProjectConfig } from "./resolveProjectConfig";
9
9
 
10
+ /**
11
+ * Compiler option keys whose values are file-system paths that must be resolved
12
+ * relative to the tsconfig that declares them, not the project root.
13
+ */
10
14
  const PATH_OPTIONS = new Set(["baseUrl", "declarationDir", "rootDir"]);
11
15
 
16
+ /**
17
+ * Intermediate type used during `extends`-chain resolution before the result is
18
+ * projected into `ITtscParsedProjectConfig`.
19
+ */
12
20
  type ResolvedCompilerOptions = {
13
21
  options: Record<string, unknown>;
22
+ /** Directory of the tsconfig that last declared each option key. */
14
23
  optionBaseDirs: Record<string, string>;
15
24
  outDir?: string;
16
25
  pluginBaseDirs: string[];
26
+ /** True when any tsconfig in the chain explicitly declared `plugins`. */
17
27
  pluginsDeclared: boolean;
18
28
  plugins: ITtscProjectPluginConfig[];
19
29
  };
20
30
 
21
- /** Read the resolved project config subset used by ttsc. */
31
+ /**
32
+ * Read and resolve the project config subset used by ttsc.
33
+ *
34
+ * Follows `extends` chains (including arrays) and merges compiler options with
35
+ * later configs taking precedence. Path-typed options (`baseUrl`,
36
+ * `declarationDir`, `rootDir`) are resolved relative to the config that
37
+ * declares them. The `outDir` is resolved to an absolute path. Plugins are
38
+ * inherited from the nearest ancestor that declares them.
39
+ */
22
40
  export function readProjectConfig(
23
41
  opts: ITtscProjectLocatorOptions = {},
24
42
  ): ITtscParsedProjectConfig {
@@ -39,12 +57,20 @@ export function readProjectConfig(
39
57
  };
40
58
  }
41
59
 
60
+ /**
61
+ * Type guard: accept any non-null object as a plugin config entry. This is
62
+ * intentionally loose because the shape is validated later by plugin loaders.
63
+ */
42
64
  function isProjectPluginConfig(
43
65
  value: unknown,
44
66
  ): value is ITtscProjectPluginConfig {
45
67
  return typeof value === "object" && value !== null;
46
68
  }
47
69
 
70
+ /**
71
+ * Resolve symlinks on `location`, returning the original path when
72
+ * `realpathSync` fails (e.g. when the file does not yet exist).
73
+ */
48
74
  function resolveRealPath(location: string): string {
49
75
  try {
50
76
  return fs.realpathSync(location);
@@ -53,10 +79,18 @@ function resolveRealPath(location: string): string {
53
79
  }
54
80
  }
55
81
 
82
+ /** Resolve `target` against `cwd` when it is not already absolute. */
56
83
  function resolveAbsolutePath(cwd: string, target: string): string {
57
84
  return path.isAbsolute(target) ? target : path.resolve(cwd, target);
58
85
  }
59
86
 
87
+ /**
88
+ * Recursively read and merge `compilerOptions` from `tsconfig` and all configs
89
+ * in its `extends` chain. `seen` tracks canonical paths to detect circular
90
+ * `extends` references; the current config is removed from `seen` in the
91
+ * `finally` block so sibling-referenced configs can be visited again via a
92
+ * different parent.
93
+ */
60
94
  function readResolvedCompilerOptions(
61
95
  tsconfig: string,
62
96
  seen: Set<string> = new Set(),
@@ -117,6 +151,16 @@ function readResolvedCompilerOptions(
117
151
  }
118
152
  }
119
153
 
154
+ /**
155
+ * Resolve the base compiler options from the `extends` field of a tsconfig.
156
+ *
157
+ * - String: a single base config; options are returned directly.
158
+ * - Array: multiple base configs merged left-to-right (later entries win).
159
+ * - Absent / non-string non-array: returns empty options.
160
+ *
161
+ * Plugin inheritance: when the current config in an array chain explicitly
162
+ * declares `plugins`, subsequent configs in the array do not override them.
163
+ */
120
164
  function resolveBaseCompilerOptions(
121
165
  tsconfig: string,
122
166
  extended: unknown,
@@ -172,6 +216,10 @@ function resolveBaseCompilerOptions(
172
216
  return merged;
173
217
  }
174
218
 
219
+ /**
220
+ * Resolve path-typed compiler options to absolute paths using the directory of
221
+ * the tsconfig that declared each option (`baseDirs`).
222
+ */
175
223
  function resolvePathOptions(
176
224
  options: Record<string, unknown>,
177
225
  baseDirs: Record<string, string>,
@@ -187,6 +235,16 @@ function resolvePathOptions(
187
235
  return resolved;
188
236
  }
189
237
 
238
+ /**
239
+ * Resolve an `extends` specifier to an absolute path using the same rules as
240
+ * TypeScript's config resolution:
241
+ *
242
+ * - Absolute path: used directly.
243
+ * - Relative specifier (`./`, `../`, `.`, `..`): resolved against the declaring
244
+ * tsconfig's directory with fallback extension candidates.
245
+ * - Bare package specifier: resolved through Node's module resolver (the resolver
246
+ * also tries appending `.json` when the first attempt fails).
247
+ */
190
248
  function resolveExtendsConfig(tsconfig: string, specifier: string): string {
191
249
  const baseDir = path.dirname(tsconfig);
192
250
  if (path.isAbsolute(specifier)) {
@@ -203,6 +261,10 @@ function resolveExtendsConfig(tsconfig: string, specifier: string): string {
203
261
  }
204
262
  }
205
263
 
264
+ /**
265
+ * Given an on-disk location, try the path as-is, with `.json` appended, and as
266
+ * a directory containing `tsconfig.json`. Returns the first match or throws.
267
+ */
206
268
  function resolveExistingExtendsPath(location: string): string {
207
269
  const candidates = new Set<string>([
208
270
  location,
@@ -217,6 +279,10 @@ function resolveExistingExtendsPath(location: string): string {
217
279
  throw new Error(`ttsc: extended tsconfig not found: ${location}`);
218
280
  }
219
281
 
282
+ /**
283
+ * Return true when `specifier` is a relative path reference: `.`, `..`, or a
284
+ * string starting with `./`, `../`, `.\\`, or `..\\`.
285
+ */
220
286
  function isRelativeSpecifier(specifier: string): boolean {
221
287
  return (
222
288
  specifier === "." ||
@@ -228,10 +294,19 @@ function isRelativeSpecifier(specifier: string): boolean {
228
294
  );
229
295
  }
230
296
 
297
+ /**
298
+ * Parse a JSONC (JSON with Comments) string by stripping comments and trailing
299
+ * commas before handing off to `JSON.parse`.
300
+ */
231
301
  function parseJsonc(input: string): unknown {
232
302
  return JSON.parse(stripTrailingCommas(stripComments(input)));
233
303
  }
234
304
 
305
+ /**
306
+ * Remove `//` line comments and `/* block comments *\/` from a JSONC string.
307
+ * Correctly handles strings that contain comment-like character sequences by
308
+ * tracking string boundaries and escape characters.
309
+ */
235
310
  function stripComments(input: string): string {
236
311
  let output = "";
237
312
  let inBlockComment = false;
@@ -292,6 +367,11 @@ function stripComments(input: string): string {
292
367
  return output;
293
368
  }
294
369
 
370
+ /**
371
+ * Remove trailing commas before `}` or `]` from a JSON string (after comments
372
+ * have already been stripped). Handles string boundaries and escape characters
373
+ * to avoid removing commas inside string values.
374
+ */
295
375
  function stripTrailingCommas(input: string): string {
296
376
  let output = "";
297
377
  let inString = false;
@@ -330,6 +410,11 @@ function stripTrailingCommas(input: string): string {
330
410
  return output;
331
411
  }
332
412
 
413
+ /**
414
+ * Return the first non-whitespace character at or after position `from` in
415
+ * `input`, or `undefined` when only whitespace remains. Used by
416
+ * `stripTrailingCommas` to detect whether a comma is trailing.
417
+ */
333
418
  function nextNonWhitespace(input: string, from: number): string | undefined {
334
419
  for (let i = from; i < input.length; i += 1) {
335
420
  const current = input[i]!;
@@ -3,7 +3,19 @@ import path from "node:path";
3
3
 
4
4
  import type { ITtscProjectLocatorOptions } from "../../../structures/internal/ITtscProjectLocatorOptions";
5
5
 
6
- /** Resolve the tsconfig/jsconfig that owns a ttsc invocation. */
6
+ /**
7
+ * Resolve the tsconfig/jsconfig that owns a ttsc invocation.
8
+ *
9
+ * Resolution order:
10
+ *
11
+ * 1. `opts.tsconfig` — resolved absolute and checked for existence.
12
+ * 2. `opts.file` — the nearest ancestor config that contains the file is found by
13
+ * walking up from the file's directory.
14
+ * 3. `opts.cwd` — the nearest ancestor config walking up from cwd.
15
+ *
16
+ * Always returns a real (symlink-resolved) absolute path. Throws when no config
17
+ * is found or the explicitly supplied path does not exist.
18
+ */
7
19
  export function resolveProjectConfig(
8
20
  opts: ITtscProjectLocatorOptions = {},
9
21
  ): string {
@@ -13,6 +25,26 @@ export function resolveProjectConfig(
13
25
  if (!fs.existsSync(resolved)) {
14
26
  throw new Error(`ttsc: tsconfig not found: ${resolved}`);
15
27
  }
28
+ // `-p <directory>` is the documented tsgo shorthand for the
29
+ // directory that contains a `tsconfig.json`. Mirror that — without
30
+ // this branch a forwarded `--tsconfig=sub` would feed the directory
31
+ // path into `readResolvedCompilerOptions`, which calls
32
+ // `fs.readFileSync` and throws `EISDIR` (the RCA's predicted
33
+ // RC-3 §5 #2 bug, pinned by
34
+ // `test_ttsc_dash_p_directory_path_is_accepted`).
35
+ if (isDirectory(resolved)) {
36
+ const tsconfigInDir = path.join(resolved, "tsconfig.json");
37
+ if (fs.existsSync(tsconfigInDir)) {
38
+ return resolveRealPath(tsconfigInDir);
39
+ }
40
+ const jsconfigInDir = path.join(resolved, "jsconfig.json");
41
+ if (fs.existsSync(jsconfigInDir)) {
42
+ return resolveRealPath(jsconfigInDir);
43
+ }
44
+ throw new Error(
45
+ `ttsc: directory has no tsconfig.json / jsconfig.json: ${resolved}`,
46
+ );
47
+ }
16
48
  return resolveRealPath(resolved);
17
49
  }
18
50
 
@@ -29,10 +61,15 @@ export function resolveProjectConfig(
29
61
  return resolveRealPath(found);
30
62
  }
31
63
 
64
+ /** Resolve `target` against `cwd` when it is not already absolute. */
32
65
  function resolveAbsolutePath(cwd: string, target: string): string {
33
66
  return path.isAbsolute(target) ? target : path.resolve(cwd, target);
34
67
  }
35
68
 
69
+ /**
70
+ * Resolve symlinks on `location`, returning the original path when
71
+ * `realpathSync` fails (e.g. when the file does not yet exist).
72
+ */
36
73
  function resolveRealPath(location: string): string {
37
74
  try {
38
75
  return fs.realpathSync(location);
@@ -41,6 +78,11 @@ function resolveRealPath(location: string): string {
41
78
  }
42
79
  }
43
80
 
81
+ /**
82
+ * Walk up the directory tree from `from`, returning the first directory that
83
+ * contains a file whose name is in `names`. Returns `null` when the filesystem
84
+ * root is reached without finding a match.
85
+ */
44
86
  function findUp(from: string, names: readonly string[]): string | null {
45
87
  let current = path.resolve(from);
46
88
  while (true) {
@@ -58,6 +100,7 @@ function findUp(from: string, names: readonly string[]): string | null {
58
100
  }
59
101
  }
60
102
 
103
+ /** Return true when `location` exists and is a directory. */
61
104
  function isDirectory(location: string): boolean {
62
105
  try {
63
106
  return fs.statSync(location).isDirectory();
@@ -1,7 +1,15 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- /** Resolve the ttsc helper binary path, or null when unavailable. */
4
+ /**
5
+ * Resolve the ttsc helper binary path, or null when unavailable.
6
+ *
7
+ * Resolution order:
8
+ *
9
+ * 1. `TTSC_BINARY` env var — must be an absolute path when set.
10
+ * 2. The per-platform npm package `@ttsc/<platform>-<arch>/bin/ttsc[.exe]`.
11
+ * 3. A `ttsc-native[.exe]` sibling of this package's root (dev/CI layout).
12
+ */
5
13
  export function resolveBinary(
6
14
  opts: { env?: NodeJS.ProcessEnv } = {},
7
15
  ): string | null {
@@ -24,6 +32,10 @@ export function resolveBinary(
24
32
  return null;
25
33
  }
26
34
 
35
+ /**
36
+ * Return the path to the dev-layout `ttsc-native[.exe]` binary adjacent to the
37
+ * package root, or null when the file does not exist.
38
+ */
27
39
  function defaultLocalBinaryPath(): string | null {
28
40
  const root = packageRootDir();
29
41
  const candidate = path.resolve(
@@ -35,6 +47,10 @@ function defaultLocalBinaryPath(): string | null {
35
47
  return fs.existsSync(candidate) ? candidate : null;
36
48
  }
37
49
 
50
+ /**
51
+ * Return the real, absolute path of the `packages/ttsc` package root by walking
52
+ * two levels up from `__dirname` (which lives in `src/compiler/internal`).
53
+ */
38
54
  function packageRootDir(): string {
39
55
  const moduleDir = path.resolve(__dirname, "..", "..");
40
56
  return fs.realpathSync.native?.(moduleDir) ?? fs.realpathSync(moduleDir);
@@ -1,8 +1,25 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- /** Locate the JavaScript file emitted for a TypeScript source file. */
4
+ import { isOutsideRelativePath } from "./paths";
5
+
6
+ /**
7
+ * Locate the JavaScript file emitted for a TypeScript source file.
8
+ *
9
+ * Resolution strategy:
10
+ *
11
+ * 1. Try to derive the exact output path by mirroring the source's relative
12
+ * position inside `projectRoot` into `outDir`, applying the correct JS
13
+ * extension (`.js` / `.mjs` / `.cjs`). Use this path if it exists on disk.
14
+ * 2. Fall back to scoring each candidate in `emittedFiles` (or a recursive
15
+ * directory scan of `outDir`) by the number of trailing path-stem segments
16
+ * shared with the source file name, and pick the highest-scoring existing
17
+ * file.
18
+ *
19
+ * Returns `null` when no matching output file is found on disk.
20
+ */
5
21
  export function resolveEmittedJavaScript(options: {
22
+ /** Pre-computed list of emitted paths; when absent `outDir` is scanned. */
6
23
  emittedFiles?: readonly string[];
7
24
  outDir: string;
8
25
  projectRoot: string;
@@ -31,6 +48,11 @@ export function resolveEmittedJavaScript(options: {
31
48
  return best && fs.existsSync(best) ? best : null;
32
49
  }
33
50
 
51
+ /**
52
+ * Derive the exact output path for `sourceFile` by mirroring its position
53
+ * relative to `projectRoot` into `outDir`. Returns `null` when the source is
54
+ * not inside the project root or when the path cannot be determined.
55
+ */
34
56
  function resolveExactEmittedFile(
35
57
  outDir: string,
36
58
  projectRoot: string,
@@ -47,6 +69,11 @@ function resolveExactEmittedFile(
47
69
  );
48
70
  }
49
71
 
72
+ /**
73
+ * Recursively enumerate every JavaScript output file under `root`. Uses an
74
+ * explicit stack instead of recursion to avoid call-stack overflow on deep
75
+ * directory trees. Non-existent roots are silently skipped.
76
+ */
50
77
  function listJavaScriptFiles(root: string): string[] {
51
78
  const out: string[] = [];
52
79
  const stack = [root];
@@ -65,15 +92,22 @@ function listJavaScriptFiles(root: string): string[] {
65
92
  return out;
66
93
  }
67
94
 
95
+ /**
96
+ * Count the number of consecutive trailing path-stem segments that `outPath`
97
+ * and `srcPath` share when both are stripped of their extensions and normalised
98
+ * to forward slashes.
99
+ *
100
+ * Example: `dist/lib/foo.js` vs `src/lib/foo.ts` → 2 (`lib`, `foo`).
101
+ */
68
102
  function sharedSourceStemSegments(outPath: string, srcPath: string): number {
69
- const trim = (location: string): string[] => {
103
+ const stripExtAndSplit = (location: string): string[] => {
70
104
  const normalized = location.replace(/\\/g, "/");
71
105
  return normalized
72
106
  .slice(0, normalized.length - path.extname(normalized).length)
73
107
  .split("/");
74
108
  };
75
- const a = trim(outPath);
76
- const b = trim(srcPath);
109
+ const a = stripExtAndSplit(outPath);
110
+ const b = stripExtAndSplit(srcPath);
77
111
  const count = Math.min(a.length, b.length);
78
112
  let shared = 0;
79
113
  for (let i = 1; i <= count; i += 1) {
@@ -83,14 +117,10 @@ function sharedSourceStemSegments(outPath: string, srcPath: string): number {
83
117
  return shared;
84
118
  }
85
119
 
86
- function isOutsideRelativePath(relative: string): boolean {
87
- return (
88
- relative === ".." ||
89
- relative.startsWith(`..${path.sep}`) ||
90
- path.isAbsolute(relative)
91
- );
92
- }
93
-
120
+ /**
121
+ * Map a TypeScript source extension to its JavaScript output counterpart.
122
+ * `.mts` → `.mjs`, `.cts` → `.cjs`, everything else → `.js`.
123
+ */
94
124
  function emittedJavaScriptExtension(filename: string): string {
95
125
  switch (path.extname(filename).toLowerCase()) {
96
126
  case ".mts":
@@ -102,6 +132,7 @@ function emittedJavaScriptExtension(filename: string): string {
102
132
  }
103
133
  }
104
134
 
135
+ /** Return true when `filename` has a `.js`, `.mjs`, or `.cjs` extension. */
105
136
  function isJavaScriptOutput(filename: string): boolean {
106
137
  return /\.(?:[cm]?js)$/i.test(filename);
107
138
  }
@@ -2,12 +2,32 @@ import fs from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import path from "node:path";
4
4
 
5
- /** Resolve the consumer project's TypeScript-Go preview binary. */
5
+ /**
6
+ * Resolve the consumer project's TypeScript-Go preview binary and metadata.
7
+ *
8
+ * Resolution order:
9
+ *
10
+ * 1. `opts.binary` or `TTSC_TSGO_BINARY` env var — must be an existing absolute
11
+ * path; returns `{ binary, packageJson: "", packageRoot, version: "custom"
12
+ * }`.
13
+ * 2. `@typescript/native-preview` resolved from the project `cwd`.
14
+ * 3. `@typescript/native-preview` resolved from `opts.resolveFrom` (for test
15
+ * harnesses and embedders that anchor to a different directory).
16
+ *
17
+ * Throws a descriptive error when the package or platform binary is missing so
18
+ * callers never have to reason about undefined binary paths.
19
+ */
6
20
  export function resolveTsgo(
7
21
  opts: {
22
+ /** Explicit path to a tsgo binary; bypasses package resolution. */
8
23
  binary?: string;
24
+ /** Directory from which to discover `@typescript/native-preview`. */
9
25
  cwd?: string;
10
26
  env?: NodeJS.ProcessEnv;
27
+ /**
28
+ * Fallback resolution anchor used when the package is not found at `cwd`.
29
+ * Useful in test harnesses that install the package into a different tree.
30
+ */
11
31
  resolveFrom?: string;
12
32
  } = {},
13
33
  ) {
@@ -28,6 +48,8 @@ export function resolveTsgo(
28
48
  }
29
49
 
30
50
  const cwd = path.resolve(opts.cwd ?? process.cwd());
51
+ // Resolve the package.json of @typescript/native-preview.
52
+ // Try cwd first, then the optional resolveFrom anchor.
31
53
  let packageJson: string;
32
54
  packageJson =
33
55
  resolveNativePreviewPackageJson(path.join(cwd, "package.json")) ??
@@ -84,6 +106,11 @@ export function resolveTsgo(
84
106
  };
85
107
  }
86
108
 
109
+ /**
110
+ * Attempt to resolve the `package.json` of `@typescript/native-preview`
111
+ * starting from `from` (a package.json path or directory). Returns `undefined`
112
+ * when the package is not resolvable from that location.
113
+ */
87
114
  function resolveNativePreviewPackageJson(from: string): string | undefined {
88
115
  try {
89
116
  return createRequire(from).resolve(
@@ -94,6 +121,7 @@ function resolveNativePreviewPackageJson(from: string): string | undefined {
94
121
  }
95
122
  }
96
123
 
124
+ /** Parse a JSON file and return the result as a plain object. */
97
125
  function readPackageJson(file: string): Record<string, unknown> {
98
126
  return JSON.parse(fs.readFileSync(file, "utf8")) as Record<string, unknown>;
99
127
  }