ttsc 0.12.2 → 0.12.4

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 (134) hide show
  1. package/cmd/platform/main.go +9 -0
  2. package/cmd/ttsc/api_compile.go +109 -101
  3. package/cmd/ttsc/api_compile_branches_test.go +55 -57
  4. package/cmd/ttsc/api_transform.go +54 -49
  5. package/cmd/ttsc/api_transform_branches_test.go +30 -32
  6. package/cmd/ttsc/build.go +111 -108
  7. package/cmd/ttsc/build_branches_test.go +95 -97
  8. package/cmd/ttsc/helpers_test.go +39 -28
  9. package/cmd/ttsc/main.go +111 -104
  10. package/cmd/ttsc/main_alias_branches_test.go +21 -23
  11. package/cmd/ttscserver/main.go +96 -90
  12. package/cmd/ttscserver/run_lsp_prefers_tsgo_flag_test.go +22 -22
  13. package/cmd/ttscserver/run_lsp_reports_server_error_test.go +22 -22
  14. package/cmd/ttscserver/run_lsp_uses_tsgo_environment_test.go +23 -23
  15. package/cmd/utility-host/main.go +30 -23
  16. package/driver/lsp.go +53 -7
  17. package/driver/plugins.go +88 -72
  18. package/driver/program.go +362 -339
  19. package/driver/rewrite.go +442 -380
  20. package/internal/lspserver/lsp_envelope.go +64 -64
  21. package/internal/lspserver/lsp_frame.go +60 -60
  22. package/internal/lspserver/lsp_plugin.go +57 -57
  23. package/internal/lspserver/lsp_proxy.go +265 -251
  24. package/internal/lspserver/lsp_server.go +155 -144
  25. package/lib/TtscCompiler.d.ts +7 -0
  26. package/lib/TtscCompiler.js +7 -0
  27. package/lib/TtscCompiler.js.map +1 -1
  28. package/lib/compiler/internal/buildNativeCompiler.d.ts +11 -1
  29. package/lib/compiler/internal/buildNativeCompiler.js +23 -3
  30. package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
  31. package/lib/compiler/internal/compileProjectInMemory.d.ts +9 -0
  32. package/lib/compiler/internal/compileProjectInMemory.js +36 -21
  33. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  34. package/lib/compiler/internal/paths.d.ts +27 -0
  35. package/lib/compiler/internal/paths.js +70 -0
  36. package/lib/compiler/internal/paths.js.map +1 -0
  37. package/lib/compiler/internal/project/readProjectConfig.d.ts +9 -1
  38. package/lib/compiler/internal/project/readProjectConfig.js +80 -1
  39. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  40. package/lib/compiler/internal/project/resolveProjectConfig.d.ts +13 -1
  41. package/lib/compiler/internal/project/resolveProjectConfig.js +24 -1
  42. package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
  43. package/lib/compiler/internal/resolveBinary.d.ts +9 -1
  44. package/lib/compiler/internal/resolveBinary.js +17 -1
  45. package/lib/compiler/internal/resolveBinary.js.map +1 -1
  46. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +16 -1
  47. package/lib/compiler/internal/resolveEmittedJavaScript.js +42 -10
  48. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  49. package/lib/compiler/internal/resolveTsgo.d.ts +21 -1
  50. package/lib/compiler/internal/resolveTsgo.js +23 -1
  51. package/lib/compiler/internal/resolveTsgo.js.map +1 -1
  52. package/lib/compiler/internal/runBuild.d.ts +19 -0
  53. package/lib/compiler/internal/runBuild.js +112 -46
  54. package/lib/compiler/internal/runBuild.js.map +1 -1
  55. package/lib/compiler/internal/runSingleFileEmit.d.ts +10 -1
  56. package/lib/compiler/internal/runSingleFileEmit.js +14 -1
  57. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  58. package/lib/compiler/internal/sharedHostHelpers.d.ts +5 -0
  59. package/lib/compiler/internal/sharedHostHelpers.js +6 -2
  60. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  61. package/lib/compiler/internal/spawnNative.d.ts +21 -0
  62. package/lib/compiler/internal/spawnNative.js +59 -0
  63. package/lib/compiler/internal/spawnNative.js.map +1 -0
  64. package/lib/compiler/internal/transformProjectInMemory.d.ts +15 -1
  65. package/lib/compiler/internal/transformProjectInMemory.js +57 -61
  66. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  67. package/lib/launcher/internal/getCompilerVersionText.js +7 -0
  68. package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
  69. package/lib/launcher/internal/prepareExecution.js +32 -0
  70. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  71. package/lib/launcher/internal/resolveCacheDir.d.ts +9 -0
  72. package/lib/launcher/internal/resolveCacheDir.js +22 -0
  73. package/lib/launcher/internal/resolveCacheDir.js.map +1 -0
  74. package/lib/launcher/internal/resolveTtscserverBinary.js +2 -0
  75. package/lib/launcher/internal/resolveTtscserverBinary.js.map +1 -1
  76. package/lib/launcher/internal/runTtsc.d.ts +10 -0
  77. package/lib/launcher/internal/runTtsc.js +101 -16
  78. package/lib/launcher/internal/runTtsc.js.map +1 -1
  79. package/lib/launcher/internal/runTtscserver.js +10 -35
  80. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  81. package/lib/launcher/internal/runTtsx.d.ts +8 -0
  82. package/lib/launcher/internal/runTtsx.js +33 -7
  83. package/lib/launcher/internal/runTtsx.js.map +1 -1
  84. package/lib/plugin/internal/buildSourcePlugin.d.ts +38 -0
  85. package/lib/plugin/internal/buildSourcePlugin.js +40 -17
  86. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  87. package/lib/plugin/internal/loadProjectPlugins.d.ts +26 -0
  88. package/lib/plugin/internal/loadProjectPlugins.js +32 -18
  89. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  90. package/lib/structures/ITtscCompilerTransformation.d.ts +15 -2
  91. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +0 -2
  92. package/package.json +9 -9
  93. package/shim/ast/go.mod +9 -0
  94. package/shim/ast/go.sum +20 -0
  95. package/shim/ast/safe_text.go +86 -0
  96. package/shim/ast/shim.go +24 -0
  97. package/shim/ast/test/node_text_joins_multi_hop_qualified_name_test.go +32 -0
  98. package/shim/ast/test/node_text_joins_one_hop_qualified_name_test.go +31 -0
  99. package/shim/ast/test/node_text_returns_empty_for_nil_test.go +23 -0
  100. package/shim/ast/test/node_text_returns_identifier_text_test.go +27 -0
  101. package/shim/ast/test/node_text_skips_empty_qualified_left_test.go +30 -0
  102. package/shim/checker/shim.go +35 -0
  103. package/shim/core/shim.go +17 -0
  104. package/shim/diagnosticwriter/shim.go +4 -0
  105. package/shim/lsp/shim.go +3 -3
  106. package/shim/printer/shim.go +20 -0
  107. package/src/TtscCompiler.ts +7 -0
  108. package/src/compiler/internal/buildNativeCompiler.ts +23 -3
  109. package/src/compiler/internal/compileProjectInMemory.ts +34 -25
  110. package/src/compiler/internal/paths.ts +68 -0
  111. package/src/compiler/internal/project/readProjectConfig.ts +86 -1
  112. package/src/compiler/internal/project/resolveProjectConfig.ts +24 -1
  113. package/src/compiler/internal/resolveBinary.ts +17 -1
  114. package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -12
  115. package/src/compiler/internal/resolveTsgo.ts +29 -1
  116. package/src/compiler/internal/runBuild.ts +110 -55
  117. package/src/compiler/internal/runSingleFileEmit.ts +14 -1
  118. package/src/compiler/internal/sharedHostHelpers.ts +7 -7
  119. package/src/compiler/internal/spawnNative.ts +64 -0
  120. package/src/compiler/internal/transformProjectInMemory.ts +47 -67
  121. package/src/launcher/internal/getCompilerVersionText.ts +7 -0
  122. package/src/launcher/internal/prepareExecution.ts +32 -0
  123. package/src/launcher/internal/resolveCacheDir.ts +19 -0
  124. package/src/launcher/internal/resolveTtscserverBinary.ts +2 -0
  125. package/src/launcher/internal/runTtsc.ts +120 -20
  126. package/src/launcher/internal/runTtscserver.ts +9 -1
  127. package/src/launcher/internal/runTtsx.ts +33 -8
  128. package/src/plugin/internal/buildSourcePlugin.ts +40 -14
  129. package/src/plugin/internal/loadProjectPlugins.ts +42 -18
  130. package/src/structures/ITtscCompilerTransformation.ts +15 -2
  131. package/src/structures/ITtscPlugin.ts +0 -1
  132. package/src/structures/internal/TtscSingleFileEmitOptions.ts +0 -2
  133. package/tools/gen_shims/main.go +43 -8
  134. package/utility/host.go +294 -245
