ttsc 0.16.3 → 0.16.6

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 (75) hide show
  1. package/README.md +96 -25
  2. package/cmd/graphbench/main.go +135 -0
  3. package/cmd/graphdump/main.go +60 -0
  4. package/cmd/ttscgraph/dump.go +69 -0
  5. package/cmd/ttscgraph/main.go +91 -0
  6. package/cmd/ttscgraph/run_dump_prints_the_graph_as_json_test.go +80 -0
  7. package/cmd/ttscgraph/run_help_version_and_bad_flags_exit_codes_test.go +75 -0
  8. package/cmd/utility-host/main.go +4 -1
  9. package/driver/overlay.go +64 -0
  10. package/driver/program.go +8 -1
  11. package/driver/session.go +85 -0
  12. package/internal/graph/build.go +330 -0
  13. package/internal/graph/build_records_a_node_per_top_level_declaration_test.go +96 -0
  14. package/internal/graph/decorators.go +120 -0
  15. package/internal/graph/decorators_capture_name_and_literal_arguments_test.go +98 -0
  16. package/internal/graph/diagnostics.go +23 -0
  17. package/internal/graph/dump.go +374 -0
  18. package/internal/graph/dump_evidence_skips_leading_trivia_test.go +92 -0
  19. package/internal/graph/dump_serializes_the_full_graph_test.go +146 -0
  20. package/internal/graph/edge_spans_point_to_resolved_usage_test.go +90 -0
  21. package/internal/graph/edges.go +581 -0
  22. package/internal/graph/edges_cover_class_level_references_test.go +93 -0
  23. package/internal/graph/enum_initializer_calls_are_edges_test.go +63 -0
  24. package/internal/graph/exports.go +60 -0
  25. package/internal/graph/file_diagnostics_match_tsgo_code_and_location_test.go +59 -0
  26. package/internal/graph/file_diagnostics_report_code_column_and_message_test.go +69 -0
  27. package/internal/graph/function_locals_do_not_collide_test.go +66 -0
  28. package/internal/graph/gitignore.go +51 -0
  29. package/internal/graph/graph.go +122 -0
  30. package/internal/graph/heritage_edges_cover_implements_and_interface_extends_test.go +76 -0
  31. package/internal/graph/heritage_edges_keep_extends_and_implements_to_same_base_test.go +61 -0
  32. package/internal/graph/heritage_edges_record_extends_versus_implements_origin_test.go +54 -0
  33. package/internal/graph/heritage_edges_traverse_barrels_and_stop_at_external_leaves_test.go +112 -0
  34. package/internal/graph/namespace_dotted_form_qualifies_deeply_test.go +60 -0
  35. package/internal/graph/namespace_members_become_qualified_nodes_and_edges_test.go +100 -0
  36. package/internal/graph/node_identity_is_position_invariant_test.go +84 -0
  37. package/internal/graph/node_modifiers_emit_union_strings_test.go +118 -0
  38. package/internal/graph/nodes_are_marked_exported_through_the_export_table_test.go +61 -0
  39. package/internal/graph/overload_implementation_replaces_signature_test.go +54 -0
  40. package/internal/graph/property_member_edges_remain_owner_visible_test.go +83 -0
  41. package/internal/graph/resolve.go +102 -0
  42. package/internal/graph/resolve_prefers_implementation_declaration_test.go +50 -0
  43. package/internal/graph/resolve_unwraps_barrel_reexport_to_real_declaration_test.go +230 -0
  44. package/internal/graph/type_ref_edges_cover_typeof_as_satisfies_test.go +79 -0
  45. package/internal/graph/type_ref_edges_resolve_named_types_across_files_test.go +52 -0
  46. package/internal/graph/value_access_edges_cover_properties_test.go +96 -0
  47. package/internal/graph/value_call_edges_attribute_function_assignments_to_members_test.go +59 -0
  48. package/internal/graph/value_call_edges_cover_jsx_components_test.go +82 -0
  49. package/internal/graph/value_call_edges_cover_methods_and_constructors_test.go +70 -0
  50. package/internal/graph/value_call_edges_cover_variable_bound_callables_test.go +46 -0
  51. package/internal/graph/value_call_edges_do_not_double_record_invoked_member_access_test.go +65 -0
  52. package/internal/graph/value_call_edges_record_construct_versus_call_origin_test.go +75 -0
  53. package/internal/graph/value_call_edges_resolve_across_files_and_dedupe_test.go +55 -0
  54. package/internal/graph/value_call_edges_skip_self_and_unresolved_test.go +74 -0
  55. package/lib/TtscService.d.ts +55 -0
  56. package/lib/TtscService.js +93 -0
  57. package/lib/TtscService.js.map +1 -0
  58. package/lib/compiler/internal/residentTransformProcess.d.ts +47 -0
  59. package/lib/compiler/internal/residentTransformProcess.js +187 -0
  60. package/lib/compiler/internal/residentTransformProcess.js.map +1 -0
  61. package/lib/compiler/internal/startResidentTransform.d.ts +26 -0
  62. package/lib/compiler/internal/startResidentTransform.js +98 -0
  63. package/lib/compiler/internal/startResidentTransform.js.map +1 -0
  64. package/lib/index.d.ts +1 -0
  65. package/lib/index.js +1 -0
  66. package/lib/index.js.map +1 -1
  67. package/package.json +8 -8
  68. package/shim/ast/shim.go +6 -0
  69. package/shim/checker/shim.go +22 -12
  70. package/src/TtscService.ts +94 -0
  71. package/src/compiler/internal/residentTransformProcess.ts +216 -0
  72. package/src/compiler/internal/startResidentTransform.ts +126 -0
  73. package/src/index.ts +1 -0
  74. package/utility/host.go +8 -2
  75. package/utility/serve.go +158 -0
