ttsc 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +66 -63
  3. package/cmd/ttscgraph/serve.go +264 -48
  4. package/cmd/ttscgraph/serve_answers_a_project_that_implements_its_own_ambient_declaration_test.go +69 -0
  5. package/cmd/ttscgraph/serve_early_error_encode_failure_is_observable_test.go +40 -0
  6. package/cmd/ttscgraph/serve_graph_snapshot_version_matches_typescript_client_test.go +32 -0
  7. package/cmd/ttscgraph/serve_phase_trace_accounts_for_load_failure_test.go +45 -0
  8. package/cmd/ttscgraph/serve_phase_trace_is_opt_in_and_payload_free_test.go +52 -0
  9. package/cmd/ttscgraph/serve_protocol_negotiates_incremental_shards_test.go +44 -0
  10. package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +2 -2
  11. package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +46 -20
  12. package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +9 -9
  13. package/cmd/ttscgraph/serve_shards.go +1390 -0
  14. package/cmd/ttscgraph/serve_shards_abandoned_generation_converges_on_retry_test.go +92 -0
  15. package/cmd/ttscgraph/serve_shards_delete_removed_source_test.go +57 -0
  16. package/cmd/ttscgraph/serve_shards_external_endpoints_test.go +53 -0
  17. package/cmd/ttscgraph/serve_shards_keep_source_distributed_dependencies_at_the_boundary_test.go +108 -0
  18. package/cmd/ttscgraph/serve_shards_match_full_dump_oracle_test.go +88 -0
  19. package/cmd/ttscgraph/serve_shards_merge_pending_and_new_edits_test.go +50 -0
  20. package/cmd/ttscgraph/serve_shards_prune_unreferenced_external_endpoints_test.go +65 -0
  21. package/cmd/ttscgraph/serve_shards_publish_created_source_test.go +92 -0
  22. package/cmd/ttscgraph/serve_shards_rebuild_declaration_edit_test.go +43 -0
  23. package/cmd/ttscgraph/serve_shards_refresh_cross_file_implementation_test.go +63 -0
  24. package/cmd/ttscgraph/serve_shards_refresh_dependent_diagnostics_test.go +80 -0
  25. package/cmd/ttscgraph/serve_shards_rename_moves_source_identity_test.go +95 -0
  26. package/cmd/ttscgraph/serve_shards_retry_preserves_committed_generation_test.go +48 -0
  27. package/cmd/ttscgraph/serve_shards_reuse_unaffected_sources_test.go +121 -0
  28. package/driver/declaration_shape.go +85 -0
  29. package/driver/program.go +39 -10
  30. package/go.sum +22 -22
  31. package/internal/graph/assigned_implementations_of_declaration_file_symbols_leave_no_edge_source_test.go +84 -0
  32. package/internal/graph/assigned_implementations_of_node_modules_symbols_leave_no_edge_source_test.go +85 -0
  33. package/internal/graph/build.go +62 -10
  34. package/internal/graph/build_files_reuses_committed_cross_file_endpoints_test.go +87 -0
  35. package/internal/graph/build_keeps_source_distributed_dependencies_at_the_external_boundary_test.go +74 -0
  36. package/internal/graph/dump.go +84 -43
  37. package/internal/graph/dump_path_mapper_caches_alias_resolution_test.go +29 -0
  38. package/internal/graph/dump_paths.go +134 -11
  39. package/internal/graph/dump_paths_canonicalize_windows_short_root_test.go +39 -0
  40. package/internal/graph/dump_rejects_provenance_only_path_errors_test.go +32 -0
  41. package/internal/graph/dump_serializes_the_full_graph_test.go +2 -2
  42. package/internal/graph/dump_symlink_project_publishes_matching_base_test.go +82 -0
  43. package/internal/graph/edges.go +73 -17
  44. package/internal/graph/exports.go +9 -2
  45. package/internal/graph/graph.go +47 -7
  46. package/internal/graph/graph_kind_contracts_match_their_producers_test.go +121 -0
  47. package/internal/graph/node_modifiers_emit_union_strings_test.go +1 -1
  48. package/internal/graph/provenance.go +11 -1
  49. package/internal/graph/resolve.go +1 -4
  50. package/internal/graph/source.go +23 -0
  51. package/internal/graph/wire_node_ids_share_alias_resolution_test.go +29 -0
  52. package/internal/lspserver/lsp_native_plugin_source.go +5 -6
  53. package/internal/lspserver/lsp_native_resident.go +17 -24
  54. package/lib/TtscService.d.ts +1 -6
  55. package/lib/TtscService.js +2 -2
  56. package/lib/TtscService.js.map +1 -1
  57. package/lib/compiler/internal/captureProcessOutput.d.ts +26 -0
  58. package/lib/compiler/internal/captureProcessOutput.js +92 -0
  59. package/lib/compiler/internal/captureProcessOutput.js.map +1 -0
  60. package/lib/compiler/internal/compileProjectInMemory.js +4 -7
  61. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  62. package/lib/compiler/internal/residentCheckProcess.d.ts +0 -2
  63. package/lib/compiler/internal/residentCheckProcess.js +1 -15
  64. package/lib/compiler/internal/residentCheckProcess.js.map +1 -1
  65. package/lib/compiler/internal/residentTransformProcess.d.ts +0 -6
  66. package/lib/compiler/internal/residentTransformProcess.js +0 -25
  67. package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
  68. package/lib/compiler/internal/spawnNative.d.ts +14 -6
  69. package/lib/compiler/internal/spawnNative.js +32 -11
  70. package/lib/compiler/internal/spawnNative.js.map +1 -1
  71. package/lib/compiler/internal/startResidentTransform.d.ts +1 -5
  72. package/lib/compiler/internal/startResidentTransform.js +1 -3
  73. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  74. package/lib/launcher/internal/prepareExecution.d.ts +2 -1
  75. package/lib/launcher/internal/prepareExecution.js +269 -23
  76. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  77. package/lib/launcher/internal/registerRuntimeHooks.js +6 -1
  78. package/lib/launcher/internal/registerRuntimeHooks.js.map +1 -1
  79. package/lib/launcher/internal/runTtscserver.js +4 -9
  80. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  81. package/lib/launcher/internal/runTtsx.js +2 -55
  82. package/lib/launcher/internal/runTtsx.js.map +1 -1
  83. package/lib/launcher/internal/runtimeHooks.d.ts +25 -2
  84. package/lib/launcher/internal/runtimeHooks.js +344 -69
  85. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  86. package/lib/plugin/internal/buildSourcePlugin.js +31 -3
  87. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  88. package/lib/plugin/internal/descriptorProcessFailure.d.ts +23 -19
  89. package/lib/plugin/internal/descriptorProcessFailure.js +40 -51
  90. package/lib/plugin/internal/descriptorProcessFailure.js.map +1 -1
  91. package/lib/plugin/internal/loadProjectPlugins.d.ts +12 -0
  92. package/lib/plugin/internal/loadProjectPlugins.js +77 -28
  93. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  94. package/package.json +8 -8
  95. package/shim/ast/extra-shim.json +4 -4
  96. package/shim/ast/go.mod +14 -14
  97. package/shim/ast/go.sum +20 -20
  98. package/shim/astnav/go.mod +14 -14
  99. package/shim/astnav/go.sum +20 -20
  100. package/shim/bundled/extra-shim.json +1 -1
  101. package/shim/bundled/go.mod +5 -5
  102. package/shim/checker/extra-shim.json +31 -31
  103. package/shim/checker/go.mod +5 -5
  104. package/shim/compiler/extra-shim.json +4 -4
  105. package/shim/compiler/go.mod +14 -14
  106. package/shim/compiler/go.sum +22 -22
  107. package/shim/core/extra-shim.json +4 -4
  108. package/shim/core/go.mod +5 -5
  109. package/shim/diagnosticwriter/go.mod +5 -5
  110. package/shim/lsp/extra-shim.json +4 -4
  111. package/shim/lsp/go.mod +16 -16
  112. package/shim/lsp/go.sum +26 -26
  113. package/shim/parser/extra-shim.json +4 -4
  114. package/shim/parser/go.mod +5 -5
  115. package/shim/printer/go.mod +14 -14
  116. package/shim/printer/go.sum +22 -22
  117. package/shim/scanner/extra-shim.json +4 -4
  118. package/shim/scanner/go.mod +5 -5
  119. package/shim/transformers/go.mod +14 -14
  120. package/shim/transformers/go.sum +20 -20
  121. package/shim/tsoptions/extra-shim.json +4 -4
  122. package/shim/tsoptions/go.mod +5 -5
  123. package/shim/tspath/extra-shim.json +4 -4
  124. package/shim/tspath/go.mod +5 -5
  125. package/shim/vfs/cachedvfs/extra-shim.json +4 -4
  126. package/shim/vfs/cachedvfs/go.mod +5 -5
  127. package/shim/vfs/extra-shim.json +4 -4
  128. package/shim/vfs/go.mod +5 -5
  129. package/shim/vfs/osvfs/extra-shim.json +4 -4
  130. package/shim/vfs/osvfs/go.mod +5 -5
  131. package/src/TtscService.ts +8 -20
  132. package/src/compiler/internal/captureProcessOutput.ts +96 -0
  133. package/src/compiler/internal/compileProjectInMemory.ts +7 -7
  134. package/src/compiler/internal/residentCheckProcess.ts +1 -21
  135. package/src/compiler/internal/residentTransformProcess.ts +0 -42
  136. package/src/compiler/internal/spawnNative.ts +39 -18
  137. package/src/compiler/internal/startResidentTransform.ts +1 -12
  138. package/src/launcher/internal/prepareExecution.ts +297 -26
  139. package/src/launcher/internal/registerRuntimeHooks.ts +11 -2
  140. package/src/launcher/internal/runTtscserver.ts +4 -9
  141. package/src/launcher/internal/runTtsx.ts +2 -78
  142. package/src/launcher/internal/runtimeHooks.ts +385 -82
  143. package/src/plugin/internal/buildSourcePlugin.ts +36 -3
  144. package/src/plugin/internal/descriptorProcessFailure.ts +34 -65
  145. package/src/plugin/internal/loadProjectPlugins.ts +78 -39
  146. package/tools/gen_shims/go.mod +14 -14
  147. package/tools/gen_shims/go.sum +12 -12
  148. package/tools/shim_audit/go.mod +10 -10
  149. package/tools/shim_audit/go.sum +8 -8