@@ -1,6 +1,3 @@
1
- import { spawnSync } from "node:child_process";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
1
  import path from "node:path";
5
2
 
6
3
  import { loadProjectPlugins } from "../../plugin/internal/loadProjectPlugins";
@@ -17,8 +14,13 @@ import {
17
14
  linkedTransformPlugins,
18
15
  selectSharedHostPlugin,
19
16
  } from "./sharedHostHelpers";
17
+ import { outputText, spawnNative } from "./spawnNative";
20
18
 
21
- /** Merge spawn env without clobbering unrelated vars. */
19
+ /**
20
+ * Merge extra environment variables over `process.env`, always injecting
21
+ * `TTSC_NODE_BINARY` so child processes can re-invoke the same Node.js binary
22
+ * without searching `PATH`.
23
+ */
22
24
  function mergeEnv(extra?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
23
25
  const base = {
24
26
  ...process.env,
@@ -28,6 +30,12 @@ function mergeEnv(extra?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
28
30
  return { ...base, ...extra };
29
31
  }
30
32
 
33
+ /**
34
+ * Build the environment for a native plugin spawn. Injects `TTSC_TSGO_BINARY`
35
+ * and `TTSC_TTSX_BINARY` alongside the base env from `mergeEnv`. For
36
+ * transform-stage plugins, also passes `TTSC_LINKED_PLUGINS_JSON` containing
37
+ * any linked sources so they run inside the same process as the host plugin.
38
+ */
31
39
  function nativePluginEnv(
32
40
  extra: NodeJS.ProcessEnv | undefined,
33
41
  execution: ReturnType<typeof resolveExecutionContext>,
@@ -49,54 +57,6 @@ function nativePluginEnv(
49
57
  return env;
50
58
  }
51
59
 
52
- function spawnBinary(
53
- binary: string,
54
- args: readonly string[],
55
- options: {
56
- cwd?: string;
57
- env?: NodeJS.ProcessEnv;
58
- encoding?: BufferEncoding;
59
- },
60
- ) {
61
- const viaNode = /\.(?:[cm]?js|ts)$/i.test(binary);
62
- if (!viaNode) {
63
- ensureExecutable(binary);
64
- }
65
- return spawnSync(
66
- viaNode ? process.execPath : binary,
67
- viaNode ? [binary, ...args] : [...args],
68
- {
69
- cwd: options.cwd,
70
- env: options.env,
71
- encoding: options.encoding,
72
- maxBuffer: 1024 * 1024 * 256,
73
- windowsHide: true,
74
- },
75
- );
76
- }
77
-
78
- function ensureExecutable(binary: string): void {
79
- if (process.platform === "win32") {
80
- return;
81
- }
82
- try {
83
- const mode = fs.statSync(binary).mode & 0o777;
84
- if ((mode & 0o111) !== 0) {
85
- return;
86
- }
87
- fs.chmodSync(binary, mode | 0o755);
88
- } catch {
89
- /* keep the original spawn error path */
90
- }
91
- }
92
-
93
- function outputText(value: string | Buffer | null | undefined): string {
94
- if (value == null) {
95
- return "";
96
- }
97
- return typeof value === "string" ? value : value.toString("utf8");
98
- }
99
-
100
60
  /**
101
61
  * Run `ttsc` against a tsconfig. Returns once the binary exits so the CLI can
102
62
  * decide how to surface diagnostics. Does not throw on non-zero exit.
@@ -199,6 +159,11 @@ export function runBuild(
199
159
  return runTsgoBuild(execution, options, args);
200
160
  }
201
161
 
162
+ /**
163
+ * Dispatch a build through the shared-host native plugin. The host plugin is
164
+ * the one that owns the process (non-linked); all other transform plugins ride
165
+ * inside it via the `--plugins-json` flag.
166
+ */
202
167
  function buildWithNativeCompilerPlugins(
203
168
  options: TtscBuildOptions,
204
169
  execution: ReturnType<typeof resolveExecutionContext>,
@@ -214,12 +179,16 @@ function buildWithNativeCompilerPlugins(
214
179
  );
215
180
  }
216
181
 
182
+ /**
183
+ * Run `tsgo -p <tsconfig> [extraArgs]` and return the normalized result. Used
184
+ * for the no-emit type-check pass that precedes file emission.
185
+ */
217
186
  function runTsgo(
218
187
  execution: ReturnType<typeof resolveExecutionContext>,
219
188
  extraArgs: readonly string[],
220
189
  options: NonNullable<Parameters<typeof runBuild>[0]>,
221
190
  ): TtscBuildResult {
222
- const res = spawnBinary(
191
+ const res = spawnNative(
223
192
  execution.tsgo.binary,
224
193
  [
225
194
  "-p",
@@ -251,12 +220,17 @@ function runTsgo(
251
220
  );
252
221
  }
253
222
 
223
+ /**
224
+ * Run `tsgo` with the full emit arguments and parse `TSFILE:` lines from stdout
225
+ * into `emittedFiles`. The TSFILE lines are stripped before the result is
226
+ * returned so they do not appear in the user-facing output.
227
+ */
254
228
  function runTsgoBuild(
255
229
  execution: ReturnType<typeof resolveExecutionContext>,
256
230
  options: NonNullable<Parameters<typeof runBuild>[0]>,
257
231
  args: readonly string[],
258
232
  ): TtscBuildResult {
259
- const res = spawnBinary(execution.tsgo.binary, args, {
233
+ const res = spawnNative(execution.tsgo.binary, args, {
260
234
  cwd: execution.projectRoot,
261
235
  env: mergeEnv(options.env),
262
236
  encoding: "utf8",
@@ -284,6 +258,7 @@ function runTsgoBuild(
284
258
  );
285
259
  }
286
260
 
261
+ /** Build the argument list for a direct `tsgo` build invocation. */
287
262
  function createTsgoBuildArgs(
288
263
  execution: ReturnType<typeof resolveExecutionContext>,
289
264
  options: NonNullable<Parameters<typeof runBuild>[0]>,
@@ -305,10 +280,15 @@ function createTsgoBuildArgs(
305
280
  return args;
306
281
  }
307
282
 
283
+ /**
284
+ * Return `["--pretty", "false"]` when structured diagnostics are requested so
285
+ * that the output can be parsed line-by-line, or an empty array otherwise.
286
+ */
308
287
  function createTsgoDiagnosticArgs(options: TtscCommonOptions): string[] {
309
288
  return options.structuredDiagnostics === true ? ["--pretty", "false"] : [];
310
289
  }
311
290
 
291
+ /** Build the argument list for a native plugin `build`/`check` invocation. */
312
292
  function createNativeBuildArgs(
313
293
  execution: ReturnType<typeof resolveExecutionContext>,
314
294
  options: TtscBuildOptions,
@@ -336,6 +316,7 @@ function createNativeBuildArgs(
336
316
  return args;
337
317
  }
338
318
 
319
+ /** Build the argument list for a native plugin check/fix/format invocation. */
339
320
  function createNativeCheckArgs(
340
321
  execution: ReturnType<typeof resolveExecutionContext>,
341
322
  options: TtscBuildOptions,
@@ -369,6 +350,10 @@ function nativeCheckSubcommand(options: TtscBuildOptions): string {
369
350
  return "check";
370
351
  }
371
352
 
353
+ /**
354
+ * Serialize the plugin list to a compact JSON string for `--plugins-json=`.
355
+ * Only the fields the native binary protocol requires are included.
356
+ */
372
357
  function serializeNativePlugins(
373
358
  plugins: readonly ITtscLoadedNativePlugin[],
374
359
  ): string {
@@ -381,6 +366,10 @@ function serializeNativePlugins(
381
366
  );
382
367
  }
383
368
 
369
+ /**
370
+ * Run every check-stage plugin in order, short-circuiting on the first non-zero
371
+ * exit. Aggregates diagnostics and output across all check plugins.
372
+ */
384
373
  function runNativeCheckPlugins(
385
374
  options: TtscBuildOptions,
386
375
  execution: ReturnType<typeof resolveExecutionContext>,
@@ -409,6 +398,11 @@ function runNativeCheckPlugins(
409
398
  return out;
410
399
  }
411
400
 
401
+ /**
402
+ * Spawn a single native plugin binary with the given args and return the
403
+ * normalized build result. `label` is used in the thrown error message so the
404
+ * caller's context (e.g. `"ttsc.check"` or `"ttsc.build"`) is preserved.
405
+ */
412
406
  function runNativePluginCommand(
413
407
  plugin: ITtscLoadedNativePlugin,
414
408
  args: readonly string[],
@@ -416,7 +410,7 @@ function runNativePluginCommand(
416
410
  execution: ReturnType<typeof resolveExecutionContext>,
417
411
  label: string,
418
412
  ): TtscBuildResult {
419
- const res = spawnBinary(plugin.binary, args, {
413
+ const res = spawnNative(plugin.binary, args, {
420
414
  cwd: execution.projectRoot,
421
415
  env: nativePluginEnv(options.env, execution, plugin),
422
416
  encoding: "utf8",
@@ -436,6 +430,14 @@ function runNativePluginCommand(
436
430
  );
437
431
  }
438
432
 
433
+ /**
434
+ * Merge two `TtscBuildResult` values into one.
435
+ *
436
+ * - `status`: the right status wins unless it is 0 (failure propagates).
437
+ * - `diagnostics`: concatenated left then right.
438
+ * - `emittedFiles`: right wins when present, otherwise left is kept.
439
+ * - `stdout`/`stderr`: concatenated left then right.
440
+ */
439
441
  export function appendBuildOutput(
440
442
  left: TtscBuildResult,
441
443
  right: TtscBuildResult,
@@ -450,6 +452,11 @@ export function appendBuildOutput(
450
452
  });
451
453
  }
452
454
 
455
+ /**
456
+ * Resolve all runtime context needed for a build: cwd, tsconfig path, project
457
+ * root, tsgo binary location, and the loaded native plugin list. Centralised
458
+ * here so every code path in `runBuild` shares the same resolution logic.
459
+ */
453
460
  function resolveExecutionContext(
454
461
  options: TtscCommonOptions & { tsconfig?: string },
455
462
  ) {
@@ -480,6 +487,10 @@ function resolveExecutionContext(
480
487
  };
481
488
  }
482
489
 
490
+ /**
491
+ * Extract `TSFILE: <path>` lines from tsgo stdout and return the absolute
492
+ * paths. tsgo emits these when `--listEmittedFiles` is passed.
493
+ */
483
494
  function parseEmittedFiles(stdout: string): string[] {
484
495
  const out: string[] = [];
485
496
  for (const line of stdout.split(/\r?\n/)) {
@@ -491,6 +502,10 @@ function parseEmittedFiles(stdout: string): string[] {
491
502
  return out;
492
503
  }
493
504
 
505
+ /**
506
+ * Remove `TSFILE:` lines from tsgo stdout so they do not appear in the
507
+ * user-facing output after they have been parsed into `emittedFiles`.
508
+ */
494
509
  function stripEmittedFileLines(stdout: string): string {
495
510
  return stdout
496
511
  .split(/\r?\n/)
@@ -499,10 +514,21 @@ function stripEmittedFileLines(stdout: string): string {
499
514
  .replace(/\n+$/, "");
500
515
  }
501
516
 
517
+ /**
518
+ * `TtscBuildResult` with `diagnostics` made optional. Used internally when
519
+ * diagnostics are parsed lazily from stdout/stderr by `normalizeBuildOutput`.
520
+ */
502
521
  type PartialBuildResult = Omit<TtscBuildResult, "diagnostics"> & {
503
522
  diagnostics?: ITtscCompilerDiagnostic[];
504
523
  };
505
524
 
525
+ /**
526
+ * Normalise a raw spawn result into a `TtscBuildResult`.
527
+ *
528
+ * When `diagnostics` is absent they are parsed from the text output. When the
529
+ * process exited non-zero but stderr is empty and stdout is non-empty, stdout
530
+ * is moved to stderr so the error is visible to callers who only check stderr.
531
+ */
506
532
  export function normalizeBuildOutput(
507
533
  result: PartialBuildResult,
508
534
  cwd?: string,
@@ -524,6 +550,11 @@ export function normalizeBuildOutput(
524
550
  };
525
551
  }
526
552
 
553
+ /**
554
+ * Parse structured diagnostics from the combined stderr+stdout text when the
555
+ * caller did not supply pre-parsed diagnostics. ANSI escape codes are stripped
556
+ * and `TSFILE:` / `Found N errors` summary lines are skipped.
557
+ */
527
558
  function parseCompilerDiagnostics(
528
559
  result: Pick<TtscBuildResult, "stderr" | "stdout">,
529
560
  cwd: string | undefined,
@@ -553,6 +584,16 @@ function parseCompilerDiagnostics(
553
584
  return out;
554
585
  }
555
586
 
587
+ /**
588
+ * Try to parse a single line as a TypeScript compiler diagnostic in one of
589
+ * three formats:
590
+ *
591
+ * - `file:line:col - category TSxxxx: message` (colon-separated, tsgo style)
592
+ * - `file(line,col): category TSxxxx: message` (paren style, classic tsc)
593
+ * - `category TSxxxx: message` (global, no file)
594
+ *
595
+ * Returns `null` when the line does not match any format.
596
+ */
556
597
  function parseDiagnosticLine(
557
598
  line: string,
558
599
  cwd: string | undefined,
@@ -599,6 +640,10 @@ function parseDiagnosticLine(
599
640
  };
600
641
  }
601
642
 
643
+ /**
644
+ * Return an absolute file path. When `file` is already absolute it is returned
645
+ * unchanged; relative paths are resolved against `cwd` when available.
646
+ */
602
647
  function normalizeDiagnosticFile(
603
648
  file: string,
604
649
  cwd: string | undefined,
@@ -609,6 +654,11 @@ function normalizeDiagnosticFile(
609
654
  return path.resolve(cwd, file);
610
655
  }
611
656
 
657
+ /**
658
+ * Map a raw category string (`"error"`, `"warning"`, `"suggestion"`,
659
+ * `"message"`) to the canonical `ITtscCompilerDiagnostic.Category`. Any
660
+ * unrecognised value is coerced to `"error"`.
661
+ */
612
662
  function normalizeDiagnosticCategory(
613
663
  value: string,
614
664
  ): ITtscCompilerDiagnostic.Category {
@@ -620,10 +670,15 @@ function normalizeDiagnosticCategory(
620
670
  : "error";
621
671
  }
622
672
 
673
+ /**
674
+ * Parse a diagnostic code as a number when it is all digits (TS numeric codes
675
+ * like `2322`), or keep it as a string for plugin-defined alphanumeric codes.
676
+ */
623
677
  function normalizeDiagnosticCode(value: string): number | string {
624
678
  return /^\d+$/.test(value) ? Number(value) : value;
625
679
  }
626
680
 
681
+ /** Strip ANSI escape sequences from `text` for line-by-line diagnostic parsing. */
627
682
  function stripAnsi(text: string): string {
628
683
  return text.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
629
684
  }
@@ -7,7 +7,16 @@ import { resolveProjectConfig } from "./project/resolveProjectConfig";
7
7
  import { resolveEmittedJavaScript } from "./resolveEmittedJavaScript";
8
8
  import { runBuild } from "./runBuild";
9
9
 
10
- /** Emit one source file by building its project into a temporary directory. */
10
+ /**
11
+ * Emit one source file by building its project into a temporary directory.
12
+ *
13
+ * The full project is compiled with `forceListEmittedFiles` so that the emitted
14
+ * file list is available for `resolveEmittedJavaScript`. The temp directory is
15
+ * always cleaned up in the `finally` block, even on error.
16
+ *
17
+ * @returns The transformed JavaScript source text.
18
+ * @throws When the build exits non-zero or no output is produced for the file.
19
+ */
11
20
  export function runSingleFileEmit(options: TtscSingleFileEmitOptions): string {
12
21
  const cwd = path.resolve(options.cwd ?? process.cwd());
13
22
  const sourceFile = realpathIfExists(
@@ -64,6 +73,10 @@ export function runSingleFileEmit(options: TtscSingleFileEmitOptions): string {
64
73
  }
65
74
  }
66
75
 
76
+ /**
77
+ * Resolve symlinks on `file` when it exists; return the original path when the
78
+ * file is not yet on disk (e.g. a synthetic path used in tests).
79
+ */
67
80
  function realpathIfExists(file: string): string {
68
81
  try {
69
82
  return fs.realpathSync(file);
@@ -4,9 +4,7 @@ import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoa
4
4
  * Reports whether the given transform source is linked into another compiler
5
5
  * host instead of owning the process itself.
6
6
  */
7
- export function isLinkedTransform(
8
- plugin: ITtscLoadedNativePlugin,
9
- ): boolean {
7
+ export function isLinkedTransform(plugin: ITtscLoadedNativePlugin): boolean {
10
8
  return plugin.stage === "transform" && plugin.kind === "linked";
11
9
  }
12
10
 
@@ -59,12 +57,14 @@ export function assertSharedHostCompatibility(
59
57
  export function selectSharedHostPlugin(
60
58
  plugins: readonly ITtscLoadedNativePlugin[],
61
59
  ): ITtscLoadedNativePlugin {
62
- return (
63
- plugins.find((plugin) => !isLinkedTransform(plugin)) ??
64
- plugins[0]!
65
- );
60
+ return plugins.find((plugin) => !isLinkedTransform(plugin)) ?? plugins[0]!;
66
61
  }
67
62
 
63
+ /**
64
+ * Return every plugin whose transform source is linked into another host binary
65
+ * rather than owning the process. The host binary passes these via
66
+ * `TTSC_LINKED_PLUGINS_JSON` so their Go code runs inside the same process.
67
+ */
68
68
  export function linkedTransformPlugins(
69
69
  plugins: readonly ITtscLoadedNativePlugin[],
70
70
  ): ITtscLoadedNativePlugin[] {
@@ -0,0 +1,64 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+
4
+ /**
5
+ * Spawn a native binary (or a Node.js script when the path has a JS/TS
6
+ * extension) with the given args and return the `spawnSync` result. Sets a
7
+ * generous 256 MiB output buffer so large projects do not truncate.
8
+ *
9
+ * When `options.encoding` is omitted it defaults to `"utf8"`. Pass an explicit
10
+ * value when the caller needs a different encoding or raw `Buffer` output.
11
+ */
12
+ export function spawnNative(
13
+ binary: string,
14
+ args: readonly string[],
15
+ options: {
16
+ cwd?: string;
17
+ env?: NodeJS.ProcessEnv;
18
+ encoding?: BufferEncoding;
19
+ },
20
+ ) {
21
+ const viaNode = /\.(?:[cm]?js|ts)$/i.test(binary);
22
+ if (!viaNode) {
23
+ ensureExecutable(binary);
24
+ }
25
+ return spawnSync(
26
+ viaNode ? process.execPath : binary,
27
+ viaNode ? [binary, ...args] : [...args],
28
+ {
29
+ cwd: options.cwd,
30
+ encoding: options.encoding ?? "utf8",
31
+ env: options.env,
32
+ maxBuffer: 1024 * 1024 * 256,
33
+ windowsHide: true,
34
+ },
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Ensure the binary has the executable bit set on POSIX systems. Silently skips
40
+ * on Windows and swallows `chmod` errors to let the original spawn error
41
+ * surface instead of masking it with a permission error.
42
+ */
43
+ export function ensureExecutable(binary: string): void {
44
+ if (process.platform === "win32") {
45
+ return;
46
+ }
47
+ try {
48
+ const mode = fs.statSync(binary).mode & 0o777;
49
+ if ((mode & 0o111) !== 0) {
50
+ return;
51
+ }
52
+ fs.chmodSync(binary, mode | 0o755);
53
+ } catch {
54
+ /* keep the original spawn error path */
55
+ }
56
+ }
57
+
58
+ /** Coerce a `spawnSync` output value to a plain string, defaulting to `""`. */
59
+ export function outputText(value: string | Buffer | null | undefined): string {
60
+ if (value == null) {
61
+ return "";
62
+ }
63
+ return typeof value === "string" ? value : value.toString("utf8");
64
+ }
@@ -1,5 +1,3 @@
1
- import { spawnSync } from "node:child_process";
2
- import fs from "node:fs";
3
1
  import path from "node:path";
4
2
 
5
3
  import {
@@ -12,6 +10,7 @@ import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoa
12
10
  import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscParsedProjectConfig";
13
11
  import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
14
12
  import { buildNativeCompiler } from "./buildNativeCompiler";
13
+ import { packageRootDir } from "./paths";
15
14
  import { readProjectConfig } from "./project/readProjectConfig";
16
15
  import { resolveBinary } from "./resolveBinary";
17
16
  import { resolveTsgo } from "./resolveTsgo";
@@ -21,8 +20,23 @@ import {
21
20
  linkedTransformPlugins,
22
21
  selectSharedHostPlugin,
23
22
  } from "./sharedHostHelpers";
23
+ import { outputText, spawnNative } from "./spawnNative";
24
24
 
25
- /** Transform a project and capture TypeScript source output in memory. */
25
+ /**
26
+ * Transform a project and capture TypeScript source output in memory.
27
+ *
28
+ * When no plugins are configured the fast path spawns the native ttsc compiler
29
+ * host (`cmd/ttsc api-transform`) which returns a JSON map of transformed
30
+ * TypeScript sources. When plugins are present:
31
+ *
32
+ * 1. Check-stage plugins run first and abort on failure.
33
+ * 2. If there are no transform-stage plugins the host is used as the transformer.
34
+ * 3. If transform plugins exist they are dispatched through the shared-host binary
35
+ * with linked plugins passed via `TTSC_LINKED_PLUGINS_JSON`.
36
+ *
37
+ * @returns A `{ result, typescript }` pair where `typescript` maps output paths
38
+ * to their transformed TypeScript source text.
39
+ */
26
40
  export function transformProjectInMemory(options: ITtscCompilerContext): {
27
41
  result: TtscBuildResult;
28
42
  typescript: Record<string, string>;
@@ -39,6 +53,7 @@ export function transformProjectInMemory(options: ITtscCompilerContext): {
39
53
  return transformProjectWithNativeHost(options, project);
40
54
  }
41
55
 
56
+ /** Return true when the project or the call-level options declare any plugins. */
42
57
  function hasConfiguredPlugins(
43
58
  options: ITtscCompilerContext,
44
59
  project: ITtscParsedProjectConfig,
@@ -46,6 +61,11 @@ function hasConfiguredPlugins(
46
61
  return hasProjectPluginEntries(project, options.plugins);
47
62
  }
48
63
 
64
+ /**
65
+ * Transform via the built-in native compiler host (`cmd/ttsc api-transform`).
66
+ * Used when no user plugins are configured, or as the fallback transformer when
67
+ * check-stage plugins pass and no transform-stage plugins are declared.
68
+ */
49
69
  function transformProjectWithNativeHost(
50
70
  options: ITtscCompilerContext,
51
71
  project: ITtscParsedProjectConfig,
@@ -160,6 +180,10 @@ function transformProjectWithPlugins(
160
180
  };
161
181
  }
162
182
 
183
+ /**
184
+ * Run every check-stage plugin in sequence, short-circuiting on the first
185
+ * failure. Returns the aggregated `TtscBuildResult` (status 0 when all pass).
186
+ */
163
187
  function runNativeChecks(
164
188
  options: ITtscCompilerContext,
165
189
  project: ITtscParsedProjectConfig,
@@ -204,6 +228,7 @@ function runNativeChecks(
204
228
  return result;
205
229
  }
206
230
 
231
+ /** Build the CLI argument list for the `transform` subcommand. */
207
232
  function createNativeTransformArgs(
208
233
  project: ITtscParsedProjectConfig,
209
234
  plugins: readonly ITtscLoadedNativePlugin[],
@@ -216,6 +241,7 @@ function createNativeTransformArgs(
216
241
  ];
217
242
  }
218
243
 
244
+ /** Build the CLI argument list for the `check` subcommand. */
219
245
  function createNativeCheckArgs(
220
246
  project: ITtscParsedProjectConfig,
221
247
  plugins: readonly ITtscLoadedNativePlugin[],
@@ -228,6 +254,10 @@ function createNativeCheckArgs(
228
254
  ];
229
255
  }
230
256
 
257
+ /**
258
+ * Serialize the plugin list to a JSON string for `--plugins-json=`. Only the
259
+ * fields the native binary needs are included to keep the arg short.
260
+ */
231
261
  function serializeNativePlugins(
232
262
  plugins: readonly ITtscLoadedNativePlugin[],
233
263
  ): string {
@@ -240,6 +270,12 @@ function serializeNativePlugins(
240
270
  );
241
271
  }
242
272
 
273
+ /**
274
+ * Build the environment for a native plugin spawn. Injects `TTSC_NODE_BINARY`,
275
+ * `TTSC_TSGO_BINARY`, and `TTSC_TTSX_BINARY` so the sidecar can re-invoke
276
+ * Node.js or tsgo without searching PATH. For transform plugins, also passes
277
+ * `TTSC_LINKED_PLUGINS_JSON` when linked sources are present.
278
+ */
243
279
  function nativePluginEnv(
244
280
  options: ITtscCompilerContext,
245
281
  projectRoot: string,
@@ -265,53 +301,13 @@ function nativePluginEnv(
265
301
  return env;
266
302
  }
267
303
 
268
- function spawnNative(
269
- binary: string,
270
- args: readonly string[],
271
- options: {
272
- cwd?: string;
273
- env?: NodeJS.ProcessEnv;
274
- },
275
- ) {
276
- const viaNode = /\.(?:[cm]?js|ts)$/i.test(binary);
277
- if (!viaNode) {
278
- ensureExecutable(binary);
279
- }
280
- return spawnSync(
281
- viaNode ? process.execPath : binary,
282
- viaNode ? [binary, ...args] : [...args],
283
- {
284
- cwd: options.cwd,
285
- encoding: "utf8",
286
- env: options.env,
287
- maxBuffer: 1024 * 1024 * 256,
288
- windowsHide: true,
289
- },
290
- );
291
- }
292
-
293
- function ensureExecutable(binary: string): void {
294
- if (process.platform === "win32") {
295
- return;
296
- }
297
- try {
298
- const mode = fs.statSync(binary).mode & 0o777;
299
- if ((mode & 0o111) !== 0) {
300
- return;
301
- }
302
- fs.chmodSync(binary, mode | 0o755);
303
- } catch {
304
- /* keep the original spawn error path */
305
- }
306
- }
307
-
308
- function outputText(value: string | Buffer | null | undefined): string {
309
- if (value == null) {
310
- return "";
311
- }
312
- return typeof value === "string" ? value : value.toString("utf8");
313
- }
314
-
304
+ /**
305
+ * Parse the JSON envelope written by the native transform host to stdout.
306
+ *
307
+ * The `typescript` field must be a `Record<string, string>`. Any other shape is
308
+ * treated as a protocol error and throws with the stderr/stdout context. JSON
309
+ * parse errors are also wrapped with the same context message.
310
+ */
315
311
  function parseNativeTransformOutput(
316
312
  stdout: string,
317
313
  stderr: string,
@@ -344,6 +340,7 @@ function parseNativeTransformOutput(
344
340
  }
345
341
  }
346
342
 
343
+ /** Type guard: true when `value` is a non-null, non-array object of strings. */
347
344
  function isTextRecord(value: unknown): value is Record<string, string> {
348
345
  return (
349
346
  typeof value === "object" &&
@@ -352,20 +349,3 @@ function isTextRecord(value: unknown): value is Record<string, string> {
352
349
  Object.values(value).every((entry) => typeof entry === "string")
353
350
  );
354
351
  }
355
-
356
- function packageRootDir(): string {
357
- let current = path.resolve(__dirname);
358
- while (true) {
359
- if (
360
- fs.existsSync(path.join(current, "package.json")) &&
361
- fs.existsSync(path.join(current, "go.mod"))
362
- ) {
363
- return fs.realpathSync.native?.(current) ?? fs.realpathSync(current);
364
- }
365
- const parent = path.dirname(current);
366
- if (parent === current) {
367
- throw new Error("ttsc: package root not found for native compiler build");
368
- }
369
- current = parent;
370
- }
371
- }
@@ -23,6 +23,7 @@ export function getCompilerVersionText(
23
23
  return `ttsc ${readOwnPackageVersion()} (${outputText(res.stdout).trim()})`;
24
24
  }
25
25
 
26
+ /** Coerce a spawnSync stdio field (string | Buffer | null) to a plain string. */
26
27
  function outputText(value: string | Buffer | null | undefined): string {
27
28
  if (value == null) {
28
29
  return "";
@@ -30,6 +31,12 @@ function outputText(value: string | Buffer | null | undefined): string {
30
31
  return typeof value === "string" ? value : value.toString("utf8");
31
32
  }
32
33
 
34
+ /**
35
+ * Read the `version` field from the `@ttsc/ttsc` package.json that sits three
36
+ * directories above the compiled launcher output (`lib/launcher/internal/`).
37
+ * Returns `"0.0.0"` on any I/O or parse failure so the banner is always safe to
38
+ * display.
39
+ */
33
40
  function readOwnPackageVersion(): string {
34
41
  try {
35
42
  const file = path.resolve(__dirname, "..", "..", "..", "package.json");