@@ -0,0 +1,96 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestValueAccessEdgesCoverPropertiesAndAccessors verifies property and
11
+ // accessor reads/writes become value-access edges, not value-call edges.
12
+ //
13
+ // Lazy getters and state properties are part of the runtime flow an agent
14
+ // needs, but they are not invocations. The graph must expose them without
15
+ // corrupting call-flow semantics.
16
+ //
17
+ // 1. Compile a class with a property initializer, getter, setter, dotted access,
18
+ // and string-literal bracket access.
19
+ // 2. Build the graph.
20
+ // 3. Assert property/getter/setter uses are value-access edges while the
21
+ // property initializer's real function call stays a value-call edge.
22
+ func TestValueAccessEdgesCoverPropertiesAndAccessors(t *testing.T) {
23
+ root := t.TempDir()
24
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
25
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export function seed(): string[] {
26
+ return []
27
+ }
28
+
29
+ export class Store {
30
+ protected items: string[] = seed()
31
+
32
+ get count(): number {
33
+ return this.items.length
34
+ }
35
+
36
+ set count(value: number) {
37
+ this.items = Array(value).fill("")
38
+ }
39
+
40
+ read(): number {
41
+ return this.count + this.items.length + this["count"] + this["items"].length
42
+ }
43
+
44
+ write(): void {
45
+ this.count = 1
46
+ this["count"] = 2
47
+ this.items = []
48
+ }
49
+ }
50
+ `)
51
+
52
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
53
+ if err != nil {
54
+ t.Fatal(err)
55
+ }
56
+ if len(diags) != 0 {
57
+ t.Fatalf("unexpected diagnostics: %v", diags)
58
+ }
59
+ defer func() { _ = prog.Close() }()
60
+
61
+ graph := Build(prog)
62
+ path := sourceFile(t, prog, "main.ts").FileName()
63
+ read := nodeID(path, "Store.read", NodeMethod)
64
+ write := nodeID(path, "Store.write", NodeMethod)
65
+ count := nodeID(path, "Store.count", NodeMethod)
66
+ items := nodeID(path, "Store.items", NodeVariable)
67
+ seed := nodeID(path, "seed", NodeFunction)
68
+
69
+ if _, ok := graph.Nodes[items]; !ok {
70
+ t.Fatalf("Build did not record Store.items; have %v", nodeIDSet(graph))
71
+ }
72
+ if !hasEdge(graph, read, count, EdgeValueAccess) {
73
+ t.Fatalf("missing value-access edge Store.read -> Store.count; edges: %v", graph.Edges)
74
+ }
75
+ if !hasEdge(graph, read, items, EdgeValueAccess) {
76
+ t.Fatalf("missing value-access edge Store.read -> Store.items; edges: %v", graph.Edges)
77
+ }
78
+ if !hasEdge(graph, count, items, EdgeValueAccess) {
79
+ t.Fatalf("missing value-access edge Store.count -> Store.items; edges: %v", graph.Edges)
80
+ }
81
+ if !hasEdge(graph, write, count, EdgeValueAccess) {
82
+ t.Fatalf("missing value-access edge Store.write -> Store.count; edges: %v", graph.Edges)
83
+ }
84
+ if !hasEdge(graph, write, items, EdgeValueAccess) {
85
+ t.Fatalf("missing value-access edge Store.write -> Store.items; edges: %v", graph.Edges)
86
+ }
87
+ if !hasEdge(graph, items, seed, EdgeValueCall) {
88
+ t.Fatalf("missing value-call edge Store.items -> seed; edges: %v", graph.Edges)
89
+ }
90
+ if hasEdge(graph, read, count, EdgeValueCall) {
91
+ t.Fatalf("getter read was also recorded as value-call Store.read -> Store.count; edges: %v", graph.Edges)
92
+ }
93
+ if hasEdge(graph, write, count, EdgeValueCall) {
94
+ t.Fatalf("setter write was also recorded as value-call Store.write -> Store.count; edges: %v", graph.Edges)
95
+ }
96
+ }
@@ -0,0 +1,59 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestValueCallEdgesAttributeFunctionAssignmentsToMembers verifies function
11
+ // assignments to resolved members are walked as that member's implementation.
12
+ //
13
+ // Libraries sometimes attach methods by assigning an arrow/function expression
14
+ // to a typed property instead of declaring a class method body. The class-level
15
+ // walk sees the assignment, but graph flow still needs the assigned member node
16
+ // to own calls made inside the RHS function.
17
+ //
18
+ // 1. Compile a fixture where `inst.run = () => helper()` assigns a typed class
19
+ // method implementation.
20
+ // 2. Build the graph.
21
+ // 3. Assert the `Service.run` method has a value-call edge to `helper`.
22
+ func TestValueCallEdgesAttributeFunctionAssignmentsToMembers(t *testing.T) {
23
+ root := t.TempDir()
24
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
25
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export function helper(): void {}
26
+ export class Service {
27
+ run(): void {}
28
+ }
29
+ export function install(inst: Service): void {
30
+ inst.run = (): void => {
31
+ helper();
32
+ };
33
+ }
34
+ `)
35
+
36
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
37
+ if err != nil {
38
+ t.Fatal(err)
39
+ }
40
+ if len(diags) != 0 {
41
+ t.Fatalf("unexpected diagnostics: %v", diags)
42
+ }
43
+ defer func() { _ = prog.Close() }()
44
+
45
+ graph := Build(prog)
46
+ path := sourceFile(t, prog, "main.ts").FileName()
47
+
48
+ run := nodeID(path, "Service.run", NodeMethod)
49
+ helper := nodeID(path, "helper", NodeFunction)
50
+
51
+ if !hasEdge(graph, run, helper, EdgeValueCall) {
52
+ t.Fatalf("missing value-call edge Service.run -> helper (assigned implementation); edges: %v", graph.Edges)
53
+ }
54
+ if graph.Nodes[run].ImplementationFile != path ||
55
+ graph.Nodes[run].ImplementationPos <= 0 ||
56
+ graph.Nodes[run].ImplementationEnd <= graph.Nodes[run].ImplementationPos {
57
+ t.Fatalf("Service.run missing assigned implementation span: %+v", graph.Nodes[run])
58
+ }
59
+ }
@@ -0,0 +1,82 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestValueCallEdgesCoverJsxComponents verifies that a JSX component use
11
+ // (`<Child />`) is a value-call edge to the component — the relationship a React
12
+ // codebase is built from, which a call-expression-only walk silently dropped. An
13
+ // intrinsic tag (`<div />`) resolves to nothing and adds no edge, so the walk
14
+ // distinguishes a component use from a host element.
15
+ //
16
+ // 1. Compile a .tsx fixture where Parent renders <Child /> inside a <div>.
17
+ // 2. Build the graph.
18
+ // 3. Assert a Parent -> Child value-call edge exists.
19
+ func TestValueCallEdgesCoverJsxComponents(t *testing.T) {
20
+ root := t.TempDir()
21
+ writeFile(t, filepath.Join(root, "tsconfig.json"), `{
22
+ "compilerOptions": {
23
+ "target": "ES2022",
24
+ "module": "commonjs",
25
+ "strict": true,
26
+ "jsx": "preserve",
27
+ "rootDir": "src",
28
+ "outDir": "dist"
29
+ },
30
+ "files": ["src/main.tsx"]
31
+ }
32
+ `)
33
+ writeFile(t, filepath.Join(root, "src", "main.tsx"), `declare namespace JSX {
34
+ interface Element {}
35
+ interface IntrinsicElements {
36
+ div: {};
37
+ }
38
+ }
39
+ export function Child(): JSX.Element {
40
+ return null as unknown as JSX.Element;
41
+ }
42
+ export function Parent(): JSX.Element {
43
+ return (
44
+ <div>
45
+ <Child />
46
+ </div>
47
+ );
48
+ }
49
+ `)
50
+
51
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
52
+ if err != nil {
53
+ t.Fatal(err)
54
+ }
55
+ if len(diags) != 0 {
56
+ t.Fatalf("unexpected diagnostics: %v", diags)
57
+ }
58
+ defer func() { _ = prog.Close() }()
59
+
60
+ graph := Build(prog)
61
+ path := sourceFile(t, prog, "main.tsx").FileName()
62
+
63
+ parent := nodeID(path, "Parent", NodeFunction)
64
+ child := nodeID(path, "Child", NodeFunction)
65
+
66
+ if !hasEdge(graph, parent, child, EdgeValueCall) {
67
+ t.Fatalf("missing value-call edge Parent -> Child (JSX component use); edges: %v", graph.Edges)
68
+ }
69
+
70
+ // The intrinsic `<div>` host element resolves to nothing and must add no edge.
71
+ // Without this negative twin, a regression that started resolving intrinsic
72
+ // tags would fill every React codebase's call graph with host-element noise
73
+ // while the positive assertion above still passed.
74
+ for _, edge := range graph.Edges {
75
+ if edge.From != parent {
76
+ continue
77
+ }
78
+ if to := graph.Nodes[edge.To]; to != nil && to.Name == "div" {
79
+ t.Fatalf("intrinsic <div> produced a spurious value-call edge from Parent: %v", edge)
80
+ }
81
+ }
82
+ }
@@ -0,0 +1,70 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestValueCallEdgesCoverMethodsAndConstructors verifies that the value-call walk
11
+ // reaches what the checker resolves but the first cut dropped: a method-to-method
12
+ // call lands on the callee's method node, and a `new T()` lands on T's class node,
13
+ // both attributed to the calling method.
14
+ //
15
+ // Before method nodes existed, a callee that resolved to a method symbol was
16
+ // dropped (no node to point at) and a `new` expression was never visited, so a
17
+ // class whose logic lived in method bodies showed almost no outgoing edges. This
18
+ // pins that gap closed — the load-bearing reason a model could trust the graph
19
+ // for an architecture question instead of re-reading the source.
20
+ //
21
+ // 1. Compile a fixture where Controller.handle calls Service.run (via a parameter
22
+ // and a constructed value) and constructs a Service.
23
+ // 2. Build the graph.
24
+ // 3. Assert handle -> Service.run (value-call, deduped) and handle -> Service
25
+ // (the new-expression constructor edge) both exist.
26
+ func TestValueCallEdgesCoverMethodsAndConstructors(t *testing.T) {
27
+ root := t.TempDir()
28
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
29
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export class Service {
30
+ run(): void {}
31
+ }
32
+ export class Controller {
33
+ handle(s: Service): void {
34
+ s.run();
35
+ const made = new Service();
36
+ made.run();
37
+ }
38
+ }
39
+ `)
40
+
41
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
42
+ if err != nil {
43
+ t.Fatal(err)
44
+ }
45
+ if len(diags) != 0 {
46
+ t.Fatalf("unexpected diagnostics: %v", diags)
47
+ }
48
+ defer func() { _ = prog.Close() }()
49
+
50
+ graph := Build(prog)
51
+ path := sourceFile(t, prog, "main.ts").FileName()
52
+
53
+ handle := nodeID(path, "Controller.handle", NodeMethod)
54
+ run := nodeID(path, "Service.run", NodeMethod)
55
+ service := nodeID(path, "Service", NodeClass)
56
+
57
+ // The callee method node both endpoints need must exist as a real node.
58
+ if _, ok := graph.Nodes[run]; !ok {
59
+ t.Fatalf("Build did not record the method node Service.run; have %v", nodeIDSet(graph))
60
+ }
61
+ // Method-to-method: handle calls Service.run (from s.run() and made.run(),
62
+ // deduped to a single edge) — the call the first cut dropped.
63
+ if !hasEdge(graph, handle, run, EdgeValueCall) {
64
+ t.Fatalf("missing value-call edge Controller.handle -> Service.run; edges: %v", graph.Edges)
65
+ }
66
+ // Constructor: `new Service()` is a value-call from handle to the class node.
67
+ if !hasEdge(graph, handle, service, EdgeValueCall) {
68
+ t.Fatalf("missing value-call edge Controller.handle -> Service (new); edges: %v", graph.Edges)
69
+ }
70
+ }
@@ -0,0 +1,46 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestValueCallEdgesCoverVariableBoundCallables verifies that a call made inside a
11
+ // top-level variable-bound function (a `const fn = () => …`) is an edge from that
12
+ // variable node. The first cut never walked a variable initializer, so an
13
+ // arrow-const that called a function produced no edge — a common modern-TS shape
14
+ // the graph silently missed.
15
+ //
16
+ // 1. Compile a fixture where `const handler = () => { helper(); }`.
17
+ // 2. Build the graph.
18
+ // 3. Assert a handler -> helper value-call edge exists.
19
+ func TestValueCallEdgesCoverVariableBoundCallables(t *testing.T) {
20
+ root := t.TempDir()
21
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
22
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export function helper(): void {}
23
+ export const handler = (): void => {
24
+ helper();
25
+ };
26
+ `)
27
+
28
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
29
+ if err != nil {
30
+ t.Fatal(err)
31
+ }
32
+ if len(diags) != 0 {
33
+ t.Fatalf("unexpected diagnostics: %v", diags)
34
+ }
35
+ defer func() { _ = prog.Close() }()
36
+
37
+ graph := Build(prog)
38
+ path := sourceFile(t, prog, "main.ts").FileName()
39
+
40
+ handler := nodeID(path, "handler", NodeVariable)
41
+ helper := nodeID(path, "helper", NodeFunction)
42
+
43
+ if !hasEdge(graph, handler, helper, EdgeValueCall) {
44
+ t.Fatalf("missing value-call edge handler -> helper (variable-bound callable body); edges: %v", graph.Edges)
45
+ }
46
+ }
@@ -0,0 +1,65 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestValueCallEdgesDoNotDoubleRecordInvokedMemberAccess verifies a property
11
+ // access used as an invocation target remains only a value-call edge.
12
+ //
13
+ // Splitting value-call from value-access is useful only if method calls and
14
+ // tagged-template calls do not also look like plain property reads. Otherwise MCP
15
+ // flow ranking cannot tell invoked behavior from state/accessor evidence.
16
+ //
17
+ // 1. Compile method, constructor, and tagged-template calls whose callee/tag is
18
+ // a property access expression.
19
+ // 2. Build the graph.
20
+ // 3. Assert each target has a value-call edge and no duplicate value-access
21
+ // edge from the same caller.
22
+ func TestValueCallEdgesDoNotDoubleRecordInvokedMemberAccess(t *testing.T) {
23
+ root := t.TempDir()
24
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
25
+ writeFile(t, filepath.Join(root, "src", "main.ts"), "export namespace Providers {\n"+
26
+ " export class Service {\n"+
27
+ " run(): void {}\n"+
28
+ " }\n"+
29
+ "}\n"+
30
+ "export namespace Tags {\n"+
31
+ " export function html(strings: TemplateStringsArray): string {\n"+
32
+ " return strings[0]\n"+
33
+ " }\n"+
34
+ "}\n"+
35
+ "export function handle(service: Providers.Service): string {\n"+
36
+ " service.run()\n"+
37
+ " new Providers.Service()\n"+
38
+ " return Tags.html`ok`\n"+
39
+ "}\n")
40
+
41
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
42
+ if err != nil {
43
+ t.Fatal(err)
44
+ }
45
+ if len(diags) != 0 {
46
+ t.Fatalf("unexpected diagnostics: %v", diags)
47
+ }
48
+ defer func() { _ = prog.Close() }()
49
+
50
+ graph := Build(prog)
51
+ path := sourceFile(t, prog, "main.ts").FileName()
52
+ handle := nodeID(path, "handle", NodeFunction)
53
+ run := nodeID(path, "Providers.Service.run", NodeMethod)
54
+ service := nodeID(path, "Providers.Service", NodeClass)
55
+ html := nodeID(path, "Tags.html", NodeFunction)
56
+
57
+ for _, target := range []string{run, service, html} {
58
+ if !hasEdge(graph, handle, target, EdgeValueCall) {
59
+ t.Fatalf("missing value-call edge handle -> %s; edges: %v", target, graph.Edges)
60
+ }
61
+ if hasEdge(graph, handle, target, EdgeValueAccess) {
62
+ t.Fatalf("invoked member access was also recorded as value-access handle -> %s; edges: %v", target, graph.Edges)
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,75 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestValueCallEdgesRecordConstructVersusCallOrigin verifies that a value-call
11
+ // edge carries the syntactic Origin the dump splits into the schema's `calls`
12
+ // versus `instantiates` kinds: a plain call records "call", a `new T()` records
13
+ // "new".
14
+ //
15
+ // The internal model keeps one EdgeValueCall kind so the existing MCP stays
16
+ // untouched; the finer distinction the redesigned graph schema requires
17
+ // (`new Foo()` is an instantiation, not a call) rides on Edge.Origin. Without it
18
+ // the dump could not recover a constructor from an invocation after the fact, so
19
+ // this pins the discriminator at its source.
20
+ //
21
+ // 1. Compile a fixture where Controller.handle both calls Service.run and
22
+ // constructs `new Service()`.
23
+ // 2. Build the graph.
24
+ // 3. Assert the handle->Service.run edge has Origin "call" and the
25
+ // handle->Service edge has Origin "new".
26
+ func TestValueCallEdgesRecordConstructVersusCallOrigin(t *testing.T) {
27
+ root := t.TempDir()
28
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
29
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export class Service {
30
+ run(): void {}
31
+ }
32
+ export class Controller {
33
+ handle(s: Service): void {
34
+ s.run();
35
+ const made = new Service();
36
+ void made;
37
+ }
38
+ }
39
+ `)
40
+
41
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
42
+ if err != nil {
43
+ t.Fatal(err)
44
+ }
45
+ if len(diags) != 0 {
46
+ t.Fatalf("unexpected diagnostics: %v", diags)
47
+ }
48
+ defer func() { _ = prog.Close() }()
49
+
50
+ graph := Build(prog)
51
+ path := sourceFile(t, prog, "main.ts").FileName()
52
+
53
+ handle := nodeID(path, "Controller.handle", NodeMethod)
54
+ run := nodeID(path, "Service.run", NodeMethod)
55
+ service := nodeID(path, "Service", NodeClass)
56
+
57
+ if got := edgeOrigin(graph, handle, run, EdgeValueCall); got != "call" {
58
+ t.Fatalf("Controller.handle -> Service.run: want Origin \"call\", got %q; edges: %v", got, graph.Edges)
59
+ }
60
+ if got := edgeOrigin(graph, handle, service, EdgeValueCall); got != "new" {
61
+ t.Fatalf("Controller.handle -> Service (new): want Origin \"new\", got %q; edges: %v", got, graph.Edges)
62
+ }
63
+ }
64
+
65
+ // edgeOrigin returns the Origin of the first from->to edge of kind, or the
66
+ // sentinel "<missing>" when no such edge exists, so a test distinguishes a wrong
67
+ // origin from an absent edge.
68
+ func edgeOrigin(graph *Graph, from, to string, kind EdgeKind) string {
69
+ for _, edge := range graph.Edges {
70
+ if edge.From == from && edge.To == to && edge.Kind == kind {
71
+ return edge.Origin
72
+ }
73
+ }
74
+ return "<missing>"
75
+ }
@@ -0,0 +1,55 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestValueCallEdgesResolveAcrossFilesAndDedupe verifies that a runtime call is
11
+ // recorded as a single value-call edge to the callee's real declaration, even
12
+ // when the caller invokes it from several sites. Cross-file resolution proves the
13
+ // edge rides the checker; the dedup keeps the call graph one-edge-per-pair so an
14
+ // impact query is not skewed by how many times a function is called.
15
+ //
16
+ // 1. Compile a fixture where caller() calls helper() (declared in another file)
17
+ // twice.
18
+ // 2. Build the graph.
19
+ // 3. Assert exactly one caller -> helper value-call edge.
20
+ func TestValueCallEdgesResolveAcrossFilesAndDedupe(t *testing.T) {
21
+ root := t.TempDir()
22
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
23
+ writeFile(t, filepath.Join(root, "src", "util.ts"), `export function helper(): number {
24
+ return 1;
25
+ }
26
+ `)
27
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `import { helper } from "./util";
28
+ export function caller(): number {
29
+ return helper() + helper();
30
+ }
31
+ `)
32
+
33
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
34
+ if err != nil {
35
+ t.Fatal(err)
36
+ }
37
+ if len(diags) != 0 {
38
+ t.Fatalf("unexpected diagnostics: %v", diags)
39
+ }
40
+ defer func() { _ = prog.Close() }()
41
+
42
+ graph := Build(prog)
43
+ caller := nodeID(sourceFile(t, prog, "main.ts").FileName(), "caller", NodeFunction)
44
+ helper := nodeID(sourceFile(t, prog, "util.ts").FileName(), "helper", NodeFunction)
45
+
46
+ count := 0
47
+ for _, edge := range graph.Edges {
48
+ if edge.From == caller && edge.To == helper && edge.Kind == EdgeValueCall {
49
+ count++
50
+ }
51
+ }
52
+ if count != 1 {
53
+ t.Fatalf("expected exactly one caller -> helper value-call edge, got %d; edges: %v", count, graph.Edges)
54
+ }
55
+ }
@@ -0,0 +1,74 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestValueCallEdgesSkipSelfAndUnresolved verifies that callEdge drops the two
11
+ // edges it is documented to skip while still recording an ordinary cross-call:
12
+ // a self-call (`rec` calling `rec`) yields no edge because `to == from`, and a
13
+ // call whose callee the checker cannot bind to a declaration (a method on an
14
+ // `any`-typed value) yields no edge because Resolve returns nil.
15
+ //
16
+ // The positive caller->helper edge is the load-bearing twin: without it the test
17
+ // would also pass if callEdge recorded nothing at all, so it proves the two skips
18
+ // are specific to a self-call and an unresolved callee, not a blanket failure to
19
+ // emit value-call edges.
20
+ //
21
+ // 1. Compile a fixture with `rec()` returning `rec()`, `caller()` calling a
22
+ // separate `helper()`, and `dynamic(host: any)` calling `host.run()`.
23
+ // 2. Build the graph.
24
+ // 3. Assert no rec->rec edge and no edge out of dynamic, but a caller->helper
25
+ // value-call edge.
26
+ func TestValueCallEdgesSkipSelfAndUnresolved(t *testing.T) {
27
+ root := t.TempDir()
28
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
29
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export function rec(): unknown {
30
+ return rec();
31
+ }
32
+ export function helper(): void {}
33
+ export function caller(): void {
34
+ helper();
35
+ }
36
+ export function dynamic(host: any): void {
37
+ host.run();
38
+ }
39
+ `)
40
+
41
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
42
+ if err != nil {
43
+ t.Fatal(err)
44
+ }
45
+ if len(diags) != 0 {
46
+ t.Fatalf("unexpected diagnostics: %v", diags)
47
+ }
48
+ defer func() { _ = prog.Close() }()
49
+
50
+ graph := Build(prog)
51
+ path := sourceFile(t, prog, "main.ts").FileName()
52
+
53
+ rec := nodeID(path, "rec", NodeFunction)
54
+ helper := nodeID(path, "helper", NodeFunction)
55
+ caller := nodeID(path, "caller", NodeFunction)
56
+ dynamic := nodeID(path, "dynamic", NodeFunction)
57
+
58
+ // Self-call: callEdge skips `to == from`, so rec never points at itself.
59
+ if hasEdge(graph, rec, rec, EdgeValueCall) {
60
+ t.Fatalf("a self-call recorded a rec -> rec value-call edge; edges: %v", graph.Edges)
61
+ }
62
+ // Unresolved callee: `host.run()` binds to no declaration, so dynamic gains no
63
+ // outgoing value-call edge.
64
+ for _, edge := range graph.Edges {
65
+ if edge.From == dynamic && edge.Kind == EdgeValueCall {
66
+ t.Fatalf("an unresolved callee recorded a value-call edge out of dynamic: %+v", edge)
67
+ }
68
+ }
69
+ // Positive twin: an ordinary cross-call is still recorded, proving the skips
70
+ // above are specific and not a blanket suppression.
71
+ if !hasEdge(graph, caller, helper, EdgeValueCall) {
72
+ t.Fatalf("missing value-call edge caller -> helper; edges: %v", graph.Edges)
73
+ }
74
+ }
@@ -0,0 +1,55 @@
1
+ import type { ITtscCompilerContext } from "./structures/ITtscCompilerContext";
2
+ /**
3
+ * Resident, incremental transform service for the `ttsc` TypeScript-Go
4
+ * pipeline.
5
+ *
6
+ * Where {@link TtscCompiler.transform} spawns a fresh process and recompiles the
7
+ * whole project on every call, `TtscService` keeps one long-lived host warm: it
8
+ * compiles the project once and then answers per-file transform requests from
9
+ * that warm program. A single service instance transforms many files (a watch
10
+ * server, an editor session, a codegen tool) while paying the project compile
11
+ * once instead of once per file. Sharing one host across separate worker
12
+ * processes (a Metro worker pool) is tracked in samchon/ttsc#255.
13
+ *
14
+ * The shape mirrors a legacy TypeScript `LanguageService`: construct it against
15
+ * a project context, ask it to transform individual files, and dispose it when
16
+ * done. Construction is synchronous (it launches the host); the host compiles
17
+ * in the background, so the first {@link transformFile} resolves once that
18
+ * compile lands.
19
+ *
20
+ * Resident mode runs through the linked-plugin shared host, so the project must
21
+ * declare at least one transform-stage plugin; the constructor throws
22
+ * otherwise. It does not run check-stage plugins (unlike
23
+ * {@link TtscCompiler.transform}); only the program's own type-checking gates an
24
+ * {@link updateFile}.
25
+ */
26
+ export declare class TtscService {
27
+ private readonly resident;
28
+ private readonly projectRoot;
29
+ /**
30
+ * Create a service bound to the given project context and launch its resident
31
+ * host. The context is the same shape {@link TtscCompiler} accepts; it is not
32
+ * replaceable per call.
33
+ */
34
+ constructor(context?: ITtscCompilerContext);
35
+ /**
36
+ * Return the transformed TypeScript source for one file, or `undefined` when
37
+ * the resident program does not contain it (for example a file excluded from
38
+ * the tsconfig). A relative `fileName` is resolved against the project root.
39
+ *
40
+ * Rejects when the resident host failed to compile the project; the rejection
41
+ * carries the host's diagnostics so callers can surface a real build error.
42
+ */
43
+ transformFile(fileName: string): Promise<string | undefined>;
44
+ /**
45
+ * Apply new in-memory content for one file and re-transform the project, so a
46
+ * subsequent {@link transformFile} reflects the edit without restarting the
47
+ * host. Returns whether the re-transform succeeded; `false` means the edit
48
+ * did not compile and the previous transform is still in effect. A relative
49
+ * `fileName` is resolved against the project root.
50
+ */
51
+ updateFile(fileName: string, content: string): Promise<boolean>;
52
+ /** Terminate the resident host and reject any in-flight requests. */
53
+ dispose(): void;
54
+ private absolutePath;
55
+ }