@@ -0,0 +1,63 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ // TestServeShardsRefreshCrossFileImplementation verifies that an assignment
11
+ // source and the declaration node whose implementation span it owns enter one
12
+ // replacement transaction.
13
+ func TestServeShardsRefreshCrossFileImplementation(t *testing.T) {
14
+ root := t.TempDir()
15
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
16
+ "compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
17
+ "include": ["src"]
18
+ }`)
19
+ service := filepath.Join(root, "src", "service.ts")
20
+ install := filepath.Join(root, "src", "install.ts")
21
+ writeGraphFile(t, service, "export class Service { run(): void {} }\n")
22
+ writeGraphFile(t, install, "import { Service } from './service';\nexport function helper(): void {}\nexport function install(value: Service): void { value.run = (): void => { helper(); }; }\n")
23
+
24
+ session, err := newGraphSession(root, "tsconfig.json")
25
+ if err != nil {
26
+ t.Fatal(err)
27
+ }
28
+ defer session.Close()
29
+ if snapshot, _, _, err := session.SnapshotShards(); err != nil || snapshot == nil {
30
+ t.Fatalf("initial shard snapshot = snapshot:%v error:%v", snapshot != nil, err)
31
+ }
32
+ serviceSource := session.compiler.Program().SourceFile(service)
33
+ if serviceSource == nil {
34
+ t.Fatal("service source was absent from resident program")
35
+ }
36
+ serviceKey := session.graphStore.sourceKeys[serviceSource.FileName()]
37
+ before := session.graphStore.shards[serviceKey].digest
38
+ evidenceFile := ""
39
+ for _, edge := range session.graphStore.shards[serviceKey].shard.Edges {
40
+ if strings.Contains(edge.From, "#Service.run:method") && strings.Contains(edge.To, "#helper:function") && edge.Evidence != nil {
41
+ evidenceFile = edge.Evidence.File
42
+ break
43
+ }
44
+ }
45
+ if evidenceFile != "src/install.ts" {
46
+ t.Fatalf("cross-file implementation edge evidence = %q", evidenceFile)
47
+ }
48
+
49
+ if err := os.WriteFile(install, []byte("import { Service } from './service';\nexport function helper(): void {}\nexport function install(value: Service): void { void value; }\n"), 0o644); err != nil {
50
+ t.Fatal(err)
51
+ }
52
+ snapshot, mode, changed, err := session.SnapshotShards()
53
+ if err != nil {
54
+ t.Fatal(err)
55
+ }
56
+ if snapshot == nil || mode != serveModeIncremental || !changed {
57
+ t.Fatalf("cross-file implementation edit = snapshot:%v mode:%q changed:%v", snapshot != nil, mode, changed)
58
+ }
59
+ if session.graphStore.shards[serviceKey].digest == before {
60
+ t.Fatal("declaration shard retained its cross-file implementation evidence")
61
+ }
62
+ assertServeShardFactsMatchFullDump(t, session)
63
+ }
@@ -0,0 +1,80 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeShardsRefreshDependentDiagnostics proves a public API edit rebuilds
10
+ // the reverse semantic closure. The dependent source text is unchanged while
11
+ // its checker diagnostic changes, so reusing that shard would be stale.
12
+ //
13
+ // 1. Commit a producer and consumer whose public types initially agree.
14
+ // 2. Change only the producer's exported return type.
15
+ // 3. Require both sources to be re-extracted and the new consumer diagnostic
16
+ // to be transmitted in its replacement shard.
17
+ func TestServeShardsRefreshDependentDiagnostics(t *testing.T) {
18
+ root := t.TempDir()
19
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
20
+ "compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
21
+ "include": ["src"]
22
+ }`)
23
+ value := filepath.Join(root, "src", "value.ts")
24
+ consumer := filepath.Join(root, "src", "consumer.ts")
25
+ writeGraphFile(t, value, "export function value(): number { return 1; }\n")
26
+ writeGraphFile(t, consumer, "import { value } from './value';\nexport const consumed: number = value();\n")
27
+
28
+ session, err := newGraphSession(root, "tsconfig.json")
29
+ if err != nil {
30
+ t.Fatal(err)
31
+ }
32
+ defer session.Close()
33
+ if snapshot, _, _, err := session.SnapshotShards(); err != nil || snapshot == nil {
34
+ t.Fatalf("initial shard snapshot = snapshot:%v error:%v", snapshot != nil, err)
35
+ }
36
+ consumerSource := session.compiler.Program().SourceFile(consumer)
37
+ if consumerSource == nil {
38
+ t.Fatal("consumer source was absent from resident program")
39
+ }
40
+ consumerKey := session.graphStore.sourceKeys[consumerSource.FileName()]
41
+ before := session.graphStore.shards[consumerKey].digest
42
+
43
+ if err := os.WriteFile(value, []byte("export function value(): string { return 'one'; }\n"), 0o644); err != nil {
44
+ t.Fatal(err)
45
+ }
46
+ snapshot, mode, changed, err := session.SnapshotShards()
47
+ if err != nil {
48
+ t.Fatal(err)
49
+ }
50
+ if snapshot == nil || mode != serveModeIncremental || !changed {
51
+ t.Fatalf("public API edit = snapshot:%v mode:%q changed:%v", snapshot != nil, mode, changed)
52
+ }
53
+ after := session.graphStore.shards[consumerKey].digest
54
+ if after == before {
55
+ t.Fatal("dependent diagnostic retained its previous shard digest")
56
+ }
57
+ upserted := false
58
+ for _, upsert := range snapshot.Upserts {
59
+ if upsert.Shard.Key == consumerKey {
60
+ upserted = true
61
+ break
62
+ }
63
+ }
64
+ if !upserted {
65
+ t.Fatal("public API edit did not transmit the changed dependent shard")
66
+ }
67
+ valueKeyFile := session.compiler.Program().SourceFile(value).FileName()
68
+ consumerKeyFile := consumerSource.FileName()
69
+ if len(session.graphStore.extractedFiles) != 2 ||
70
+ session.graphStore.extractedFiles[0] != consumerKeyFile ||
71
+ session.graphStore.extractedFiles[1] != valueKeyFile {
72
+ t.Fatalf(
73
+ "public API edit extracted %v, want reverse closure [%s %s]",
74
+ session.graphStore.extractedFiles,
75
+ consumerKeyFile,
76
+ valueKeyFile,
77
+ )
78
+ }
79
+ assertServeShardFactsMatchFullDump(t, session)
80
+ }
@@ -0,0 +1,95 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeShardsRenameMovesSourceIdentity verifies a renamed source deletes its
10
+ // old shard and publishes a new one whose coordinates name the new path.
11
+ //
12
+ // A rename is a delete and a create at the filesystem layer but not at the
13
+ // resolution layer: the old path can still be imported, and the shard key binds
14
+ // the path, so carrying the old identity forward would leave the consumer with
15
+ // two coordinates for one declaration. The dependent's import is rewritten in
16
+ // the same step, which is what keeps the program resolvable and proves the
17
+ // closure covers both ends of the move.
18
+ //
19
+ // 1. Commit a project whose consumer imports a source by its original path.
20
+ // 2. Rename that source and repoint the import in the same generation.
21
+ // 3. Require the old shard deleted, a new shard published, and the dependent replaced.
22
+ func TestServeShardsRenameMovesSourceIdentity(t *testing.T) {
23
+ root := t.TempDir()
24
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
25
+ "compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
26
+ "include": ["src"]
27
+ }`)
28
+ original := filepath.Join(root, "src", "original.ts")
29
+ consumerFile := filepath.Join(root, "src", "consumer.ts")
30
+ writeGraphFile(t, original, "export function moved(): number { return 3; }\n")
31
+ writeGraphFile(t, consumerFile, "import { moved } from './original';\nexport function consume(): number { return moved(); }\n")
32
+
33
+ session, err := newGraphSession(root, "tsconfig.json")
34
+ if err != nil {
35
+ t.Fatal(err)
36
+ }
37
+ defer session.Close()
38
+ initial, mode, changed, err := session.SnapshotShards()
39
+ if err != nil {
40
+ t.Fatal(err)
41
+ }
42
+ if initial == nil || mode != serveModeInitial || !changed {
43
+ t.Fatalf("initial snapshot = snapshot:%v mode:%q changed:%v", initial != nil, mode, changed)
44
+ }
45
+ originalSource := session.compiler.Program().SourceFile(original)
46
+ consumerSource := session.compiler.Program().SourceFile(consumerFile)
47
+ if originalSource == nil || consumerSource == nil {
48
+ t.Fatal("fixture source was absent from resident program")
49
+ }
50
+ originalKeyFile := originalSource.FileName()
51
+ consumerKeyFile := consumerSource.FileName()
52
+ originalKey := session.graphStore.sourceKeys[originalKeyFile]
53
+ initialConsumerKey := session.graphStore.sourceKeys[consumerKeyFile]
54
+ if originalKey == "" || initialConsumerKey == "" {
55
+ t.Fatal("initial generation omitted a fixture source shard")
56
+ }
57
+
58
+ renamed := filepath.Join(root, "src", "renamed.ts")
59
+ if err := os.Rename(original, renamed); err != nil {
60
+ t.Fatal(err)
61
+ }
62
+ writeGraphFile(t, consumerFile, "import { moved } from './renamed';\nexport function consume(): number { return moved(); }\n")
63
+
64
+ next, mode, changed, err := session.SnapshotShards()
65
+ if err != nil {
66
+ t.Fatal(err)
67
+ }
68
+ if next == nil || !changed || next.BaseGeneration != initial.Generation {
69
+ t.Fatalf("rename generation = snapshot:%#v mode:%q changed:%v", next, mode, changed)
70
+ }
71
+ if !containsString(next.Deletes, originalKey) {
72
+ t.Fatalf("rename did not delete the superseded shard %q: %v", originalKey, next.Deletes)
73
+ }
74
+ if _, exists := session.graphStore.sourceKeys[originalKeyFile]; exists {
75
+ t.Fatal("committed store retained the pre-rename source identity")
76
+ }
77
+ renamedSource := session.compiler.Program().SourceFile(renamed)
78
+ if renamedSource == nil {
79
+ t.Fatal("renamed source did not enter the resident program")
80
+ }
81
+ renamedKey := session.graphStore.sourceKeys[renamedSource.FileName()]
82
+ if renamedKey == "" {
83
+ t.Fatal("renamed source did not acquire a committed shard identity")
84
+ }
85
+ if renamedKey == originalKey {
86
+ t.Fatalf("renamed source reused the pre-rename shard key %q", renamedKey)
87
+ }
88
+ if !containsUpsertedShardKey(next, renamedKey) {
89
+ t.Fatalf("generation did not publish the renamed source shard %q", renamedKey)
90
+ }
91
+ nextConsumerKey := session.graphStore.sourceKeys[consumerKeyFile]
92
+ if nextConsumerKey == initialConsumerKey {
93
+ t.Fatalf("dependent shard identity %q survived a repointed import", nextConsumerKey)
94
+ }
95
+ }
@@ -0,0 +1,48 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ // TestServeShardsRetryPreservesCommittedGeneration verifies projection failure
11
+ // cannot advance the native shard store. The same captured compiler change is
12
+ // retried against the prior base and publishes exactly the next sequence only
13
+ // after every replacement validates.
14
+ func TestServeShardsRetryPreservesCommittedGeneration(t *testing.T) {
15
+ root := graphSessionFixture(t)
16
+ session, err := newGraphSession(root, "tsconfig.json")
17
+ if err != nil {
18
+ t.Fatal(err)
19
+ }
20
+ defer session.Close()
21
+ initial, _, _, err := session.SnapshotShards()
22
+ if err != nil {
23
+ t.Fatal(err)
24
+ }
25
+ committed := session.graphStore
26
+ file := filepath.Join(root, "src", "index.ts")
27
+ if err := os.WriteFile(file, []byte("export class AfterRetry {}\n"), 0o644); err != nil {
28
+ t.Fatal(err)
29
+ }
30
+
31
+ session.cwd = "relative-project"
32
+ snapshot, _, changed, err := session.SnapshotShards()
33
+ if err == nil || !strings.Contains(err.Error(), "project root") {
34
+ t.Fatalf("projection error = %v, want absolute-root rejection", err)
35
+ }
36
+ if snapshot != nil || changed || session.graphStore != committed || session.pending == nil {
37
+ t.Fatalf("failed transaction mutated publication state: snapshot:%v changed:%v store:%v pending:%v", snapshot != nil, changed, session.graphStore != committed, session.pending != nil)
38
+ }
39
+
40
+ session.cwd = root
41
+ recovered, mode, changed, err := session.SnapshotShards()
42
+ if err != nil {
43
+ t.Fatal(err)
44
+ }
45
+ if recovered == nil || mode != serveModeIncremental || !changed || recovered.Sequence != initial.Sequence+1 || recovered.BaseGeneration != initial.Generation {
46
+ t.Fatalf("recovered transaction = snapshot:%#v mode:%q changed:%v", recovered, mode, changed)
47
+ }
48
+ }
@@ -0,0 +1,121 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "runtime"
7
+ "testing"
8
+ )
9
+
10
+ // TestServeShardsReuseUnaffectedSources verifies a private body edit advances
11
+ // only the changed content-addressed source shard. TypeScript's forced
12
+ // declaration output proves the public shape stayed fixed, so neither the
13
+ // dependent nor unrelated source is re-extracted or retransmitted.
14
+ //
15
+ // 1. Commit a project where one source has a dependent and an unrelated peer.
16
+ // 2. Change only the source's private function body and request another shard snapshot.
17
+ // 3. Require one extracted source and no dependent or unrelated shard replacement.
18
+ func TestServeShardsReuseUnaffectedSources(t *testing.T) {
19
+ root := t.TempDir()
20
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
21
+ "compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
22
+ "include": ["src"]
23
+ }`)
24
+ writeGraphFile(t, filepath.Join(root, "src", "value.ts"), "export function value(): number { return 1; }\n")
25
+ writeGraphFile(t, filepath.Join(root, "src", "consumer.ts"), "import { value } from './value';\nexport function consume(): number { return value(); }\n")
26
+ writeGraphFile(t, filepath.Join(root, "src", "unrelated.ts"), "export const unrelated = true;\n")
27
+
28
+ session, err := newGraphSession(root, "tsconfig.json")
29
+ if err != nil {
30
+ t.Fatal(err)
31
+ }
32
+ defer session.Close()
33
+ initial, mode, changed, err := session.SnapshotShards()
34
+ if err != nil {
35
+ t.Fatal(err)
36
+ }
37
+ if initial == nil || mode != serveModeInitial || !changed {
38
+ t.Fatalf("initial snapshot = snapshot:%v mode:%q changed:%v", initial != nil, mode, changed)
39
+ }
40
+ valueFile := filepath.Join(root, "src", "value.ts")
41
+ consumerFile := filepath.Join(root, "src", "consumer.ts")
42
+ unrelatedFile := filepath.Join(root, "src", "unrelated.ts")
43
+ valueSource := session.compiler.Program().SourceFile(valueFile)
44
+ consumerSource := session.compiler.Program().SourceFile(consumerFile)
45
+ unrelatedSource := session.compiler.Program().SourceFile(unrelatedFile)
46
+ if valueSource == nil || consumerSource == nil || unrelatedSource == nil {
47
+ t.Fatal("fixture source was absent from resident program")
48
+ }
49
+ valueKeyFile := valueSource.FileName()
50
+ consumerKeyFile := consumerSource.FileName()
51
+ unrelatedKeyFile := unrelatedSource.FileName()
52
+ initialValueKey := session.graphStore.sourceKeys[valueKeyFile]
53
+ initialConsumerKey := session.graphStore.sourceKeys[consumerKeyFile]
54
+ initialUnrelatedKey := session.graphStore.sourceKeys[unrelatedKeyFile]
55
+ unrelatedWireFile := session.graphStore.wireSources[unrelatedKeyFile]
56
+ for index := range session.graphStore.provenance.Sources {
57
+ if session.graphStore.provenance.Sources[index].File != unrelatedKeyFile {
58
+ continue
59
+ }
60
+ if runtime.GOOS == "windows" {
61
+ volume := "C:"
62
+ if filepath.VolumeName(root) == volume {
63
+ volume = "Z:"
64
+ }
65
+ session.graphStore.provenance.Sources[index].File = volume + "/unrelated.ts"
66
+ } else {
67
+ session.graphStore.provenance.Sources[index].File = "//foreign/share/unrelated.ts"
68
+ }
69
+ break
70
+ }
71
+ if unrelatedWireFile == "" {
72
+ t.Fatal("unrelated source was absent from physical/wire provenance")
73
+ }
74
+ if err := os.WriteFile(valueFile, []byte("export function value(): number { return 2; }\n"), 0o644); err != nil {
75
+ t.Fatal(err)
76
+ }
77
+ delta, mode, changed, err := session.SnapshotShards()
78
+ if err != nil {
79
+ t.Fatal(err)
80
+ }
81
+ if delta == nil || mode != serveModeIncremental || !changed || delta.BaseSequence != initial.Sequence || delta.BaseGeneration != initial.Generation {
82
+ t.Fatalf("incremental coordinates: snapshot=%#v mode=%q changed=%v", delta, mode, changed)
83
+ }
84
+ nextValueKey := session.graphStore.sourceKeys[valueKeyFile]
85
+ if nextValueKey == initialValueKey {
86
+ t.Fatal("changed source retained its content-addressed shard key")
87
+ }
88
+ if !containsString(delta.Deletes, initialValueKey) {
89
+ t.Fatalf("delta did not delete superseded source shard %q: %v", initialValueKey, delta.Deletes)
90
+ }
91
+ if session.graphStore.sourceKeys[consumerKeyFile] != initialConsumerKey || session.graphStore.sourceKeys[unrelatedKeyFile] != initialUnrelatedKey {
92
+ t.Fatal("body edit moved an unchanged source shard identity")
93
+ }
94
+ if len(session.graphStore.extractedFiles) != 1 || session.graphStore.extractedFiles[0] != valueKeyFile {
95
+ t.Fatalf("private body edit extracted %v, want only %s", session.graphStore.extractedFiles, valueKeyFile)
96
+ }
97
+ wireFilePreserved := false
98
+ for _, source := range session.graphStore.wireProvenance.Sources {
99
+ if source.File == unrelatedWireFile {
100
+ wireFilePreserved = true
101
+ break
102
+ }
103
+ }
104
+ if !wireFilePreserved {
105
+ t.Fatalf("unchanged wire provenance lost %q", unrelatedWireFile)
106
+ }
107
+ for _, upsert := range delta.Upserts {
108
+ if upsert.Shard.Key == initialConsumerKey || upsert.Shard.Key == initialUnrelatedKey {
109
+ t.Fatalf("delta retransmitted byte-identical shard %q", upsert.Shard.Key)
110
+ }
111
+ }
112
+ }
113
+
114
+ func containsString(values []string, expected string) bool {
115
+ for _, value := range values {
116
+ if value == expected {
117
+ return true
118
+ }
119
+ }
120
+ return false
121
+ }
@@ -0,0 +1,85 @@
1
+ package driver
2
+
3
+ import (
4
+ "context"
5
+ "crypto/sha256"
6
+ "encoding/hex"
7
+ "fmt"
8
+ "strings"
9
+
10
+ "github.com/microsoft/typescript-go/shim/ast"
11
+ shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
12
+ shimtspath "github.com/microsoft/typescript-go/shim/tspath"
13
+ )
14
+
15
+ // DeclarationShapeDigest hashes the declaration output TypeScript itself
16
+ // derives for one source file. Resident consumers compare the old and new
17
+ // values to distinguish a private body edit from a public semantic movement
18
+ // before expanding invalidation through reverse dependencies.
19
+ func (p *Program) DeclarationShapeDigest(file *ast.SourceFile) (string, error) {
20
+ if p == nil || p.TSProgram == nil || file == nil {
21
+ return "", fmt.Errorf("driver: declaration shape requires a loaded source file")
22
+ }
23
+ if err := p.ApplyLinkedPlugins(); err != nil {
24
+ return "", err
25
+ }
26
+ var signature strings.Builder
27
+ emitted := false
28
+ p.TSProgram.Emit(context.Background(), shimcompiler.EmitOptions{
29
+ TargetSourceFile: file,
30
+ EmitOnly: shimcompiler.EmitOnlyForcedDts,
31
+ WriteFile: func(_ string, text string, data *shimcompiler.WriteFileData) error {
32
+ emitted = true
33
+ if data != nil && data.SourceMapUrlPos >= 0 && data.SourceMapUrlPos <= len(text) {
34
+ text = text[:data.SourceMapUrlPos]
35
+ }
36
+ signature.WriteString(text)
37
+ if data != nil {
38
+ for _, diagnostic := range data.Diagnostics {
39
+ appendDeclarationShapeDiagnostic(&signature, file, diagnostic)
40
+ }
41
+ }
42
+ return nil
43
+ },
44
+ })
45
+ if !emitted {
46
+ // Mirrors tsgo's incremental fallback to the file version when forced
47
+ // declaration emit has no output. This is conservative: any body movement
48
+ // expands dependents instead of risking a stale semantic closure.
49
+ signature.WriteString(file.Text())
50
+ }
51
+ digest := sha256.Sum256([]byte(signature.String()))
52
+ return hex.EncodeToString(digest[:]), nil
53
+ }
54
+
55
+ func appendDeclarationShapeDiagnostic(builder *strings.Builder, source *ast.SourceFile, diagnostic *ast.Diagnostic) {
56
+ if diagnostic == nil {
57
+ return
58
+ }
59
+ builder.WriteString("\n")
60
+ diagnosticFile := diagnostic.File()
61
+ if diagnosticFile != nil && diagnosticFile != source {
62
+ builder.WriteString(shimtspath.EnsurePathIsNonModuleName(shimtspath.GetRelativePathFromDirectory(
63
+ shimtspath.GetDirectoryPath(string(source.Path())),
64
+ string(diagnosticFile.Path()),
65
+ shimtspath.ComparePathsOptions{},
66
+ )))
67
+ }
68
+ if diagnosticFile != nil {
69
+ builder.WriteString(fmt.Sprintf("(%d,%d): ", diagnostic.Pos(), diagnostic.Len()))
70
+ }
71
+ builder.WriteString(diagnostic.Category().Name())
72
+ builder.WriteString(fmt.Sprintf("%d: ", diagnostic.Code()))
73
+ builder.WriteString(string(diagnostic.MessageKey()))
74
+ builder.WriteString("\n")
75
+ for _, argument := range diagnostic.MessageArgs() {
76
+ builder.WriteString(argument)
77
+ builder.WriteString("\n")
78
+ }
79
+ for _, chain := range diagnostic.MessageChain() {
80
+ appendDeclarationShapeDiagnostic(builder, source, chain)
81
+ }
82
+ for _, related := range diagnostic.RelatedInformation() {
83
+ appendDeclarationShapeDiagnostic(builder, source, related)
84
+ }
85
+ }
package/driver/program.go CHANGED
@@ -513,8 +513,10 @@ func ApplySourcePreamble(text string, preamble string) string {
513
513
  return bom + preamble + rest
514
514
  }
