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
@@ -9,12 +9,22 @@ import type { ITtscCompilerDiagnostic } from "../../structures/ITtscCompilerDiag
9
9
  import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscParsedProjectConfig";
10
10
  import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
11
11
  import { buildNativeCompiler } from "./buildNativeCompiler";
12
+ import { isOutsideRelativePath, packageRootDir } from "./paths";
12
13
  import { readProjectConfig } from "./project/readProjectConfig";
13
14
  import { runBuild } from "./runBuild";
14
15
 
15
16
  /**
16
17
  * Compile a project and capture emitted files without writing to the project
17
18
  * tree.
19
+ *
20
+ * When no plugins are configured the fast path spawns the native ttsc compiler
21
+ * host (`cmd/ttsc api-compile`) which returns a structured JSON response
22
+ * containing diagnostics and an output file map. When plugins are present the
23
+ * slow path goes through `runBuild` into a temp directory and reads the files
24
+ * back from disk.
25
+ *
26
+ * @returns A map of output path → file content plus a `TtscBuildResult` with
27
+ * diagnostics and the exit status.
18
28
  */
19
29
  export function compileProjectInMemory(options: ITtscCompilerContext): {
20
30
  output: Record<string, string>;
@@ -64,6 +74,7 @@ export function compileProjectInMemory(options: ITtscCompilerContext): {
64
74
  };
65
75
  }
66
76
 
