ttsc 0.23.0 → 0.24.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 (46) hide show
  1. package/cmd/ttscgraph/serve.go +264 -48
  2. package/cmd/ttscgraph/serve_early_error_encode_failure_is_observable_test.go +40 -0
  3. package/cmd/ttscgraph/serve_graph_snapshot_version_matches_typescript_client_test.go +32 -0
  4. package/cmd/ttscgraph/serve_phase_trace_accounts_for_load_failure_test.go +45 -0
  5. package/cmd/ttscgraph/serve_phase_trace_is_opt_in_and_payload_free_test.go +52 -0
  6. package/cmd/ttscgraph/serve_protocol_negotiates_incremental_shards_test.go +44 -0
  7. package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +2 -2
  8. package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +46 -20
  9. package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +9 -9
  10. package/cmd/ttscgraph/serve_shards.go +1390 -0
  11. package/cmd/ttscgraph/serve_shards_abandoned_generation_converges_on_retry_test.go +92 -0
  12. package/cmd/ttscgraph/serve_shards_delete_removed_source_test.go +57 -0
  13. package/cmd/ttscgraph/serve_shards_external_endpoints_test.go +53 -0
  14. package/cmd/ttscgraph/serve_shards_keep_source_distributed_dependencies_at_the_boundary_test.go +108 -0
  15. package/cmd/ttscgraph/serve_shards_match_full_dump_oracle_test.go +88 -0
  16. package/cmd/ttscgraph/serve_shards_merge_pending_and_new_edits_test.go +50 -0
  17. package/cmd/ttscgraph/serve_shards_prune_unreferenced_external_endpoints_test.go +65 -0
  18. package/cmd/ttscgraph/serve_shards_publish_created_source_test.go +92 -0
  19. package/cmd/ttscgraph/serve_shards_rebuild_declaration_edit_test.go +43 -0
  20. package/cmd/ttscgraph/serve_shards_refresh_cross_file_implementation_test.go +63 -0
  21. package/cmd/ttscgraph/serve_shards_refresh_dependent_diagnostics_test.go +80 -0
  22. package/cmd/ttscgraph/serve_shards_rename_moves_source_identity_test.go +95 -0
  23. package/cmd/ttscgraph/serve_shards_retry_preserves_committed_generation_test.go +48 -0
  24. package/cmd/ttscgraph/serve_shards_reuse_unaffected_sources_test.go +121 -0
  25. package/driver/declaration_shape.go +85 -0
  26. package/driver/program.go +39 -10
  27. package/internal/graph/build.go +62 -10
  28. package/internal/graph/build_files_reuses_committed_cross_file_endpoints_test.go +87 -0
  29. package/internal/graph/build_keeps_source_distributed_dependencies_at_the_external_boundary_test.go +74 -0
  30. package/internal/graph/dump.go +84 -43
  31. package/internal/graph/dump_path_mapper_caches_alias_resolution_test.go +29 -0
  32. package/internal/graph/dump_paths.go +134 -11
  33. package/internal/graph/dump_paths_canonicalize_windows_short_root_test.go +39 -0
  34. package/internal/graph/dump_rejects_provenance_only_path_errors_test.go +32 -0
  35. package/internal/graph/dump_serializes_the_full_graph_test.go +2 -2
  36. package/internal/graph/dump_symlink_project_publishes_matching_base_test.go +82 -0
  37. package/internal/graph/edges.go +58 -16
  38. package/internal/graph/exports.go +9 -2
  39. package/internal/graph/graph.go +47 -7
  40. package/internal/graph/graph_kind_contracts_match_their_producers_test.go +121 -0
  41. package/internal/graph/node_modifiers_emit_union_strings_test.go +1 -1
  42. package/internal/graph/provenance.go +11 -1
  43. package/internal/graph/resolve.go +1 -4
  44. package/internal/graph/source.go +23 -0
  45. package/internal/graph/wire_node_ids_share_alias_resolution_test.go +29 -0
  46. package/package.json +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
  }
@@ -8,23 +8,53 @@ import (
8
8
  "github.com/samchon/ttsc/packages/ttsc/driver"
9
9
  )
10
10
 