515
515
 
516
- // SourceFiles exposes the program's user-authored source files (declaration
517
- // files filtered out).
516
+ // SourceFiles exposes the program's resident non-declaration source files.
517
+ // Imported implementation files from source-distributed dependencies can be
518
+ // present; consumers that need project-owned files must apply their own root
519
+ // predicate.
518
520
  func (p *Program) SourceFiles() []*ast.SourceFile {
519
521
  // Discarded on purpose; see SourceFile. `Diagnostics` carries the failure.
520
522
  _ = p.ApplyLinkedPlugins()
@@ -564,6 +566,19 @@ func (p *Program) HasLinkedProgramPlugins() bool {
564
566
  // Diagnostics returns project diagnostics that must block compilation or
565
567
  // runtime execution before any JavaScript is emitted or evaluated.
566
568
  func (p *Program) Diagnostics() []Diagnostic {
569
+ return p.diagnostics(nil)
570
+ }
571
+
572
+ // DiagnosticsForFiles returns diagnostics whose semantic work is restricted to
573
+ // selected source files, plus the program/global diagnostics that qualify the
574
+ // same immutable Program generation. The resident graph shard producer uses it
575
+ // for the compiler-invalidated closure; callers that need the complete project
576
+ // continue to use Diagnostics.
577
+ func (p *Program) DiagnosticsForFiles(files []*ast.SourceFile) []Diagnostic {
578
+ return p.diagnostics(files)
579
+ }
580
+
581
+ func (p *Program) diagnostics(files []*ast.SourceFile) []Diagnostic {
567
582
  if p == nil || p.TSProgram == nil {
568
583
  return []Diagnostic{{Message: "driver: nil program"}}
569
584
  }
@@ -600,14 +615,28 @@ func (p *Program) Diagnostics() []Diagnostic {
600
615
  })
601
616
  }
602
617
  ctx := context.Background()
603
- raw := shimcompiler.GetDiagnosticsOfAnyProgram(
604
- ctx,
605
- p.TSProgram,
606
- nil,
607
- false,
608
- p.TSProgram.GetBindDiagnostics,
609
- p.TSProgram.GetSemanticDiagnostics,
610
- )
618
+ var raw []*ast.Diagnostic
619
+ if files == nil {
620
+ raw = shimcompiler.GetDiagnosticsOfAnyProgram(
621
+ ctx,
622
+ p.TSProgram,
623
+ nil,
624
+ false,
625
+ p.TSProgram.GetBindDiagnostics,
626
+ p.TSProgram.GetSemanticDiagnostics,
627
+ )
628
+ } else {
629
+ for _, file := range files {
630
+ raw = append(raw, shimcompiler.GetDiagnosticsOfAnyProgram(
631
+ ctx,
632
+ p.TSProgram,
633
+ file,
634
+ false,
635
+ p.TSProgram.GetBindDiagnostics,
636
+ p.TSProgram.GetSemanticDiagnostics,
637
+ )...)
638
+ }
639
+ }
611
640
  raw = filterDiagnostics(raw)
612
641
  return append(out, convertDiagnostics(shimcompiler.SortAndDeduplicateDiagnostics(raw))...)
613
642
  }
