ttsc 0.19.1 → 0.19.2

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 (37) hide show
  1. package/README.md +68 -186
  2. package/cmd/graphdump/main.go +21 -1
  3. package/cmd/ttsc/api_compile.go +3 -13
  4. package/cmd/ttsc/api_transform.go +13 -0
  5. package/cmd/ttscgraph/dump.go +40 -1
  6. package/cmd/ttscgraph/serve.go +194 -25
  7. package/cmd/ttscgraph/serve_error_response_still_names_its_mode_test.go +73 -0
  8. package/cmd/ttscgraph/serve_protocol_fails_the_stream_on_an_unaddressable_request_line_test.go +43 -0
  9. package/cmd/ttscgraph/serve_protocol_version_matches_the_typescript_client_test.go +48 -0
  10. package/cmd/ttscgraph/serve_snapshot_proves_its_program_without_a_second_read_test.go +109 -0
  11. package/driver/graph.go +133 -0
  12. package/internal/graph/diagnostics.go +6 -5
  13. package/internal/graph/dump.go +69 -17
  14. package/internal/graph/dump_diagnostics_ride_the_generation_that_built_it_test.go +79 -0
  15. package/internal/graph/dump_evidence_skips_leading_trivia_test.go +1 -1
  16. package/internal/graph/dump_is_a_function_of_the_source_test.go +1 -1
  17. package/internal/graph/dump_never_serializes_a_list_as_null_test.go +76 -0
  18. package/internal/graph/dump_schema_version_matches_the_typescript_client_test.go +48 -0
  19. package/internal/graph/dump_serializes_the_full_graph_test.go +21 -7
  20. package/internal/graph/node_modifiers_emit_union_strings_test.go +1 -1
  21. package/internal/graph/provenance.go +312 -0
  22. package/lib/TtscCompiler.js +9 -4
  23. package/lib/TtscCompiler.js.map +1 -1
  24. package/lib/compiler/internal/transformProjectInMemory.d.ts +4 -0
  25. package/lib/compiler/internal/transformProjectInMemory.js +67 -12
  26. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  27. package/lib/structures/ITtscCompilerTransformation.d.ts +98 -0
  28. package/package.json +8 -8
  29. package/shim/compiler/incremental.go +58 -0
  30. package/shim/core/shim.go +5 -0
  31. package/src/TtscCompiler.ts +19 -4
  32. package/src/compiler/internal/transformProjectInMemory.ts +106 -12
  33. package/src/structures/ITtscCompilerTransformation.ts +107 -0
  34. package/tools/shim_audit/baseline.json +2 -1
  35. package/tools/shim_audit/main.go +17 -0
  36. package/utility/host.go +17 -15
  37. package/cmd/ttscgraph/serve_protocol_rejects_invalid_request_line_test.go +0 -43
@@ -9,6 +9,39 @@ import type { ITtscCompilerDiagnostic } from "./ITtscCompilerDiagnostic";
9
9
  */
10
10
  export type ITtscCompilerTransformation = ITtscCompilerTransformation.ISuccess | ITtscCompilerTransformation.IFailure | ITtscCompilerTransformation.IException;
