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.
- package/README.md +96 -25
- package/cmd/graphbench/main.go +135 -0
- package/cmd/graphdump/main.go +60 -0
- package/cmd/ttscgraph/dump.go +69 -0
- package/cmd/ttscgraph/main.go +91 -0
- package/cmd/ttscgraph/run_dump_prints_the_graph_as_json_test.go +80 -0
- package/cmd/ttscgraph/run_help_version_and_bad_flags_exit_codes_test.go +75 -0
- package/cmd/utility-host/main.go +4 -1
- package/driver/overlay.go +64 -0
- package/driver/program.go +8 -1
- package/driver/session.go +85 -0
- package/internal/graph/build.go +330 -0
- package/internal/graph/build_records_a_node_per_top_level_declaration_test.go +96 -0
- package/internal/graph/decorators.go +120 -0
- package/internal/graph/decorators_capture_name_and_literal_arguments_test.go +98 -0
- package/internal/graph/diagnostics.go +23 -0
- package/internal/graph/dump.go +374 -0
- package/internal/graph/dump_evidence_skips_leading_trivia_test.go +92 -0
- package/internal/graph/dump_serializes_the_full_graph_test.go +146 -0
- package/internal/graph/edge_spans_point_to_resolved_usage_test.go +90 -0
- package/internal/graph/edges.go +581 -0
- package/internal/graph/edges_cover_class_level_references_test.go +93 -0
- package/internal/graph/enum_initializer_calls_are_edges_test.go +63 -0
- package/internal/graph/exports.go +60 -0
- package/internal/graph/file_diagnostics_match_tsgo_code_and_location_test.go +59 -0
- package/internal/graph/file_diagnostics_report_code_column_and_message_test.go +69 -0
- package/internal/graph/function_locals_do_not_collide_test.go +66 -0
- package/internal/graph/gitignore.go +51 -0
- package/internal/graph/graph.go +122 -0
- package/internal/graph/heritage_edges_cover_implements_and_interface_extends_test.go +76 -0
- package/internal/graph/heritage_edges_keep_extends_and_implements_to_same_base_test.go +61 -0
- package/internal/graph/heritage_edges_record_extends_versus_implements_origin_test.go +54 -0
- package/internal/graph/heritage_edges_traverse_barrels_and_stop_at_external_leaves_test.go +112 -0
- package/internal/graph/namespace_dotted_form_qualifies_deeply_test.go +60 -0
- package/internal/graph/namespace_members_become_qualified_nodes_and_edges_test.go +100 -0
- package/internal/graph/node_identity_is_position_invariant_test.go +84 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +118 -0
- package/internal/graph/nodes_are_marked_exported_through_the_export_table_test.go +61 -0
- package/internal/graph/overload_implementation_replaces_signature_test.go +54 -0
- package/internal/graph/property_member_edges_remain_owner_visible_test.go +83 -0
- package/internal/graph/resolve.go +102 -0
- package/internal/graph/resolve_prefers_implementation_declaration_test.go +50 -0
- package/internal/graph/resolve_unwraps_barrel_reexport_to_real_declaration_test.go +230 -0
- package/internal/graph/type_ref_edges_cover_typeof_as_satisfies_test.go +79 -0
- package/internal/graph/type_ref_edges_resolve_named_types_across_files_test.go +52 -0
- package/internal/graph/value_access_edges_cover_properties_test.go +96 -0
- package/internal/graph/value_call_edges_attribute_function_assignments_to_members_test.go +59 -0
- package/internal/graph/value_call_edges_cover_jsx_components_test.go +82 -0
- package/internal/graph/value_call_edges_cover_methods_and_constructors_test.go +70 -0
- package/internal/graph/value_call_edges_cover_variable_bound_callables_test.go +46 -0
- package/internal/graph/value_call_edges_do_not_double_record_invoked_member_access_test.go +65 -0
- package/internal/graph/value_call_edges_record_construct_versus_call_origin_test.go +75 -0
- package/internal/graph/value_call_edges_resolve_across_files_and_dedupe_test.go +55 -0
- package/internal/graph/value_call_edges_skip_self_and_unresolved_test.go +74 -0
- package/lib/TtscService.d.ts +55 -0
- package/lib/TtscService.js +93 -0
- package/lib/TtscService.js.map +1 -0
- package/lib/compiler/internal/residentTransformProcess.d.ts +47 -0
- package/lib/compiler/internal/residentTransformProcess.js +187 -0
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -0
- package/lib/compiler/internal/startResidentTransform.d.ts +26 -0
- package/lib/compiler/internal/startResidentTransform.js +98 -0
- package/lib/compiler/internal/startResidentTransform.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/package.json +8 -8
- package/shim/ast/shim.go +6 -0
- package/shim/checker/shim.go +22 -12
- package/src/TtscService.ts +94 -0
- package/src/compiler/internal/residentTransformProcess.ts +216 -0
- package/src/compiler/internal/startResidentTransform.ts +126 -0
- package/src/index.ts +1 -0
- package/utility/host.go +8 -2
- 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
|
+
}
|