package/go.sum CHANGED
@@ -1,22 +1,22 @@
1
- github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=
2
- github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
3
- github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
4
- github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
5
- github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
6
- github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
7
- github.com/microsoft/typescript-go v0.0.0-20260429010842-56ab4af42157 h1:llOMPhKDiQ+UEJiAaQuuWaJOTg5V7tNNTEf9JPWoYSY=
8
- github.com/microsoft/typescript-go v0.0.0-20260429010842-56ab4af42157/go.mod h1:m8YA0PMC7ti0GW0RI05D6fEcjQeu98XVS+FWF+VDW2k=
9
- github.com/peter-evans/patience v0.3.0 h1:rX0JdJeepqdQl1Sk9c9uvorjYYzL2TfgLX1adqYm9cA=
10
- github.com/peter-evans/patience v0.3.0/go.mod h1:Kmxu5sY1NmBLFSStvXjX1wS9mIv7wMcP/ubucyMOAu0=
11
- github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
12
- github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
13
- github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
14
- github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
15
- golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
16
- golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
17
- golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
18
- golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
19
- golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
20
- golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
21
- gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
22
- gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
1
+ github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=
2
+ github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
3
+ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
4
+ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
5
+ github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
6
+ github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
7
+ github.com/microsoft/typescript-go v0.0.0-20260429010842-56ab4af42157 h1:llOMPhKDiQ+UEJiAaQuuWaJOTg5V7tNNTEf9JPWoYSY=
8
+ github.com/microsoft/typescript-go v0.0.0-20260429010842-56ab4af42157/go.mod h1:m8YA0PMC7ti0GW0RI05D6fEcjQeu98XVS+FWF+VDW2k=
9
+ github.com/peter-evans/patience v0.3.0 h1:rX0JdJeepqdQl1Sk9c9uvorjYYzL2TfgLX1adqYm9cA=
10
+ github.com/peter-evans/patience v0.3.0/go.mod h1:Kmxu5sY1NmBLFSStvXjX1wS9mIv7wMcP/ubucyMOAu0=
11
+ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
12
+ github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
13
+ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
14
+ github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
15
+ golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
16
+ golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
17
+ golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
18
+ golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
19
+ golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
20
+ golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
21
+ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
22
+ gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=