11
11
  export declare namespace ITtscCompilerTransformation {
12
+ /**
13
+ * Host-owned reference graph of the transformed program, mirroring the
14
+ * envelope's optional `graph` section.
15
+ *
16
+ * The graph is the language-semantic input bound of the transform under `tsc
17
+ * --incremental` semantics: any symbol a file can reference is reachable
18
+ * through its import/reference closure or is ambient. Bundler adapters
19
+ * register, per transformed file `F`, the reachability closure of
20
+ * {@link edges} from `F` together with {@link globals} and {@link configs}, so
21
+ * persistent caches and watch graphs invalidate soundly without per-plugin
22
+ * dependency reporting.
23
+ *
24
+ * Keys and values follow the same convention as {@link ISuccess.typescript}:
25
+ * project-relative slash paths, falling back to absolute slash paths outside
26
+ * the project root.
27
+ */
28
+ interface IReferenceGraph {
29
+ /**
30
+ * Direct resolved references per file: imports, re-exports, `///
31
+ * <reference>` targets, and type reference directives — type-only edges
32
+ * included. Direct edges only; consumers compute transitive reachability
33
+ * themselves.
34
+ */
35
+ edges: Record<string, string[]>;
36
+ /**
37
+ * Files contributing to the global scope (ambient declaration files, script
38
+ * files, global augmentations, `typeRoots` entries). A change to any of
39
+ * them can affect every file in the program.
40
+ */
41
+ globals: string[];
42
+ /** The project tsconfig followed by its `extends` ancestry. */
43
+ configs: string[];
44
+ }
12
45
  /** Successful source-to-source transformation result. */
13
46
  interface ISuccess {
14
47
  /** Indicates that transformation completed without diagnostics. */
@@ -36,6 +69,54 @@ export declare namespace ITtscCompilerTransformation {
36
69
  * invalidation. ttsc passes the paths through verbatim.
37
70
  */
38
71
  dependencies?: Record<string, string[]>;
72
+ /**
73
+ * Transformed files (keyed like {@link typescript}) whose
74
+ * {@link dependencies} entry the transform host declares **complete**: every
75
+ * input beyond the file itself and the universal
76
+ * {@link IReferenceGraph.configs} chain is listed there.
77
+ *
78
+ * The declaration narrows invalidation. For a listed file a consumer uses
79
+ * `dependencies[F] ∪ graph.configs` instead of the union with the
80
+ * host-owned `reach(graph.edges, F) ∪ graph.globals` bound, so a change to
81
+ * a file the transform never consulted no longer re-runs it. Unlisted files
82
+ * keep the union, so a mixed envelope composes per file.
83
+ *
84
+ * This is a responsibility transfer, not a hint: an omission makes the
85
+ * consumer serve stale output, and that is a defect of the declaring plugin
86
+ * rather than of the host. Producers list a file only when the reported set
87
+ * is derived from what the transform actually consulted (a Checker-driven
88
+ * generator's per-file consulted-declaration list). When several plugin
89
+ * entries contribute to one file, the envelope's author may list it only if
90
+ * every contributing entry declared its own list complete for it.
91
+ *
92
+ * The config chain a listed file keeps is {@link graph}'s, so a host that
93
+ * declares completeness should stamp {@link graph} too; without it a listed
94
+ * file retains no universal input at all.
95
+ *
96
+ * Optional, and never required for correctness: an envelope without this
97
+ * field keeps the sound host-owned bound. A file that is both listed here
98
+ * and in {@link volatile} keeps the union, since the two claims contradict
99
+ * and the conservative one wins.
100
+ */
101
+ dependenciesComplete?: string[];
102
+ /**
103
+ * Host-owned reference graph of the transformed program.
104
+ *
105
+ * Optional: only present when the transform host stamped a `graph` section
106
+ * into its stdout envelope (the built-in native host and the linked-plugin
107
+ * host always do; external sidecars adopt through the driver SDK).
108
+ * Malformed sections are dropped, never fatal — the field is advisory
109
+ * invalidation metadata, not output.
110
+ */
111
+ graph?: IReferenceGraph;
112
+ /**
113
+ * Transformed files (keyed like {@link typescript}) whose output depends on
114
+ * non-file inputs (environment, time, network) as declared by the transform
115
+ * plugin via the envelope's optional `volatile` list. No file-dependency
116
+ * scheme can represent such inputs, so consumers must exclude these files
117
+ * from caching instead of watching more files.
118
+ */
119
+ volatile?: string[];
39
120
  }
40
121
  /** Source-to-source transformation result that completed with diagnostics. */
41
122
  interface IFailure {
@@ -57,6 +138,23 @@ export declare namespace ITtscCompilerTransformation {
57
138
  * transform did not complete its pass.
58
139
  */
59
140
  dependencies?: Record<string, string[]>;
141
+ /**
142
+ * Files whose reported dependency list the host declares complete. Same
143
+ * shape and semantics as {@link ISuccess.dependenciesComplete}; may be
144
+ * partial when the transform did not complete its pass.
145
+ */
146
+ dependenciesComplete?: string[];
147
+ /**
148
+ * Host-owned reference graph. Same shape and semantics as
149
+ * {@link ISuccess.graph}; may be absent when diagnostics prevented the host
150
+ * from loading the program.
151
+ */
152
+ graph?: IReferenceGraph;
153
+ /**
154
+ * Volatile transformed files. Same shape and semantics as
155
+ * {@link ISuccess.volatile}.
156
+ */
157
+ volatile?: string[];
60
158
  }
61
159
  /** Unexpected host-level error during transformation. */
62
160
  interface IException {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ttsc",
3
- "version": "0.19.1",
3
+ "version": "0.19.2",
4
4
  "description": "General-purpose TypeScript-Go compiler, runtime, plugin host, and LSP host.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -35,13 +35,13 @@
35
35
  "typescript": "^7.0.2"
36
36
  },
37
37
  "optionalDependencies": {
38
- "@ttsc/linux-x64": "0.19.1",
39
- "@ttsc/linux-arm": "0.19.1",
40
- "@ttsc/linux-arm64": "0.19.1",
41
- "@ttsc/darwin-x64": "0.19.1",
42
- "@ttsc/darwin-arm64": "0.19.1",
43
- "@ttsc/win32-arm64": "0.19.1",
44
- "@ttsc/win32-x64": "0.19.1"
38
+ "@ttsc/linux-x64": "0.19.2",
39
+ "@ttsc/linux-arm": "0.19.2",
40
+ "@ttsc/linux-arm64": "0.19.2",
41
+ "@ttsc/darwin-x64": "0.19.2",
42
+ "@ttsc/win32-x64": "0.19.2",
43
+ "@ttsc/win32-arm64": "0.19.2",
44
+ "@ttsc/darwin-arm64": "0.19.2"
45
45
  },
46
46
  "sideEffects": false,
47
47
  "files": [
@@ -0,0 +1,58 @@
1
+ // gen_shims:hand-maintained
2
+ //
3
+ // Reference-graph parts of tsgo's own incremental engine (`tsc --incremental`
4
+ // semantics), linked from internal/execute/incremental so ttsc's transform
5
+ // envelopes can carry the same language-semantic input bound the compiler
6
+ // itself uses for invalidation: per-file direct resolved references and the
7
+ // files that contribute to the global scope.
8
+ package compiler
9
+
10
+ import (
11
+ _ "unsafe"
12
+
13
+ innerast "github.com/microsoft/typescript-go/internal/ast"
14
+ "github.com/microsoft/typescript-go/internal/collections"
15
+ innercompiler "github.com/microsoft/typescript-go/internal/compiler"
16
+ "github.com/microsoft/typescript-go/internal/tspath"
17
+
18
+ // The linknamed symbols below live in the incremental package; the blank
19
+ // import compiles it into every shim consumer so the references resolve.
20
+ _ "github.com/microsoft/typescript-go/internal/execute/incremental"
21
+ )
22
+
23
+ //go:linkname incrementalGetReferencedFiles github.com/microsoft/typescript-go/internal/execute/incremental.getReferencedFiles
24
+ func incrementalGetReferencedFiles(program *innercompiler.Program, file *innerast.SourceFile) *collections.Set[tspath.Path]
25
+
26
+ //go:linkname incrementalFileAffectsGlobalScope github.com/microsoft/typescript-go/internal/execute/incremental.fileAffectsGlobalScope
27
+ func incrementalFileAffectsGlobalScope(file *innerast.SourceFile) bool
28
+
29
+ // GetReferencedFilePaths returns the canonical paths of every file that `file`
30
+ // directly references in `program`: resolved imports and re-exports (type-only
31
+ // included), `/// <reference>` targets, resolved type reference directives,
32
+ // module augmentations, and ambient-module declaration files. This is exactly
33
+ // the per-file `referencedMap` entry tsgo's incremental engine stores in
34
+ // `tsbuildinfo`, so the result is the sound language-semantic upper bound on
35
+ // which program files a symbol in `file` can resolve through.
36
+ //
37
+ // The returned strings are tspath.Path values (case-canonicalized on
38
+ // case-insensitive filesystems); map them back to real file names through
39
+ // Program.GetSourceFileByPath when the original spelling matters.
40
+ func GetReferencedFilePaths(program *Program, file *innerast.SourceFile) []string {
41
+ set := incrementalGetReferencedFiles(program, file)
42
+ if set == nil {
43
+ return nil
44
+ }
45
+ out := make([]string, 0, set.Len())
46
+ for path := range set.Keys() {
47
+ out = append(out, string(path))
48
+ }
49
+ return out
50
+ }
51
+
52
+ // FileAffectsGlobalScope reports whether editing `file` can change the global
53
+ // scope: global-scope module augmentations, ambient declaration files, and
54
+ // script (non-module) files. Mirrors the predicate tsgo's incremental engine
55
+ // uses to decide that a change must invalidate every file in the program.
56
+ func FileAffectsGlobalScope(file *innerast.SourceFile) bool {
57
+ return incrementalFileAffectsGlobalScope(file)
58
+ }
package/shim/core/shim.go CHANGED
@@ -50,6 +50,11 @@ const (
50
50
  JsxEmitReactJSXDev = innercore.JsxEmitReactJSXDev
51
51
  )
52
52
 
53
+ // Version reports the TypeScript compiler version typescript-go implements, in
54
+ // the same form `tsc --version` prints. A graph snapshot publishes it so a
55
+ // consumer can tell which checker resolved the facts it is reading.
56
+ func Version() string { return innercore.Version() }
57
+
53
58
  // NewTextRange constructs a closed/open [pos, end) text range.
54
59
  func NewTextRange(pos, end int) TextRange { return innercore.NewTextRange(pos, end) }
55
60
 
@@ -259,8 +259,11 @@ interface ProjectResult {
259
259
 
260
260
  interface ProjectTransformation {
261
261
  dependencies?: Record<string, string[]>;
262
+ dependenciesComplete?: string[];
263
+ graph?: ITtscCompilerTransformation.IReferenceGraph;
262
264
  result: TtscBuildResult;
263
265
  typescript: Record<string, string>;
266
+ volatile?: string[];
264
267
  }
265
268
 
266
269
  function runProject(task: () => ProjectResult): ITtscCompilerResult {
@@ -371,20 +374,32 @@ function toCompilerResult(project: ProjectResult): ITtscCompilerResult {
371
374
  function toCompilerTransformation(
372
375
  project: ProjectTransformation,
373
376
  ): ITtscCompilerTransformation {
374
- const { dependencies, result, typescript } = project;
375
- const dependencyField = dependencies === undefined ? {} : { dependencies };
377
+ const {
378
+ dependencies,
379
+ dependenciesComplete,
380
+ graph,
381
+ result,
382
+ typescript,
383
+ volatile,
384
+ } = project;
385
+ const advisoryFields = {
386
+ ...(dependencies === undefined ? {} : { dependencies }),
387
+ ...(dependenciesComplete === undefined ? {} : { dependenciesComplete }),
388
+ ...(graph === undefined ? {} : { graph }),
389
+ ...(volatile === undefined ? {} : { volatile }),
390
+ };
376
391
  if (result.status === 0 && !hasErrorDiagnostics(result.diagnostics)) {
377
392
  return {
378
393
  ...(result.diagnostics.length === 0
379
394
  ? {}
380
395
  : { diagnostics: result.diagnostics }),
381
- ...dependencyField,
396
+ ...advisoryFields,
382
397
  type: "success",
383
398
  typescript,
384
399
  };
385
400
  }
386
401
  return {
387
- ...dependencyField,
402
+ ...advisoryFields,
388
403
  diagnostics:
389
404
  result.diagnostics.length === 0
390
405
  ? [createProcessDiagnostic(result)]
@@ -6,6 +6,7 @@ import {
6
6
  } from "../../plugin/internal/loadProjectPlugins";
7
7
  import type { ITtscCompilerContext } from "../../structures/ITtscCompilerContext";
8
8
  import type { ITtscCompilerDiagnostic } from "../../structures/ITtscCompilerDiagnostic";
9
+ import type { ITtscCompilerTransformation } from "../../structures/ITtscCompilerTransformation";
9
10
  import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
10
11
  import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscParsedProjectConfig";
11
12
  import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
@@ -41,8 +42,11 @@ import { outputText, spawnNative } from "./spawnNative";
41
42
  */
42
43
  export function transformProjectInMemory(options: ITtscCompilerContext): {
43
44
  dependencies?: Record<string, string[]>;
45
+ dependenciesComplete?: string[];
46
+ graph?: ITtscCompilerTransformation.IReferenceGraph;
44
47
  result: TtscBuildResult;
45
48
  typescript: Record<string, string>;
49
+ volatile?: string[];
46
50
  } {
47
51
  const cwd = path.resolve(options.cwd ?? process.cwd());
48
52
  const project = readProjectConfig({
@@ -74,8 +78,11 @@ function transformProjectWithNativeHost(
74
78
  project: ITtscParsedProjectConfig,
75
79
  ): {
76
80
  dependencies?: Record<string, string[]>;
81
+ dependenciesComplete?: string[];
82
+ graph?: ITtscCompilerTransformation.IReferenceGraph;
77
83
  result: TtscBuildResult;
78
84
  typescript: Record<string, string>;
85
+ volatile?: string[];
79
86
  } {
80
87
  const binary = buildNativeCompiler({
81
88
  cacheBaseDir: project.root,
@@ -101,9 +108,7 @@ function transformProjectWithNativeHost(
101
108
  outputText(res.stderr),
102
109
  );
103
110
  return {
104
- ...(output.dependencies === undefined
105
- ? {}
106
- : { dependencies: output.dependencies }),
111
+ ...envelopeSideChannels(output),
107
112
  result: {
108
113
  diagnostics: output.diagnostics,
109
114
  status: res.status ?? 1,
@@ -120,8 +125,11 @@ function transformProjectWithPlugins(
120
125
  project: ITtscParsedProjectConfig,
121
126
  ): {
122
127
  dependencies?: Record<string, string[]>;
128
+ dependenciesComplete?: string[];
129
+ graph?: ITtscCompilerTransformation.IReferenceGraph;
123
130
  result: TtscBuildResult;
124
131
  typescript: Record<string, string>;
132
+ volatile?: string[];
125
133
  } {
126
134
  const loaded = loadProjectPlugins({
127
135
  binary: resolveBinary(options) ?? "",
@@ -159,9 +167,7 @@ function transformProjectWithPlugins(
159
167
  if (transformers.length === 0) {
160
168
  const transformed = transformProjectWithNativeHost(options, project);
161
169
  return {
162
- ...(transformed.dependencies === undefined
163
- ? {}
164
- : { dependencies: transformed.dependencies }),
170
+ ...envelopeSideChannels(transformed),
165
171
  result: appendBuildOutput(checked, transformed.result),
166
172
  typescript: transformed.typescript,
167
173
  };
@@ -196,12 +202,39 @@ function transformProjectWithPlugins(
196
202
  stdout: "",
197
203
  stderr: outputText(res.stderr),
198
204
  };
205
+ return {
206
+ ...envelopeSideChannels(output),
207
+ result: appendBuildOutput(checked, result),
208
+ typescript: output.typescript,
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Collect the optional advisory envelope fields (`dependencies`,
214
+ * `dependenciesComplete`, `graph`, `volatile`) into a spreadable object,
215
+ * omitting absent fields so downstream result shapes stay free of `undefined`
216
+ * keys.
217
+ */
218
+ function envelopeSideChannels(output: {
219
+ dependencies?: Record<string, string[]>;
220
+ dependenciesComplete?: string[];
221
+ graph?: ITtscCompilerTransformation.IReferenceGraph;
222
+ volatile?: string[];
223
+ }): {
224
+ dependencies?: Record<string, string[]>;
225
+ dependenciesComplete?: string[];
226
+ graph?: ITtscCompilerTransformation.IReferenceGraph;
227
+ volatile?: string[];
228
+ } {
199
229
  return {
200
230
  ...(output.dependencies === undefined
201
231
  ? {}
202
232
  : { dependencies: output.dependencies }),
203
- result: appendBuildOutput(checked, result),
204
- typescript: output.typescript,
233
+ ...(output.dependenciesComplete === undefined
234
+ ? {}
235
+ : { dependenciesComplete: output.dependenciesComplete }),
236
+ ...(output.graph === undefined ? {} : { graph: output.graph }),
237
+ ...(output.volatile === undefined ? {} : { volatile: output.volatile }),
205
238
  };
206
239
  }
207
240
 
@@ -368,24 +401,34 @@ function nativePluginEnv(
368
401
  * treated as a protocol error and throws with the stderr/stdout context. JSON
369
402
  * parse errors are also wrapped with the same context message.
370
403
  *
371
- * The optional `dependencies` field (per-file consulted-source lists, see
372
- * `ITtscCompilerTransformation`) is forwarded when well-formed; entries that
373
- * are not string arrays are dropped rather than failing the transform — the
374
- * field is advisory watch metadata, not output.
404
+ * The optional `dependencies`, `dependenciesComplete`, `graph`, and `volatile`
405
+ * fields (see `ITtscCompilerTransformation`) are forwarded when well-formed;
406
+ * entries that do not match the expected shape are dropped rather than failing
407
+ * the transform — the fields are advisory invalidation metadata, not output.
408
+ *
409
+ * Dropping a malformed `dependenciesComplete` member is the safe direction on
410
+ * purpose: an unlisted file keeps the sound host-owned bound, so a garbled
411
+ * declaration costs over-invalidation, never a stale output.
375
412
  */
376
413
  function parseNativeTransformOutput(
377
414
  stdout: string,
378
415
  stderr: string,
379
416
  ): {
380
417
  dependencies?: Record<string, string[]>;
418
+ dependenciesComplete?: string[];
381
419
  diagnostics: ITtscCompilerDiagnostic[];
420
+ graph?: ITtscCompilerTransformation.IReferenceGraph;
382
421
  typescript: Record<string, string>;
422
+ volatile?: string[];
383
423
  } {
384
424
  try {
385
425
  const parsed = JSON.parse(stdout) as {
386
426
  dependencies?: Record<string, string[]>;
427
+ dependenciesComplete?: string[];
387
428
  diagnostics?: ITtscCompilerDiagnostic[];
429
+ graph?: ITtscCompilerTransformation.IReferenceGraph;
388
430
  typescript?: Record<string, string>;
431
+ volatile?: string[];
389
432
  };
390
433
  if (!isTextRecord(parsed.typescript)) {
391
434
  throw new Error(
@@ -393,8 +436,14 @@ function parseNativeTransformOutput(
393
436
  );
394
437
  }
395
438
  const dependencies = parseDependencyLists(parsed.dependencies);
439
+ const dependenciesComplete = parseFileList(parsed.dependenciesComplete);
440
+ const graph = parseReferenceGraph(parsed.graph);
441
+ const volatile = parseFileList(parsed.volatile);
396
442
  return {
397
443
  ...(dependencies === undefined ? {} : { dependencies }),
444
+ ...(dependenciesComplete === undefined ? {} : { dependenciesComplete }),
445
+ ...(graph === undefined ? {} : { graph }),
446
+ ...(volatile === undefined ? {} : { volatile }),
398
447
  diagnostics: Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [],
399
448
  typescript: parsed.typescript,
400
449
  };
@@ -434,6 +483,51 @@ function parseDependencyLists(
434
483
  return Object.keys(output).length === 0 ? undefined : output;
435
484
  }
436
485
 
486
+ /**
487
+ * Normalize the optional `graph` envelope section with the same tolerance as
488
+ * `dependencies`: non-object sections are dropped, edge entries that are not
489
+ * string arrays are dropped, and non-string list members are filtered. A
490
+ * section carrying nothing usable collapses to `undefined`.
491
+ */
492
+ function parseReferenceGraph(
493
+ value: unknown,
494
+ ): ITtscCompilerTransformation.IReferenceGraph | undefined {
495
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
496
+ return undefined;
497
+ }
498
+ const section = value as {
499
+ configs?: unknown;
500
+ edges?: unknown;
501
+ globals?: unknown;
502
+ };
503
+ const edges = parseDependencyLists(section.edges) ?? {};
504
+ const globals = parseFileList(section.globals) ?? [];
505
+ const configs = parseFileList(section.configs) ?? [];
506
+ if (
507
+ Object.keys(edges).length === 0 &&
508
+ globals.length === 0 &&
509
+ configs.length === 0
510
+ ) {
511
+ return undefined;
512
+ }
513
+ return { configs, edges, globals };
514
+ }
515
+
516
+ /**
517
+ * Normalize an optional string-list envelope field (`dependenciesComplete`,
518
+ * `volatile`, and the `globals`/`configs` graph sections), or `undefined` when
519
+ * absent or carrying nothing usable.
520
+ */
521
+ function parseFileList(value: unknown): string[] | undefined {
522
+ if (!Array.isArray(value)) {
523
+ return undefined;
524
+ }
525
+ const files = value.filter(
526
+ (entry): entry is string => typeof entry === "string" && entry.length !== 0,
527
+ );
528
+ return files.length === 0 ? undefined : files;
529
+ }
530
+
437
531
  /** Type guard: true when `value` is a non-null, non-array object of strings. */
438
532
  function isTextRecord(value: unknown): value is Record<string, string> {
439
533
  return (
@@ -14,6 +14,42 @@ export type ITtscCompilerTransformation =
14
14
  | ITtscCompilerTransformation.IException;
15
15
 
16
16
  export namespace ITtscCompilerTransformation {
17
+ /**
18
+ * Host-owned reference graph of the transformed program, mirroring the
19
+ * envelope's optional `graph` section.
20
+ *
21
+ * The graph is the language-semantic input bound of the transform under `tsc
22
+ * --incremental` semantics: any symbol a file can reference is reachable
23
+ * through its import/reference closure or is ambient. Bundler adapters
24
+ * register, per transformed file `F`, the reachability closure of
25
+ * {@link edges} from `F` together with {@link globals} and {@link configs}, so
26
+ * persistent caches and watch graphs invalidate soundly without per-plugin
27
+ * dependency reporting.
28
+ *
29
+ * Keys and values follow the same convention as {@link ISuccess.typescript}:
30
+ * project-relative slash paths, falling back to absolute slash paths outside
31
+ * the project root.
32
+ */
33
+ export interface IReferenceGraph {
34
+ /**
35
+ * Direct resolved references per file: imports, re-exports, `///
36
+ * <reference>` targets, and type reference directives — type-only edges
37
+ * included. Direct edges only; consumers compute transitive reachability
38
+ * themselves.
39
+ */
40
+ edges: Record<string, string[]>;
41
+
42
+ /**
43
+ * Files contributing to the global scope (ambient declaration files, script
44
+ * files, global augmentations, `typeRoots` entries). A change to any of
45
+ * them can affect every file in the program.
46
+ */
47
+ globals: string[];
48
+
49
+ /** The project tsconfig followed by its `extends` ancestry. */
50
+ configs: string[];
51
+ }
52
+
17
53
  /** Successful source-to-source transformation result. */
18
54
  export interface ISuccess {
19
55
  /** Indicates that transformation completed without diagnostics. */
@@ -44,6 +80,57 @@ export namespace ITtscCompilerTransformation {
44
80
  * invalidation. ttsc passes the paths through verbatim.
45
81
  */
46
82
  dependencies?: Record<string, string[]>;
83
+
84
+ /**
85
+ * Transformed files (keyed like {@link typescript}) whose
86
+ * {@link dependencies} entry the transform host declares **complete**: every
87
+ * input beyond the file itself and the universal
88
+ * {@link IReferenceGraph.configs} chain is listed there.
89
+ *
90
+ * The declaration narrows invalidation. For a listed file a consumer uses
91
+ * `dependencies[F] ∪ graph.configs` instead of the union with the
92
+ * host-owned `reach(graph.edges, F) ∪ graph.globals` bound, so a change to
93
+ * a file the transform never consulted no longer re-runs it. Unlisted files
94
+ * keep the union, so a mixed envelope composes per file.
95
+ *
96
+ * This is a responsibility transfer, not a hint: an omission makes the
97
+ * consumer serve stale output, and that is a defect of the declaring plugin
98
+ * rather than of the host. Producers list a file only when the reported set
99
+ * is derived from what the transform actually consulted (a Checker-driven
100
+ * generator's per-file consulted-declaration list). When several plugin
101
+ * entries contribute to one file, the envelope's author may list it only if
102
+ * every contributing entry declared its own list complete for it.
103
+ *
104
+ * The config chain a listed file keeps is {@link graph}'s, so a host that
105
+ * declares completeness should stamp {@link graph} too; without it a listed
106
+ * file retains no universal input at all.
107
+ *
108
+ * Optional, and never required for correctness: an envelope without this
109
+ * field keeps the sound host-owned bound. A file that is both listed here
110
+ * and in {@link volatile} keeps the union, since the two claims contradict
111
+ * and the conservative one wins.
112
+ */
113
+ dependenciesComplete?: string[];
114
+
115
+ /**
116
+ * Host-owned reference graph of the transformed program.
117
+ *
118
+ * Optional: only present when the transform host stamped a `graph` section
119
+ * into its stdout envelope (the built-in native host and the linked-plugin
120
+ * host always do; external sidecars adopt through the driver SDK).
121
+ * Malformed sections are dropped, never fatal — the field is advisory
122
+ * invalidation metadata, not output.
123
+ */
124
+ graph?: IReferenceGraph;
125
+
126
+ /**
127
+ * Transformed files (keyed like {@link typescript}) whose output depends on
128
+ * non-file inputs (environment, time, network) as declared by the transform
129
+ * plugin via the envelope's optional `volatile` list. No file-dependency
130
+ * scheme can represent such inputs, so consumers must exclude these files
131
+ * from caching instead of watching more files.
132
+ */
133
+ volatile?: string[];
47
134
  }
48
135
 
49
136
  /** Source-to-source transformation result that completed with diagnostics. */
@@ -69,6 +156,26 @@ export namespace ITtscCompilerTransformation {
69
156
  * transform did not complete its pass.
70
157
  */
71
158
  dependencies?: Record<string, string[]>;
159
+
160
+ /**
161
+ * Files whose reported dependency list the host declares complete. Same
162
+ * shape and semantics as {@link ISuccess.dependenciesComplete}; may be
163
+ * partial when the transform did not complete its pass.
164
+ */
165
+ dependenciesComplete?: string[];
166
+
167
+ /**
168
+ * Host-owned reference graph. Same shape and semantics as
169
+ * {@link ISuccess.graph}; may be absent when diagnostics prevented the host
170
+ * from loading the program.
171
+ */
172
+ graph?: IReferenceGraph;
173
+
174
+ /**
175
+ * Volatile transformed files. Same shape and semantics as
176
+ * {@link ISuccess.volatile}.
177
+ */
178
+ volatile?: string[];
72
179
  }
73
180
 
74
181
  /** Unexpected host-level error during transformation. */
@@ -669,7 +669,6 @@
669
669
  "FUNC|core|ResolveConfigFileNameOfProjectReference",
670
670
  "FUNC|core|ShouldRewriteModuleSpecifier",
671
671
  "FUNC|core|UTF16Len",
672
- "FUNC|core|Version",
673
672
  "FUNC|core|VersionMajorMinor",
674
673
  "FUNC|core|WithRequestID",
675
674
  "FUNC|diagnosticwriter|FormatDiagnosticWithColorAndContext",
@@ -678,6 +677,8 @@
678
677
  "FUNC|diagnosticwriter|TryClearScreen",
679
678
  "FUNC|diagnosticwriter|WriteFormatDiagnostic",
680
679
  "FUNC|diagnosticwriter|WriteFormatDiagnostics",
680
+ "FUNC|execute/incremental|ComputeHash",
681
+ "FUNC|execute/incremental|GetMTime",
681
682
  "FUNC|printer|FormatGeneratedName",
682
683
  "FUNC|printer|GetDefaultIndentSize",
683
684
  "FUNC|printer|GetEmitContext",
@@ -90,6 +90,19 @@ var shimDirs = map[string]string{
90
90
  "vfs/osvfs": "vfs/osvfs",
91
91
  }
92
92
 
93
+ // auditOnlyInternal lists upstream internal packages that shim sources
94
+ // reference — go:linkname targets and blank imports that compile them into
95
+ // consumer binaries — without owning a shim directory of their own. They are
96
+ // loaded for type information so the shim import scan can resolve them; their
97
+ // exported surface then enters the ratcheted reachable-gap scan like any other
98
+ // loaded package, so unneeded helpers are accepted into baseline.json rather
99
+ // than silently skipped.
100
+ var auditOnlyInternal = []string{
101
+ // shim/compiler/incremental.go linknames the incremental engine's
102
+ // reference-graph functions (getReferencedFiles, fileAffectsGlobalScope).
103
+ "execute/incremental",
104
+ }
105
+
93
106
  // reachable holds, per upstream package suffix, the set of upstream symbol names
94
107
  // the shim makes nameable (selector references + linkname targets).
95
108
  type reachable map[string]map[string]bool
@@ -525,6 +538,10 @@ func loadInner(anchorDir string) (map[string]*packages.Package, error) {
525
538
  full = append(full, internalPrefix+suffix)
526
539
  expected[suffix] = true
527
540
  }
541
+ for _, suffix := range auditOnlyInternal {
542
+ full = append(full, internalPrefix+suffix)
543
+ expected[suffix] = true
544
+ }
528
545
  loaded, err := packages.Load(&packages.Config{
529
546
  Dir: anchorDir,
530
547
  Mode: packages.LoadTypes | packages.NeedDeps | packages.NeedImports,