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,92 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeShardsAbandonedGenerationConvergesOnRetry verifies a consumer that
10
+ // abandons a request mid-flight can still converge, and that the facts it
11
+ // eventually commits match the ones it discarded.
12
+ //
13
+ // The producer has no abort path: it commits after the projection succeeds, so
14
+ // a client that walks away leaves the producer's committed generation ahead of
15
+ // the consumer's. Cancellation is therefore discharged by base negotiation
16
+ // rather than by cancellation handling, which is sound only if the abandoned
17
+ // generation is not the sole carrier of its facts. A consumer replaying from
18
+ // its own stale base has to receive those same facts again.
19
+ //
20
+ // 1. Commit one generation, then edit and take a generation the client discards.
21
+ // 2. Edit again and take the generation a recovered client would actually read.
22
+ // 3. Require the store to have advanced once per accepted call and the final
23
+ // shard for the edited source to carry the latest text, not the abandoned one.
24
+ func TestServeShardsAbandonedGenerationConvergesOnRetry(t *testing.T) {
25
+ root := graphSessionFixture(t)
26
+ session, err := newGraphSession(root, "tsconfig.json")
27
+ if err != nil {
28
+ t.Fatal(err)
29
+ }
30
+ defer session.Close()
31
+ committed, _, _, err := session.SnapshotShards()
32
+ if err != nil {
33
+ t.Fatal(err)
34
+ }
35
+ file := filepath.Join(root, "src", "index.ts")
36
+ indexSource := session.compiler.Program().SourceFile(file)
37
+ if indexSource == nil {
38
+ t.Fatal("fixture source was absent from resident program")
39
+ }
40
+ indexKeyFile := indexSource.FileName()
41
+
42
+ // The generation the client asks for and then abandons before reading.
43
+ if err := os.WriteFile(file, []byte("export class Abandoned {}\n"), 0o644); err != nil {
44
+ t.Fatal(err)
45
+ }
46
+ abandoned, _, changed, err := session.SnapshotShards()
47
+ if err != nil {
48
+ t.Fatal(err)
49
+ }
50
+ if abandoned == nil || !changed || abandoned.BaseGeneration != committed.Generation {
51
+ t.Fatalf("abandoned generation = snapshot:%#v changed:%v", abandoned, changed)
52
+ }
53
+ abandonedKey := session.graphStore.sourceKeys[indexKeyFile]
54
+
55
+ // The producer moved on regardless. A later edit plus the client's retry has
56
+ // to publish a generation that supersedes the abandoned one rather than
57
+ // depending on the consumer having applied it.
58
+ if err := os.WriteFile(file, []byte("export class Converged {}\n"), 0o644); err != nil {
59
+ t.Fatal(err)
60
+ }
61
+ converged, _, changed, err := session.SnapshotShards()
62
+ if err != nil {
63
+ t.Fatal(err)
64
+ }
65
+ if converged == nil || !changed {
66
+ t.Fatalf("converged generation = snapshot:%#v changed:%v", converged, changed)
67
+ }
68
+ if converged.BaseGeneration != abandoned.Generation {
69
+ t.Fatalf(
70
+ "converged base = %q, want the abandoned generation %q the producer actually committed",
71
+ converged.BaseGeneration,
72
+ abandoned.Generation,
73
+ )
74
+ }
75
+ if converged.Sequence != abandoned.Sequence+1 {
76
+ t.Fatalf("converged sequence = %d, want %d", converged.Sequence, abandoned.Sequence+1)
77
+ }
78
+ convergedKey := session.graphStore.sourceKeys[indexKeyFile]
79
+ if convergedKey == "" || convergedKey == abandonedKey {
80
+ t.Fatalf("edited source kept the abandoned shard identity %q", convergedKey)
81
+ }
82
+ if !containsUpsertedShardKey(converged, convergedKey) {
83
+ t.Fatalf("converged generation did not publish the latest shard %q", convergedKey)
84
+ }
85
+ if !containsString(converged.Deletes, abandonedKey) {
86
+ t.Fatalf(
87
+ "converged generation did not supersede the abandoned shard %q: %v",
88
+ abandonedKey,
89
+ converged.Deletes,
90
+ )
91
+ }
92
+ }
@@ -0,0 +1,57 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeShardsDeleteRemovedSource verifies a root-set reload names the
10
+ // superseded source shard explicitly while publishing a complete replacement
11
+ // generation. A consumer never has to infer deletion from a missing payload.
12
+ func TestServeShardsDeleteRemovedSource(t *testing.T) {
13
+ root := graphSessionFixture(t)
14
+ keep := filepath.Join(root, "src", "keep.ts")
15
+ writeGraphFile(t, keep, "export const keep = true;\n")
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
+ removed := filepath.Join(root, "src", "index.ts")
26
+ removedSource := session.compiler.Program().SourceFile(removed)
27
+ keepSource := session.compiler.Program().SourceFile(keep)
28
+ if removedSource == nil || keepSource == nil {
29
+ t.Fatal("fixture source was absent from resident program")
30
+ }
31
+ removedKeyFile := removedSource.FileName()
32
+ keepKeyFile := keepSource.FileName()
33
+ removedKey := session.graphStore.sourceKeys[removedKeyFile]
34
+ if removedKey == "" {
35
+ t.Fatal("initial generation omitted removable source shard")
36
+ }
37
+ if err := os.Remove(removed); err != nil {
38
+ t.Fatal(err)
39
+ }
40
+
41
+ replacement, mode, changed, err := session.SnapshotShards()
42
+ if err != nil {
43
+ t.Fatal(err)
44
+ }
45
+ if replacement == nil || mode != serveModeReload || !changed || replacement.BaseGeneration != initial.Generation {
46
+ t.Fatalf("replacement generation = snapshot:%#v mode:%q changed:%v", replacement, mode, changed)
47
+ }
48
+ if !containsString(replacement.Deletes, removedKey) {
49
+ t.Fatalf("replacement did not delete removed source shard %q: %v", removedKey, replacement.Deletes)
50
+ }
51
+ if _, exists := session.graphStore.sourceKeys[removedKeyFile]; exists {
52
+ t.Fatal("committed store retained removed source identity")
53
+ }
54
+ if session.graphStore.sourceKeys[keepKeyFile] == "" {
55
+ t.Fatal("replacement generation dropped remaining source")
56
+ }
57
+ }
@@ -0,0 +1,53 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ func TestServeShardsKeepReferencedExternalEndpointsOnIncrementalLane(t *testing.T) {
10
+ root := t.TempDir()
11
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
12
+ "compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
13
+ "include": ["src"]
14
+ }`)
15
+ writeGraphFile(t, filepath.Join(root, "src", "api.d.ts"), "export declare function external(): number;\n")
16
+ index := filepath.Join(root, "src", "index.ts")
17
+ writeGraphFile(t, index, "import { external } from './api';\nexport function local(): number { return external() + 1; }\n")
18
+
19
+ session, err := newGraphSession(root, "tsconfig.json")
20
+ if err != nil {
21
+ t.Fatal(err)
22
+ }
23
+ defer session.Close()
24
+ if snapshot, _, _, err := session.SnapshotShards(); err != nil || snapshot == nil {
25
+ t.Fatalf("initial shard snapshot = snapshot:%v error:%v", snapshot != nil, err)
26
+ }
27
+ if err := os.WriteFile(index, []byte("import { external } from './api';\nexport function local(): number { return external() + 2; }\n"), 0o644); err != nil {
28
+ t.Fatal(err)
29
+ }
30
+ snapshot, mode, changed, err := session.SnapshotShards()
31
+ if err != nil {
32
+ t.Fatal(err)
33
+ }
34
+ if snapshot == nil || mode != serveModeIncremental || !changed {
35
+ t.Fatalf("external endpoint edit = snapshot:%v mode:%q changed:%v", snapshot != nil, mode, changed)
36
+ }
37
+ retained := false
38
+ for _, node := range session.graphStore.nodes {
39
+ if node.External && node.Name == "external" {
40
+ retained = true
41
+ break
42
+ }
43
+ }
44
+ if !retained {
45
+ t.Fatalf(
46
+ "referenced external endpoint left the internal base-node cache: nodes=%#v references=%#v external=%#v",
47
+ session.graphStore.nodes,
48
+ session.graphStore.externalReferences,
49
+ session.graphStore.externalNodes,
50
+ )
51
+ }
52
+ assertServeShardFactsMatchFullDump(t, session)
53
+ }
@@ -0,0 +1,108 @@
1
+ package main
2
+
3
+ import (
4
+ "path/filepath"
5
+ "strings"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeShardsKeepSourceDistributedDependenciesAtTheBoundary proves the
10
+ // incremental store owns only workspace declarations while provenance still
11
+ // attests to every source the resident checker loaded.
12
+ //
13
+ // 1. Snapshot one workspace source that imports a raw TypeScript package.
14
+ // 2. Assert the dependency owns provenance but no authored graph facts.
15
+ // 3. Edit the dependency and assert the store publishes a complete replacement.
16
+ // 4. Compare the replacement store with the full-dump oracle.
17
+ func TestServeShardsKeepSourceDistributedDependenciesAtTheBoundary(t *testing.T) {
18
+ root := t.TempDir()
19
+ dependencyPath := filepath.Join(root, "node_modules", "dep-src", "src", "index.ts")
20
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
21
+ "compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
22
+ "files": ["src/main.ts"]
23
+ }`)
24
+ writeGraphFile(t, filepath.Join(root, "node_modules", "dep-src", "package.json"), `{
25
+ "name": "dep-src",
26
+ "version": "1.0.0",
27
+ "main": "src/index.ts"
28
+ }`)
29
+ writeGraphFile(t, dependencyPath, "export function dependencyValue(): number { return 1; }\nexport function dependencyInternal(): number { return dependencyValue(); }\n")
30
+ writeGraphFile(t, filepath.Join(root, "src", "main.ts"), "import { dependencyValue } from 'dep-src';\nexport function workspaceValue(): number { return dependencyValue() + 1; }\n")
31
+
32
+ session, err := newGraphSession(root, "tsconfig.json")
33
+ if err != nil {
34
+ t.Fatal(err)
35
+ }
36
+ defer session.Close()
37
+ if snapshot, _, _, err := session.SnapshotShards(); err != nil || snapshot == nil {
38
+ t.Fatalf("initial shard snapshot = snapshot:%v error:%v", snapshot != nil, err)
39
+ }
40
+
41
+ dependencyFile := ""
42
+ dependencyDigest := ""
43
+ for _, source := range session.graphStore.provenance.Sources {
44
+ if strings.Contains(filepath.ToSlash(source.File), "/node_modules/dep-src/") {
45
+ dependencyFile = source.File
46
+ dependencyDigest = source.Checker
47
+ break
48
+ }
49
+ }
50
+ if dependencyFile == "" {
51
+ t.Fatal("raw dependency source is absent from provenance")
52
+ }
53
+ for _, file := range session.graphStore.extractedFiles {
54
+ if file == dependencyFile {
55
+ t.Fatalf("raw dependency source was treated as an authored extraction: %v", session.graphStore.extractedFiles)
56
+ }
57
+ }
58
+ dependencyShard, ok := session.graphStore.shards[session.graphStore.sourceKeys[dependencyFile]]
59
+ if !ok || dependencyShard.shard.Source == nil {
60
+ t.Fatalf("dependency provenance shard missing for %q", dependencyFile)
61
+ }
62
+ if len(dependencyShard.shard.Nodes) != 0 || len(dependencyShard.shard.Edges) != 0 {
63
+ t.Fatalf("dependency provenance shard owns graph facts: nodes=%v edges=%v", dependencyShard.shard.Nodes, dependencyShard.shard.Edges)
64
+ }
65
+
66
+ boundaryFound := false
67
+ for _, node := range session.graphStore.nodes {
68
+ normalized := filepath.ToSlash(node.File)
69
+ if strings.Contains(normalized, "/node_modules/dep-src/") {
70
+ if !node.External || node.Name != "dependencyValue" {
71
+ t.Fatalf("unexpected dependency graph node: %+v", node)
72
+ }
73
+ boundaryFound = true
74
+ }
75
+ }
76
+ if !boundaryFound {
77
+ t.Fatal("referenced dependency boundary leaf is absent")
78
+ }
79
+
80
+ writeGraphFile(t, dependencyPath, "export function dependencyValue(): number { return 2; }\nexport function dependencyInternal(): number { return dependencyValue(); }\n")
81
+ replacement, mode, changed, err := session.SnapshotShards()
82
+ if err != nil {
83
+ t.Fatal(err)
84
+ }
85
+ if replacement == nil {
86
+ t.Fatal("dependency edit did not publish a shard snapshot")
87
+ }
88
+ if mode != serveModeRebuild || !changed || len(replacement.Upserts) != len(replacement.Manifest) {
89
+ t.Fatalf(
90
+ "dependency edit should report and publish a complete rebuild: mode=%q changed=%v manifest=%d upserts=%d",
91
+ mode,
92
+ changed,
93
+ len(replacement.Manifest),
94
+ len(replacement.Upserts),
95
+ )
96
+ }
97
+ dependencyDigestChanged := false
98
+ for _, source := range session.graphStore.provenance.Sources {
99
+ if source.File == dependencyFile && source.Checker != dependencyDigest {
100
+ dependencyDigestChanged = true
101
+ break
102
+ }
103
+ }
104
+ if !dependencyDigestChanged {
105
+ t.Fatal("raw dependency edit did not refresh checker provenance")
106
+ }
107
+ assertServeShardFactsMatchFullDump(t, session)
108
+ }
@@ -0,0 +1,88 @@
1
+ package main
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "path/filepath"
7
+ "reflect"
8
+ "sort"
9
+ "testing"
10
+
11
+ "github.com/samchon/ttsc/packages/ttsc/internal/graph"
12
+ )
13
+
14
+ // TestServeShardsMatchFullDumpOracle proves the initial transaction and one
15
+ // accepted edit reconstruct exactly the facts the complete builder produces.
16
+ // This keeps shard ownership and base-node reuse as an optimization boundary,
17
+ // never a second semantic contract.
18
+ func TestServeShardsMatchFullDumpOracle(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", "base.ts"), "export interface Base { run(): void; }\n")
25
+ writeGraphFile(t, filepath.Join(root, "src", "impl.ts"), "import { Base } from './base';\nexport class Impl implements Base { run(): void {} }\n")
26
+ writeGraphFile(t, filepath.Join(root, "src", "consumer.ts"), "import { Impl } from './impl';\nexport function consume(): void { new Impl().run(); }\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
+ assertServeShardFactsMatchFullDump(t, session)
37
+
38
+ impl := filepath.Join(root, "src", "impl.ts")
39
+ if err := os.WriteFile(impl, []byte("import { Base } from './base';\nexport class Impl implements Base { run(): void { const touched = true; void touched; } }\n"), 0o644); err != nil {
40
+ t.Fatal(err)
41
+ }
42
+ snapshot, mode, changed, err := session.SnapshotShards()
43
+ if err != nil {
44
+ t.Fatal(err)
45
+ }
46
+ if snapshot == nil || mode != serveModeIncremental || !changed {
47
+ t.Fatalf("edited shard snapshot = snapshot:%v mode:%q changed:%v", snapshot != nil, mode, changed)
48
+ }
49
+ assertServeShardFactsMatchFullDump(t, session)
50
+ }
51
+
52
+ func assertServeShardFactsMatchFullDump(t *testing.T, session *graphSession) {
53
+ t.Helper()
54
+ complete, err := session.buildDump()
55
+ if err != nil {
56
+ t.Fatal(err)
57
+ }
58
+ var nodes []graph.DumpNode
59
+ var edges []graph.DumpEdge
60
+ var diagnostics []graph.Diagnostic
61
+ for _, committed := range session.graphStore.shards {
62
+ nodes = append(nodes, committed.shard.Nodes...)
63
+ edges = append(edges, committed.shard.Edges...)
64
+ diagnostics = append(diagnostics, committed.shard.Diagnostics...)
65
+ }
66
+ if !reflect.DeepEqual(canonicalJSONRows(nodes), canonicalJSONRows(complete.Nodes)) {
67
+ t.Fatal("committed shard nodes differ from complete-build oracle")
68
+ }
69
+ if !reflect.DeepEqual(canonicalJSONRows(edges), canonicalJSONRows(complete.Edges)) {
70
+ t.Fatal("committed shard edges differ from complete-build oracle")
71
+ }
72
+ if !reflect.DeepEqual(canonicalJSONRows(diagnostics), canonicalJSONRows(complete.Diagnostics)) {
73
+ t.Fatal("committed shard diagnostics differ from complete-build oracle")
74
+ }
75
+ }
76
+
77
+ func canonicalJSONRows[T any](values []T) []string {
78
+ rows := make([]string, 0, len(values))
79
+ for _, value := range values {
80
+ encoded, err := json.Marshal(value)
81
+ if err != nil {
82
+ panic(err)
83
+ }
84
+ rows = append(rows, string(encoded))
85
+ }
86
+ sort.Strings(rows)
87
+ return rows
88
+ }
@@ -0,0 +1,50 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeShardsMergePendingAndNewEdits verifies a second disk edit cannot
10
+ // replace the invalidation closure of an earlier uncommitted generation. Both
11
+ // changed sources must enter the retry transaction before it can advance the
12
+ // shard-store base.
13
+ func TestServeShardsMergePendingAndNewEdits(t *testing.T) {
14
+ root := graphSessionFixture(t)
15
+ other := filepath.Join(root, "src", "other.ts")
16
+ writeGraphFile(t, other, "export class OtherBefore {}\n")
17
+ session, err := newGraphSession(root, "tsconfig.json")
18
+ if err != nil {
19
+ t.Fatal(err)
20
+ }
21
+ defer session.Close()
22
+ if snapshot, _, _, err := session.SnapshotShards(); err != nil || snapshot == nil {
23
+ t.Fatalf("initial snapshot = snapshot:%v error:%v", snapshot != nil, err)
24
+ }
25
+
26
+ first := filepath.Join(root, "src", "index.ts")
27
+ if err := os.WriteFile(first, []byte("export class FirstAfter {}\n"), 0o644); err != nil {
28
+ t.Fatal(err)
29
+ }
30
+ session.cwd = "relative-project"
31
+ if _, _, _, err := session.SnapshotShards(); err == nil {
32
+ t.Fatal("first edit unexpectedly committed through invalid project root")
33
+ }
34
+ if session.pending == nil {
35
+ t.Fatal("failed first edit left no pending invalidation")
36
+ }
37
+
38
+ session.cwd = root
39
+ if err := os.WriteFile(other, []byte("export class OtherAfter {}\n"), 0o644); err != nil {
40
+ t.Fatal(err)
41
+ }
42
+ recovered, mode, changed, err := session.SnapshotShards()
43
+ if err != nil {
44
+ t.Fatal(err)
45
+ }
46
+ if recovered == nil || mode != serveModeIncremental || !changed || session.pending != nil {
47
+ t.Fatalf("merged recovery = snapshot:%v mode:%q changed:%v pending:%v", recovered != nil, mode, changed, session.pending != nil)
48
+ }
49
+ assertServeShardFactsMatchFullDump(t, session)
50
+ }
@@ -0,0 +1,65 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeShardsPruneUnreferencedExternalEndpoints verifies an external leaf
10
+ // leaves both resident endpoint indexes when its last owning edge disappears.
11
+ func TestServeShardsPruneUnreferencedExternalEndpoints(t *testing.T) {
12
+ root := t.TempDir()
13
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
14
+ "compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
15
+ "include": ["src"]
16
+ }`)
17
+ writeGraphFile(t, filepath.Join(root, "src", "api.d.ts"), "export declare function external(): number;\n")
18
+ index := filepath.Join(root, "src", "index.ts")
19
+ writeGraphFile(t, index, "import { external } from './api';\nexport function run(): number { return external(); }\n")
20
+
21
+ session, err := newGraphSession(root, "tsconfig.json")
22
+ if err != nil {
23
+ t.Fatal(err)
24
+ }
25
+ defer session.Close()
26
+ if snapshot, _, _, err := session.SnapshotShards(); err != nil || snapshot == nil {
27
+ t.Fatalf("initial shard snapshot = snapshot:%v error:%v", snapshot != nil, err)
28
+ }
29
+ source := session.compiler.Program().SourceFile(index)
30
+ if source == nil {
31
+ t.Fatal("fixture source was absent from resident program")
32
+ }
33
+ sourceKey := session.graphStore.sourceKeys[source.FileName()]
34
+ var externalID string
35
+ for id := range session.graphStore.sourceExternal[sourceKey] {
36
+ externalID = id
37
+ break
38
+ }
39
+ if externalID == "" {
40
+ t.Fatal("fixture did not produce an external endpoint")
41
+ }
42
+
43
+ if err := os.WriteFile(index, []byte("import { external } from './api';\nexport function run(): number { return 1; }\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("external endpoint edit = snapshot:%v mode:%q changed:%v", snapshot != nil, mode, changed)
52
+ }
53
+ if session.graphStore.externalReferences[externalID] != 0 {
54
+ t.Fatalf("external endpoint %s retained a reference count", externalID)
55
+ }
56
+ if _, exists := session.graphStore.externalNodes[externalID]; exists {
57
+ t.Fatalf("external endpoint %s remained in the wire endpoint cache", externalID)
58
+ }
59
+ for id, node := range session.graphStore.nodes {
60
+ if node.External {
61
+ t.Fatalf("external endpoint %s remained in the internal endpoint cache as %s", externalID, id)
62
+ }
63
+ }
64
+ assertServeShardFactsMatchFullDump(t, session)
65
+ }
@@ -0,0 +1,92 @@
1
+ package main
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+ )
7
+
8
+ // TestServeShardsPublishCreatedSource verifies a source that appears while the
9
+ // session is resident enters the generation as its own shard and unblocks the
10
+ // dependents that could not resolve it before.
11
+ //
12
+ // Deletion is proven explicitly, but its mirror is the case where a wrong
13
+ // closure is invisible: the consumer holds a graph that simply lacks a file,
14
+ // and an agent asking about it is told the symbol does not exist rather than
15
+ // receiving an error. The dependent must also refresh, because the import that
16
+ // previously failed to resolve now produces real cross-file facts.
17
+ //
18
+ // 1. Commit a project whose consumer imports a module that does not exist yet.
19
+ // 2. Create that module and request another shard snapshot.
20
+ // 3. Require a shard for the new source and a replaced shard for its dependent.
21
+ func TestServeShardsPublishCreatedSource(t *testing.T) {
22
+ root := t.TempDir()
23
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
24
+ "compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
25
+ "include": ["src"]
26
+ }`)
27
+ consumerFile := filepath.Join(root, "src", "consumer.ts")
28
+ writeGraphFile(t, consumerFile, "import { later } from './later';\nexport function consume(): number { return later(); }\n")
29
+
30
+ session, err := newGraphSession(root, "tsconfig.json")
31
+ if err != nil {
32
+ t.Fatal(err)
33
+ }
34
+ defer session.Close()
35
+ initial, mode, changed, err := session.SnapshotShards()
36
+ if err != nil {
37
+ t.Fatal(err)
38
+ }
39
+ if initial == nil || mode != serveModeInitial || !changed {
40
+ t.Fatalf("initial snapshot = snapshot:%v mode:%q changed:%v", initial != nil, mode, changed)
41
+ }
42
+ consumerSource := session.compiler.Program().SourceFile(consumerFile)
43
+ if consumerSource == nil {
44
+ t.Fatal("fixture source was absent from resident program")
45
+ }
46
+ consumerKeyFile := consumerSource.FileName()
47
+ initialConsumerKey := session.graphStore.sourceKeys[consumerKeyFile]
48
+ if initialConsumerKey == "" {
49
+ t.Fatal("initial generation omitted the dependent source shard")
50
+ }
51
+
52
+ createdFile := filepath.Join(root, "src", "later.ts")
53
+ writeGraphFile(t, createdFile, "export function later(): number { return 7; }\n")
54
+
55
+ next, mode, changed, err := session.SnapshotShards()
56
+ if err != nil {
57
+ t.Fatal(err)
58
+ }
59
+ if next == nil || !changed || next.BaseGeneration != initial.Generation {
60
+ t.Fatalf("created-source generation = snapshot:%#v mode:%q changed:%v", next, mode, changed)
61
+ }
62
+ createdSource := session.compiler.Program().SourceFile(createdFile)
63
+ if createdSource == nil {
64
+ t.Fatal("created source did not enter the resident program")
65
+ }
66
+ createdKey := session.graphStore.sourceKeys[createdSource.FileName()]
67
+ if createdKey == "" {
68
+ t.Fatal("created source did not acquire a committed shard identity")
69
+ }
70
+ if !containsUpsertedShardKey(next, createdKey) {
71
+ t.Fatalf("generation did not publish the created source shard %q", createdKey)
72
+ }
73
+ // The dependent's import resolved for the first time, so its cross-file facts
74
+ // changed even though its own text did not. Reusing its prior shard would
75
+ // publish a consumer graph with a call edge to nothing.
76
+ nextConsumerKey := session.graphStore.sourceKeys[consumerKeyFile]
77
+ if nextConsumerKey == initialConsumerKey {
78
+ t.Fatalf("dependent shard identity %q survived a newly resolved import", nextConsumerKey)
79
+ }
80
+ if !containsUpsertedShardKey(next, nextConsumerKey) {
81
+ t.Fatalf("generation did not republish the dependent shard %q", nextConsumerKey)
82
+ }
83
+ }
84
+
85
+ func containsUpsertedShardKey(snapshot *serveGraphSnapshot, expected string) bool {
86
+ for _, upsert := range snapshot.Upserts {
87
+ if upsert.Shard.Key == expected {
88
+ return true
89
+ }
90
+ }
91
+ return false
92
+ }
@@ -0,0 +1,43 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeShardsRebuildDeclarationEdit verifies that a declaration-file
10
+ // movement publishes one honest complete replacement instead of entering the
11
+ // authored-source partial builder with an empty invalidation set.
12
+ func TestServeShardsRebuildDeclarationEdit(t *testing.T) {
13
+ root := t.TempDir()
14
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
15
+ "compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
16
+ "include": ["src"]
17
+ }`)
18
+ declaration := filepath.Join(root, "src", "api.d.ts")
19
+ writeGraphFile(t, declaration, "export declare function external(): number;\n")
20
+ writeGraphFile(t, filepath.Join(root, "src", "index.ts"), "import { external } from './api';\nexport const value = external();\n")
21
+
22
+ session, err := newGraphSession(root, "tsconfig.json")
23
+ if err != nil {
24
+ t.Fatal(err)
25
+ }
26
+ defer session.Close()
27
+ initial, _, _, err := session.SnapshotShards()
28
+ if err != nil || initial == nil {
29
+ t.Fatalf("initial shard snapshot = snapshot:%v error:%v", initial != nil, err)
30
+ }
31
+ if err := os.WriteFile(declaration, []byte("export declare function external(): string;\n"), 0o644); err != nil {
32
+ t.Fatal(err)
33
+ }
34
+
35
+ replacement, mode, changed, err := session.SnapshotShards()
36
+ if err != nil {
37
+ t.Fatal(err)
38
+ }
39
+ if replacement == nil || mode != serveModeRebuild || !changed || replacement.BaseGeneration != initial.Generation {
40
+ t.Fatalf("declaration edit = snapshot:%#v mode:%q changed:%v", replacement, mode, changed)
41
+ }
42
+ assertServeShardFactsMatchFullDump(t, session)
43
+ }