11
- // Build walks the program's user-authored source files and records a node for
12
- // each top-level declaration. driver.SourceFiles already drops declaration files
13
- // and the program never compiles a dependency's `.ts`, so every node Build emits
14
- // is workspace source: External is false. External boundary leaves enter the
15
- // graph only as the resolved target of an edge (see Resolve).
11
+ // Build walks the program's workspace source files and records a node for
12
+ // each top-level declaration. The checker can load a dependency's raw `.ts`
13
+ // entry, so IsWorkspaceSourceFile owns the declaration boundary instead of
14
+ // assuming every non-declaration source is authored here. External boundary
15
+ // leaves enter the graph only as the resolved target of an edge (see Resolve).
16
16
  func Build(prog *driver.Program) *Graph {
17
+ return BuildFiles(prog, nil, nil)
18
+ }
19
+
20
+ // BuildFiles resolves graph facts owned by selected source files against one
21
+ // immutable Program. A nil selection preserves Build's complete-project
22
+ // behavior. A non-nil selection emits only replacement nodes and outgoing
23
+ // facts for those files while using baseNodes as a read-only endpoint index.
24
+ //
25
+ // This is the semantic primitive a resident shard store needs: an ordinary edit
26
+ // asks the new checker only about its invalidated closure instead of walking
27
+ // every declaration again, while cross-file targets retain the exact stable IDs
28
+ // established by the preceding committed generation.
29
+ func BuildFiles(prog *driver.Program, selected []string, baseNodes map[string]*Node) *Graph {
30
+ selectedFiles := map[string]bool{}
31
+ if selected != nil {
32
+ for _, file := range selected {
33
+ selectedFiles[file] = true
34
+ }
35
+ }
17
36
  g := &Graph{
18
- Nodes: map[string]*Node{},
19
- bodyNodes: map[string]bool{},
20
- seen: map[edgeKey]struct{}{},
21
- resolved: map[*shimast.Node]*Target{},
37
+ Nodes: map[string]*Node{},
38
+ bodyNodes: map[string]bool{},
39
+ seen: map[edgeKey]struct{}{},
40
+ resolved: map[*shimast.Node]*Target{},
41
+ edgeEvidenceFiles: map[string]string{},
42
+ baseNodes: baseNodes,
43
+ selectedFiles: selectedFiles,
44
+ ExportedTargets: map[string]bool{},
45
+ ImplementationSources: map[string]map[string]bool{},
22
46
  }
23
47
  for _, file := range prog.SourceFiles() {
48
+ if !IsWorkspaceSourceFile(file) {
49
+ continue
50
+ }
51
+ if selected != nil && !selectedFiles[file.FileName()] {
52
+ continue
53
+ }
24
54
  g.putModuleNode(file)
25
55
  collectDeclarations(g, file)
26
56
  }
27
- g.addEdges(prog)
57
+ g.addEdges(prog, selectedFiles, selected != nil)
28
58
  return g
29
59
  }
30
60
 
@@ -49,6 +79,28 @@ func SourceTexts(prog *driver.Program) map[string]string {
49
79
  return out
50
80
  }
51
81
 
82
+ // SourceTextsForFiles returns resident checker text only for the named source
83
+ // files. It is the partial-build counterpart of SourceTexts: selected graph
84
+ // shards still receive exact evidence and signatures, without walking or
85
+ // retaining every unchanged source body again.
86
+ func SourceTextsForFiles(prog *driver.Program, files []string) map[string]string {
87
+ if prog == nil || prog.TSProgram == nil {
88
+ return map[string]string{}
89
+ }
90
+ _ = prog.ApplyLinkedPlugins()
91
+ selected := make(map[string]bool, len(files))
92
+ for _, file := range files {
93
+ selected[file] = true
94
+ }
95
+ out := make(map[string]string, len(files))
96
+ for _, source := range prog.TSProgram.SourceFiles() {
97
+ if source != nil && selected[source.FileName()] {
98
+ out[source.FileName()] = source.Text()
99
+ }
100
+ }
101
+ return out
102
+ }
103
+
52
104
  // collectDeclarations records a node for each declaration statement in file,
53
105
  // plus a method node for each callable member of a class or interface, so
54
106
  // method-to-method calls have both endpoints. It descends into namespace bodies