77
+ /** Return true when the project or the call-level options declare any plugins. */
67
78
  function hasConfiguredPlugins(
68
79
  options: ITtscCompilerContext,
69
80
  project: ITtscParsedProjectConfig,
@@ -71,6 +82,10 @@ function hasConfiguredPlugins(
71
82
  return hasProjectPluginEntries(project, options.plugins);
72
83
  }
73
84
 
85
+ /**
86
+ * Plugin-backed compilation: emit into a temp directory via `runBuild`, then
87
+ * read back every file the build wrote so they can be returned as strings.
88
+ */
74
89
  function compileProjectWithPlugins(
75
90
  options: ITtscCompilerContext,
76
91
  cwd: string,
@@ -101,6 +116,15 @@ function compileProjectWithPlugins(
101
116
  }
102
117
  }
103
118
 
119
+ /**
120
+ * Build a function that maps a path relative to the temp output directory to
121
+ * the key used in the returned `output` map.
122
+ *
123
+ * When `outDir` is inside the project root the key is relative to the project
124
+ * root (preserving the `outDir` prefix). When `outDir` is outside the project
125
+ * root the key is absolute-style (`/absolute/outDir/relative`). When `outDir`
126
+ * is absent the key is the bare relative path.
127
+ */
104
128
  function outputKeyMapper(
105
129
  project: ITtscParsedProjectConfig,
106
130
  ): (relativePath: string) => string {
@@ -116,14 +140,7 @@ function outputKeyMapper(
116
140
  return (relativePath) => pathToKey(path.join(outDir, relativePath));
117
141
  }
118
142
 
119
- function isOutsideRelativePath(relative: string): boolean {
120
- return (
121
- relative === ".." ||
122
- relative.startsWith(`..${path.sep}`) ||
123
- path.isAbsolute(relative)
124
- );
125
- }
126
-
143
+ /** Read every file in `directory` recursively and return a `path→content` map. */
127
144
  function readOutputDirectory(
128
145
  directory: string,
129
146
  keyOf: (relativePath: string) => string,
@@ -141,6 +158,7 @@ function readOutputDirectory(
141
158
  return output;
142
159
  }
143
160
 
161
+ /** Recursively list all files under `directory`, sorted for stable output. */
144
162
  function listFiles(directory: string): string[] {
145
163
  const out: string[] = [];
146
164
  for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
@@ -154,10 +172,18 @@ function listFiles(directory: string): string[] {
154
172
  return out.sort();
155
173
  }
156
174
 
175
+ /** Normalise a file path to a forward-slash key suitable for the output map. */
157
176
  function pathToKey(file: string): string {
158
177
  return file.replace(/\\/g, "/");
159
178
  }
160
179
 
180
+ /**
181
+ * Parse the JSON envelope written by the native compiler host to stdout.
182
+ *
183
+ * On success returns `{ diagnostics, output }`. On JSON parse failure throws a
184
+ * descriptive error using stderr (preferred) or stdout as context, so callers
185
+ * see the original compiler error rather than a generic JSON parse message.
186
+ */
161
187
  function parseNativeCompileOutput(
162
188
  stdout: string,
163
189
  stderr: string,
@@ -181,20 +207,3 @@ function parseNativeCompileOutput(
181
207
  );
182
208
  }
183
209
  }
184
-
185
- function packageRootDir(): string {
186
- let current = path.resolve(__dirname);
187
- while (true) {
188
- if (
189
- fs.existsSync(path.join(current, "package.json")) &&
190
- fs.existsSync(path.join(current, "go.mod"))
191
- ) {
192
- return fs.realpathSync.native?.(current) ?? fs.realpathSync(current);
193
- }
194
- const parent = path.dirname(current);
195
- if (parent === current) {
196
- throw new Error("ttsc: package root not found for native compiler build");
197
- }
198
- current = parent;
199
- }
200
- }
@@ -0,0 +1,68 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ /**
5
+ * Walk up the directory tree from `__dirname` until a directory that contains
6
+ * both `package.json` and `go.mod` is found. This is the `packages/ttsc` root
7
+ * and must be passed to `buildNativeCompiler` so it can locate `cmd/ttsc`.
8
+ *
9
+ * Uses `fs.realpathSync.native` when available to resolve symlinks identically
10
+ * to the Go toolchain's own path resolution.
11
+ *
12
+ * Throws when the root cannot be found (i.e. the package is not in a Go
13
+ * workspace), which would indicate a broken installation.
14
+ */
15
+ export function packageRootDir(): string {
16
+ let current = path.resolve(__dirname);
17
+ while (true) {
18
+ if (
19
+ fs.existsSync(path.join(current, "package.json")) &&
20
+ fs.existsSync(path.join(current, "go.mod"))
21
+ ) {
22
+ return fs.realpathSync.native?.(current) ?? fs.realpathSync(current);
23
+ }
24
+ const parent = path.dirname(current);
25
+ if (parent === current) {
26
+ throw new Error("ttsc: package root not found for native compiler build");
27
+ }
28
+ current = parent;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Walk up the directory tree from `from` looking for a `go.mod` file. Stops
34
+ * after `maxDepth` hops so callers can bound the search to a known
35
+ * neighbourhood of the plugin source directory.
36
+ *
37
+ * Returns the absolute path to the first `go.mod` found, or `null` when no
38
+ * `go.mod` exists within the depth limit or filesystem root is reached first.
39
+ */
40
+ export function findNearestGoMod(
41
+ from: string,
42
+ maxDepth: number,
43
+ ): string | null {
44
+ let current = path.resolve(from);
45
+ let depth = 0;
46
+ while (true) {
47
+ const candidate = path.join(current, "go.mod");
48
+ if (fs.existsSync(candidate)) return candidate;
49
+ if (depth >= maxDepth) return null;
50
+ const parent = path.dirname(current);
51
+ if (parent === current) return null;
52
+ current = parent;
53
+ depth += 1;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Return `true` when a relative path escapes its base directory — i.e. starts
59
+ * with `..` or is an absolute path. Used by callers that need to guard against
60
+ * path traversal before building output keys or spawning subprocesses.
61
+ */
62
+ export function isOutsideRelativePath(relative: string): boolean {
63
+ return (
64
+ relative === ".." ||
65
+ relative.startsWith(`..${path.sep}`) ||
66
+ path.isAbsolute(relative)
67
+ );
68
+ }
@@ -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 {
@@ -29,10 +41,15 @@ export function resolveProjectConfig(
29
41
  return resolveRealPath(found);
30
42
  }
31
43
 
44
+ /** Resolve `target` against `cwd` when it is not already absolute. */
32
45
  function resolveAbsolutePath(cwd: string, target: string): string {
33
46
  return path.isAbsolute(target) ? target : path.resolve(cwd, target);
34
47
  }
35
48
 
49
+ /**
50
+ * Resolve symlinks on `location`, returning the original path when
51
+ * `realpathSync` fails (e.g. when the file does not yet exist).
52
+ */
36
53
  function resolveRealPath(location: string): string {
37
54
  try {
38
55
  return fs.realpathSync(location);
@@ -41,6 +58,11 @@ function resolveRealPath(location: string): string {
41
58
  }
42
59
  }
43
60
 
61
+ /**
62
+ * Walk up the directory tree from `from`, returning the first directory that
63
+ * contains a file whose name is in `names`. Returns `null` when the filesystem
64
+ * root is reached without finding a match.
65
+ */
44
66
  function findUp(from: string, names: readonly string[]): string | null {
45
67
  let current = path.resolve(from);
46
68
  while (true) {
@@ -58,6 +80,7 @@ function findUp(from: string, names: readonly string[]): string | null {
58
80
  }
59
81
  }
60
82
 
83
+ /** Return true when `location` exists and is a directory. */
61
84
  function isDirectory(location: string): boolean {
62
85
  try {
63
86
  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
  }