ttsc 0.16.2 → 0.16.5

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 (81) 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 +122 -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 +390 -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 +538 -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 +119 -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_cover_jsx_components_test.go +82 -0
  48. package/internal/graph/value_call_edges_cover_methods_and_constructors_test.go +70 -0
  49. package/internal/graph/value_call_edges_cover_variable_bound_callables_test.go +46 -0
  50. package/internal/graph/value_call_edges_do_not_double_record_invoked_member_access_test.go +65 -0
  51. package/internal/graph/value_call_edges_record_construct_versus_call_origin_test.go +75 -0
  52. package/internal/graph/value_call_edges_resolve_across_files_and_dedupe_test.go +55 -0
  53. package/internal/graph/value_call_edges_skip_self_and_unresolved_test.go +74 -0
  54. package/lib/TtscService.d.ts +55 -0
  55. package/lib/TtscService.js +93 -0
  56. package/lib/TtscService.js.map +1 -0
  57. package/lib/compiler/internal/residentTransformProcess.d.ts +47 -0
  58. package/lib/compiler/internal/residentTransformProcess.js +187 -0
  59. package/lib/compiler/internal/residentTransformProcess.js.map +1 -0
  60. package/lib/compiler/internal/startResidentTransform.d.ts +26 -0
  61. package/lib/compiler/internal/startResidentTransform.js +98 -0
  62. package/lib/compiler/internal/startResidentTransform.js.map +1 -0
  63. package/lib/index.d.ts +1 -0
  64. package/lib/index.js +1 -0
  65. package/lib/index.js.map +1 -1
  66. package/lib/plugin/internal/loadProjectPlugins.js +17 -3
  67. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  68. package/lib/structures/ITtscPlugin.d.ts +7 -5
  69. package/lib/structures/ITtscPluginFactoryContext.d.ts +24 -1
  70. package/package.json +8 -8
  71. package/shim/ast/shim.go +6 -0
  72. package/shim/checker/shim.go +22 -12
  73. package/src/TtscService.ts +94 -0
  74. package/src/compiler/internal/residentTransformProcess.ts +216 -0
  75. package/src/compiler/internal/startResidentTransform.ts +126 -0
  76. package/src/index.ts +1 -0
  77. package/src/plugin/internal/loadProjectPlugins.ts +17 -3
  78. package/src/structures/ITtscPlugin.ts +7 -5
  79. package/src/structures/ITtscPluginFactoryContext.ts +26 -1
  80. package/utility/host.go +8 -2
  81. package/utility/serve.go +158 -0
@@ -0,0 +1,112 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "strings"
6
+ "testing"
7
+
8
+ "github.com/samchon/ttsc/packages/ttsc/driver"
9
+ )
10
+
11
+ // TestHeritageEdgesTraverseBarrelsAndStopAtExternalLeaves verifies the two edge
12
+ // behaviors that distinguish a checker-resolved graph from a path-heuristic one:
13
+ // a heritage edge into a barrel-re-exported base lands on the sibling source
14
+ // that declares it (not the index file), and a heritage edge into a dependency
15
+ // becomes an external boundary leaf that the walk does not descend into.
16
+ //
17
+ // 1. Compile a fixture: `Sub extends Base` where Base is re-exported through a
18
+ // barrel, and `SubExt extends Ext` where Ext is declared in a node_modules
19
+ // `.d.ts`.
20
+ // 2. Build the graph.
21
+ // 3. Assert the Sub->Base edge targets the real declaration in impl.ts and is
22
+ // not external, while the SubExt->Ext edge targets an external leaf under
23
+ // node_modules.
24
+ func TestHeritageEdgesTraverseBarrelsAndStopAtExternalLeaves(t *testing.T) {
25
+ root := t.TempDir()
26
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
27
+ writeFile(t, filepath.Join(root, "node_modules", "dep", "package.json"), `{
28
+ "name": "dep",
29
+ "version": "1.0.0",
30
+ "types": "index.d.ts"
31
+ }
32
+ `)
33
+ writeFile(t, filepath.Join(root, "node_modules", "dep", "index.d.ts"), `export declare class Ext {}
34
+ `)
35
+ writeFile(t, filepath.Join(root, "src", "impl.ts"), `export class Base {}
36
+ `)
37
+ writeFile(t, filepath.Join(root, "src", "index.ts"), `export { Base } from "./impl";
38
+ `)
39
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `import { Base } from "./index";
40
+ import { Ext } from "dep";
41
+ export class Sub extends Base {}
42
+ export class SubExt extends Ext {}
43
+ `)
44
+
45
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
46
+ if err != nil {
47
+ t.Fatal(err)
48
+ }
49
+ if len(diags) != 0 {
50
+ t.Fatalf("unexpected diagnostics: %v", diags)
51
+ }
52
+ defer func() { _ = prog.Close() }()
53
+
54
+ graph := Build(prog)
55
+ mainPath := sourceFile(t, prog, "main.ts").FileName()
56
+ implPath := sourceFile(t, prog, "impl.ts").FileName()
57
+
58
+ // Barrel traversal: the base resolved to the sibling source, not the index.
59
+ base := nodeID(implPath, "Base", NodeClass)
60
+ if node, ok := graph.Nodes[base]; !ok || node.External {
61
+ t.Fatalf("Base node missing or misclassified external (barrel not traversed): %+v", node)
62
+ }
63
+ sub := nodeID(mainPath, "Sub", NodeClass)
64
+ if !hasEdge(graph, sub, base, EdgeHeritage) {
65
+ t.Fatalf("missing heritage edge Sub -> Base@impl.ts; edges: %v", graph.Edges)
66
+ }
67
+
68
+ // External leaf: the dependency base is a boundary leaf, kept as a node with
69
+ // its incoming edge but never walked into.
70
+ ext := findNodeByName(graph, "Ext")
71
+ if ext == nil {
72
+ t.Fatal("Ext base was not recorded as a node")
73
+ }
74
+ if !ext.External || !strings.Contains(ext.File, "/node_modules/") {
75
+ t.Fatalf("Ext should be an external node_modules leaf: %+v", ext)
76
+ }
77
+ subExt := nodeID(mainPath, "SubExt", NodeClass)
78
+ if !hasEdge(graph, subExt, ext.ID, EdgeHeritage) {
79
+ t.Fatalf("missing heritage edge SubExt -> Ext (external); edges: %v", graph.Edges)
80
+ }
81
+ }
82
+
83
+ // hasEdge reports whether the graph holds a from->to edge of the given kind.
84
+ func hasEdge(graph *Graph, from, to string, kind EdgeKind) bool {
85
+ for _, edge := range graph.Edges {
86
+ if edge.From == from && edge.To == to && edge.Kind == kind {
87
+ return true
88
+ }
89
+ }
90
+ return false
91
+ }
92
+
93
+ // hasDecorator reports whether the graph recorded a decorator named name on the
94
+ // node target.
95
+ func hasDecorator(graph *Graph, target, name string) bool {
96
+ for _, dec := range graph.Decorators {
97
+ if dec.Target == target && dec.Name == name {
98
+ return true
99
+ }
100
+ }
101
+ return false
102
+ }
103
+
104
+ // findNodeByName returns the first node whose declared name is name, or nil.
105
+ func findNodeByName(graph *Graph, name string) *Node {
106
+ for _, node := range graph.Nodes {
107
+ if node.Name == name {
108
+ return node
109
+ }
110
+ }
111
+ return nil
112
+ }
@@ -0,0 +1,60 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestNamespaceDottedFormQualifiesDeeply covers the chained-module shape that a
11
+ // single-level namespace test does not: `namespace A.B.C { … }` is parsed as a
12
+ // ModuleDeclaration whose body is another ModuleDeclaration, so moduleStatements
13
+ // must descend through the chain and qualifiedName must build the full dotted
14
+ // prefix. A regression in either would silently drop every declaration in a
15
+ // dotted (or deeply nested) namespace — the idiom of .d.ts-heavy and
16
+ // proto-generated codebases.
17
+ func TestNamespaceDottedFormQualifiesDeeply(t *testing.T) {
18
+ root := t.TempDir()
19
+ writeFile(t, filepath.Join(root, "tsconfig.json"), `{
20
+ "compilerOptions": {
21
+ "target": "ES2022",
22
+ "module": "commonjs",
23
+ "strict": true,
24
+ "rootDir": "src",
25
+ "outDir": "dist"
26
+ },
27
+ "files": ["src/main.ts"]
28
+ }
29
+ `)
30
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export namespace A.B.C {
31
+ export function deep(): void {}
32
+ }
33
+
34
+ export function caller(): void {
35
+ A.B.C.deep();
36
+ }
37
+ `)
38
+
39
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
40
+ if err != nil {
41
+ t.Fatal(err)
42
+ }
43
+ if len(diags) != 0 {
44
+ t.Fatalf("unexpected diagnostics: %v", diags)
45
+ }
46
+ defer func() { _ = prog.Close() }()
47
+
48
+ graph := Build(prog)
49
+ path := sourceFile(t, prog, "main.ts").FileName()
50
+
51
+ deep := nodeID(path, "A.B.C.deep", NodeFunction)
52
+ caller := nodeID(path, "caller", NodeFunction)
53
+
54
+ if _, ok := graph.Nodes[deep]; !ok {
55
+ t.Fatalf("missing deeply-namespaced node %q; nodes: %v", deep, graph.Nodes)
56
+ }
57
+ if !hasEdge(graph, caller, deep, EdgeValueCall) {
58
+ t.Fatalf("missing value-call edge caller -> A.B.C.deep; edges: %v", graph.Edges)
59
+ }
60
+ }
@@ -0,0 +1,100 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestNamespaceMembersBecomeQualifiedNodesAndEdges verifies that declarations
11
+ // inside a `namespace` are first-class graph nodes, keyed by their
12
+ // namespace-qualified name, and that edges cross the namespace boundary in both
13
+ // directions. A top-level-statements-only walk recorded none of this: every
14
+ // namespaced declaration and every edge touching one was silently dropped.
15
+ //
16
+ // The fixture exercises four relationships:
17
+ //
18
+ // - intra-namespace call: Service.run -> Service.helper
19
+ // - top-level into a namespace: bootstrap -> Service.run
20
+ // - namespaced method type-ref: Service.Worker.process -> Payload
21
+ // - namespaced type referenced: WorkerRef -> Service.Worker
22
+ func TestNamespaceMembersBecomeQualifiedNodesAndEdges(t *testing.T) {
23
+ root := t.TempDir()
24
+ writeFile(t, filepath.Join(root, "tsconfig.json"), `{
25
+ "compilerOptions": {
26
+ "target": "ES2022",
27
+ "module": "commonjs",
28
+ "strict": true,
29
+ "rootDir": "src",
30
+ "outDir": "dist"
31
+ },
32
+ "files": ["src/main.ts"]
33
+ }
34
+ `)
35
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export interface Payload {}
36
+
37
+ export namespace Service {
38
+ export function helper(): void {}
39
+
40
+ export function run(): void {
41
+ helper();
42
+ }
43
+
44
+ export class Worker {
45
+ process(p: Payload): void {
46
+ void p;
47
+ }
48
+ }
49
+ }
50
+
51
+ export function bootstrap(): void {
52
+ Service.run();
53
+ }
54
+
55
+ export type WorkerRef = Service.Worker;
56
+ `)
57
+
58
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
59
+ if err != nil {
60
+ t.Fatal(err)
61
+ }
62
+ if len(diags) != 0 {
63
+ t.Fatalf("unexpected diagnostics: %v", diags)
64
+ }
65
+ defer func() { _ = prog.Close() }()
66
+
67
+ graph := Build(prog)
68
+ path := sourceFile(t, prog, "main.ts").FileName()
69
+
70
+ serviceHelper := nodeID(path, "Service.helper", NodeFunction)
71
+ serviceRun := nodeID(path, "Service.run", NodeFunction)
72
+ serviceWorker := nodeID(path, "Service.Worker", NodeClass)
73
+ workerProcess := nodeID(path, "Service.Worker.process", NodeMethod)
74
+ bootstrap := nodeID(path, "bootstrap", NodeFunction)
75
+ workerRef := nodeID(path, "WorkerRef", NodeTypeAlias)
76
+ payload := nodeID(path, "Payload", NodeInterface)
77
+
78
+ // The namespaced declarations are nodes in their own right.
79
+ for _, id := range []string{serviceHelper, serviceRun, serviceWorker, workerProcess} {
80
+ if _, ok := graph.Nodes[id]; !ok {
81
+ t.Errorf("missing namespaced node %q", id)
82
+ }
83
+ }
84
+
85
+ if !hasEdge(graph, serviceRun, serviceHelper, EdgeValueCall) {
86
+ t.Errorf("missing value-call edge Service.run -> Service.helper (intra-namespace)")
87
+ }
88
+ if !hasEdge(graph, bootstrap, serviceRun, EdgeValueCall) {
89
+ t.Errorf("missing value-call edge bootstrap -> Service.run (top-level into namespace)")
90
+ }
91
+ if !hasEdge(graph, workerProcess, payload, EdgeTypeRef) {
92
+ t.Errorf("missing type-ref edge Service.Worker.process -> Payload (namespaced method)")
93
+ }
94
+ if !hasEdge(graph, workerRef, serviceWorker, EdgeTypeRef) {
95
+ t.Errorf("missing type-ref edge WorkerRef -> Service.Worker (namespaced type referenced)")
96
+ }
97
+ if t.Failed() {
98
+ t.Logf("nodes: %d, edges: %v", len(graph.Nodes), graph.Edges)
99
+ }
100
+ }
@@ -0,0 +1,84 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestNodeIdentityIsPositionInvariant verifies that a declaration keeps the same
11
+ // node id after lines are inserted above it. A byte-offset or line-number key
12
+ // would re-key every declaration below an edit, forcing a future incremental
13
+ // layer to churn the whole graph; the realpath + name + kind key does not.
14
+ //
15
+ // The probe rewrites the same file with a comment and blank lines prepended,
16
+ // shifting every offset below, and asserts (a) the reload actually changed the
17
+ // source text, so the result is not a stale cached read, and (b) the node-id set
18
+ // is identical across the shift.
19
+ //
20
+ // 1. Compile a fixture, capture its node ids and source text.
21
+ // 2. Rewrite the same file with leading lines that shift every offset.
22
+ // 3. Assert the text changed but the node-id set did not.
23
+ func TestNodeIdentityIsPositionInvariant(t *testing.T) {
24
+ root := t.TempDir()
25
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
26
+ main := filepath.Join(root, "src", "main.ts")
27
+
28
+ writeFile(t, main, `export function fn(): void {}
29
+ export const value = 1;
30
+ `)
31
+ firstIDs, firstText := buildSnapshot(t, root)
32
+
33
+ writeFile(t, main, `// a leading comment that shifts every offset below it
34
+
35
+ export function fn(): void {}
36
+
37
+ export const value = 1;
38
+ `)
39
+ secondIDs, secondText := buildSnapshot(t, root)
40
+
41
+ if firstText == secondText {
42
+ t.Fatal("the reload returned identical source text; the probe is reading a stale cache, not the shifted file")
43
+ }
44
+ if len(firstIDs) != 2 {
45
+ t.Fatalf("expected 2 nodes, got %d: %v", len(firstIDs), firstIDs)
46
+ }
47
+ if !equalStringSets(firstIDs, secondIDs) {
48
+ t.Fatalf("node identity changed when positions shifted:\n first=%v\n second=%v", firstIDs, secondIDs)
49
+ }
50
+ }
51
+
52
+ // buildSnapshot compiles root, then returns the built graph's node-id set and
53
+ // the source text of main.ts so the caller can confirm a reload took effect.
54
+ func buildSnapshot(t *testing.T, root string) (map[string]bool, string) {
55
+ t.Helper()
56
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
57
+ if err != nil {
58
+ t.Fatal(err)
59
+ }
60
+ if len(diags) != 0 {
61
+ t.Fatalf("unexpected diagnostics: %v", diags)
62
+ }
63
+ defer func() { _ = prog.Close() }()
64
+
65
+ text := sourceFile(t, prog, "main.ts").Text()
66
+ ids := map[string]bool{}
67
+ for id := range Build(prog).Nodes {
68
+ ids[id] = true
69
+ }
70
+ return ids, text
71
+ }
72
+
73
+ // equalStringSets reports whether a and b hold the same keys.
74
+ func equalStringSets(a, b map[string]bool) bool {
75
+ if len(a) != len(b) {
76
+ return false
77
+ }
78
+ for key := range a {
79
+ if !b[key] {
80
+ return false
81
+ }
82
+ }
83
+ return true
84
+ }
@@ -0,0 +1,118 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestNodeModifiersEmitUnionStrings verifies that the dump records a declaration's
11
+ // syntactic modifiers as wire strings drawn only from the TtscGraphNodeModifier
12
+ // union, mapping the combined modifier flags of a class, a static/readonly
13
+ // property, an accessibility-qualified async method, and an exported const onto
14
+ // their union members.
15
+ //
16
+ // The TypeScript loader runs `typia.assert<ITtscGraphDump>` over this dump, so a
17
+ // modifier string outside the union would reject the whole graph. The assertion
18
+ // pins both the exact strings emitted and the negative twin — a member with no
19
+ // modifier must carry none — so an over-broad flag mapping cannot slip through.
20
+ //
21
+ // 1. Compile a fixture with `export abstract class`, a `static readonly`
22
+ // property, a `private async` method, a plain method, and `export const enum`
23
+ // (the `const` keyword that is a modifier, unlike a `const` variable).
24
+ // 2. Build and marshal the dump.
25
+ // 3. Assert each node's modifiers are exactly the expected union strings, the
26
+ // plain method has none, and every emitted string is a union member.
27
+ func TestNodeModifiersEmitUnionStrings(t *testing.T) {
28
+ root := t.TempDir()
29
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
30
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export abstract class Service {
31
+ static readonly config: number = 1;
32
+ private async run(): Promise<void> {}
33
+ plain(): void {}
34
+ }
35
+ export const enum Mode {
36
+ On,
37
+ }
38
+ `)
39
+
40
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
41
+ if err != nil {
42
+ t.Fatal(err)
43
+ }
44
+ if len(diags) != 0 {
45
+ t.Fatalf("unexpected diagnostics: %v", diags)
46
+ }
47
+ defer func() { _ = prog.Close() }()
48
+
49
+ g := Build(prog)
50
+ dump := NewDump(g, root, "tsconfig.json", nil, SourceTexts(prog))
51
+
52
+ byID := make(map[string]DumpNode, len(dump.Nodes))
53
+ for _, n := range dump.Nodes {
54
+ byID[n.ID] = n
55
+ }
56
+
57
+ want := map[string][]string{
58
+ "src/main.ts#Service:class": {"export", "abstract"},
59
+ "src/main.ts#Service.config:variable": {"static", "readonly"},
60
+ "src/main.ts#Service.run:method": {"async", "private"},
61
+ "src/main.ts#Mode:enum": {"export", "const"},
62
+ }
63
+ for id, expected := range want {
64
+ node, ok := byID[id]
65
+ if !ok {
66
+ t.Fatalf("missing node %q; have %v", id, dumpNodeIDs(dump))
67
+ }
68
+ if !equalStrings(node.Modifiers, expected) {
69
+ t.Fatalf("node %q modifiers = %v, want %v", id, node.Modifiers, expected)
70
+ }
71
+ }
72
+
73
+ // Negative twin: a member with no modifier keyword emits no modifiers.
74
+ plain, ok := byID["src/main.ts#Service.plain:method"]
75
+ if !ok {
76
+ t.Fatalf("missing plain method node; have %v", dumpNodeIDs(dump))
77
+ }
78
+ if len(plain.Modifiers) != 0 {
79
+ t.Fatalf("plain method modifiers = %v, want none", plain.Modifiers)
80
+ }
81
+
82
+ // Every emitted modifier is a member of the TtscGraphNodeModifier union, since
83
+ // an unknown string would reject the dump under typia.assert.
84
+ union := map[string]bool{
85
+ "export": true, "default": true, "declare": true, "abstract": true,
86
+ "static": true, "readonly": true, "async": true, "const": true,
87
+ "public": true, "private": true, "protected": true, "optional": true,
88
+ }
89
+ for _, n := range dump.Nodes {
90
+ for _, m := range n.Modifiers {
91
+ if !union[m] {
92
+ t.Fatalf("node %q emitted non-union modifier %q", n.ID, m)
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ // equalStrings reports whether two string slices have the same elements in order.
99
+ func equalStrings(a, b []string) bool {
100
+ if len(a) != len(b) {
101
+ return false
102
+ }
103
+ for i := range a {
104
+ if a[i] != b[i] {
105
+ return false
106
+ }
107
+ }
108
+ return true
109
+ }
110
+
111
+ // dumpNodeIDs returns a dump's node ids for failure messages.
112
+ func dumpNodeIDs(d Dump) []string {
113
+ ids := make([]string, 0, len(d.Nodes))
114
+ for _, n := range d.Nodes {
115
+ ids = append(ids, n.ID)
116
+ }
117
+ return ids
118
+ }
@@ -0,0 +1,61 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestNodesAreMarkedExportedThroughTheExportTable verifies that markExports flags
11
+ // a node as Exported from the checker's module export table, not from a syntactic
12
+ // `export` modifier — so a declaration exported by a separate `export { Service }`
13
+ // statement counts, while an unexported sibling does not.
14
+ //
15
+ // The export surface drives the public-API projection, and the codebases the
16
+ // graph targets export most of their surface through `export { … }` and barrel
17
+ // re-exports rather than inline modifiers. A modifier-only scan would miss
18
+ // Service here; the export-table walk must catch it, and must still leave the
19
+ // unexported Internal alone.
20
+ //
21
+ // 1. Compile a fixture declaring `class Service` and `class Internal`, exporting
22
+ // only Service through a trailing `export { Service }` statement.
23
+ // 2. Build the graph.
24
+ // 3. Assert the Service node is Exported and the Internal node is not.
25
+ func TestNodesAreMarkedExportedThroughTheExportTable(t *testing.T) {
26
+ root := t.TempDir()
27
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
28
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `class Service {}
29
+ class Internal {}
30
+ export { Service };
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
+ path := sourceFile(t, prog, "main.ts").FileName()
44
+
45
+ service := graph.Nodes[nodeID(path, "Service", NodeClass)]
46
+ internal := graph.Nodes[nodeID(path, "Internal", NodeClass)]
47
+ if service == nil || internal == nil {
48
+ t.Fatalf("Build did not record both classes; have %v", nodeIDSet(graph))
49
+ }
50
+
51
+ // Exported via a separate `export { Service }` statement — no inline modifier,
52
+ // so only the checker export table reveals it.
53
+ if !service.Exported {
54
+ t.Fatalf("Service should be marked Exported through the export table")
55
+ }
56
+ // Negative twin: Internal is never exported, so the public-API projection must
57
+ // not surface it.
58
+ if internal.Exported {
59
+ t.Fatalf("Internal is not exported but was marked Exported")
60
+ }
61
+ }
@@ -0,0 +1,54 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "strings"
6
+ "testing"
7
+
8
+ "github.com/samchon/ttsc/packages/ttsc/driver"
9
+ )
10
+
11
+ // TestOverloadImplementationReplacesSignature verifies that an overload set's
12
+ // graph node points at the executable implementation, not the first signature.
13
+ // Overload-heavy APIs otherwise force agents to re-open the file to see the
14
+ // calls that the graph already attributed to the node.
15
+ func TestOverloadImplementationReplacesSignature(t *testing.T) {
16
+ root := t.TempDir()
17
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
18
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export class Query {
19
+ join(name: string): this
20
+ join(name: string, alias?: string): this {
21
+ return this.realJoin(name, alias)
22
+ }
23
+
24
+ private realJoin(name: string, alias?: string): this {
25
+ return this
26
+ }
27
+ }
28
+ `)
29
+
30
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
31
+ if err != nil {
32
+ t.Fatal(err)
33
+ }
34
+ if len(diags) != 0 {
35
+ t.Fatalf("unexpected diagnostics: %v", diags)
36
+ }
37
+ defer func() { _ = prog.Close() }()
38
+
39
+ graph := Build(prog)
40
+ file := sourceFile(t, prog, "main.ts")
41
+ path := file.FileName()
42
+ id := nodeID(path, "Query.join", NodeMethod)
43
+ node := graph.Nodes[id]
44
+ if node == nil {
45
+ t.Fatalf("missing node Query.join; have %v", nodeIDSet(graph))
46
+ }
47
+ source := file.Text()[node.Pos:node.End]
48
+ if !strings.Contains(source, "return this.realJoin") {
49
+ t.Fatalf("expected Query.join node to cover the implementation body, got:\n%s", source)
50
+ }
51
+ if !hasEdge(graph, id, nodeID(path, "Query.realJoin", NodeMethod), EdgeValueCall) {
52
+ t.Fatalf("missing value-call edge Query.join -> Query.realJoin; edges: %v", graph.Edges)
53
+ }
54
+ }
@@ -0,0 +1,83 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestPropertyMemberEdgesRemainOwnerVisible verifies property-member nodes are
11
+ // additive rather than replacing class/interface owner-level edges.
12
+ //
13
+ // Property nodes give MCP precise evidence for state/property flows, but broad
14
+ // architecture questions still need `Service -> Dep` and `Service -> makeDep`
15
+ // without already knowing `Service.dep`.
16
+ //
17
+ // 1. Compile a class property with both a type reference and initializer call,
18
+ // plus an interface property signature.
19
+ // 2. Build the graph.
20
+ // 3. Assert both the owner node and the property node expose the same
21
+ // dependency evidence.
22
+ func TestPropertyMemberEdgesRemainOwnerVisible(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 interface Dep {
26
+ value: string
27
+ }
28
+
29
+ export function makeDep(): Dep {
30
+ return { value: "ok" }
31
+ }
32
+
33
+ export class Service {
34
+ dep: Dep = makeDep()
35
+ }
36
+
37
+ export interface Contract {
38
+ dep: Dep
39
+ }
40
+ `)
41
+
42
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
43
+ if err != nil {
44
+ t.Fatal(err)
45
+ }
46
+ if len(diags) != 0 {
47
+ t.Fatalf("unexpected diagnostics: %v", diags)
48
+ }
49
+ defer func() { _ = prog.Close() }()
50
+
51
+ graph := Build(prog)
52
+ path := sourceFile(t, prog, "main.ts").FileName()
53
+ dep := nodeID(path, "Dep", NodeInterface)
54
+ makeDep := nodeID(path, "makeDep", NodeFunction)
55
+ service := nodeID(path, "Service", NodeClass)
56
+ serviceDep := nodeID(path, "Service.dep", NodeVariable)
57
+ contract := nodeID(path, "Contract", NodeInterface)
58
+ contractDep := nodeID(path, "Contract.dep", NodeVariable)
59
+
60
+ for _, id := range []string{serviceDep, contractDep} {
61
+ if _, ok := graph.Nodes[id]; !ok {
62
+ t.Fatalf("missing property node %s; nodes: %v", id, nodeIDSet(graph))
63
+ }
64
+ }
65
+ if !hasEdge(graph, serviceDep, dep, EdgeTypeRef) {
66
+ t.Fatalf("missing type-ref edge Service.dep -> Dep; edges: %v", graph.Edges)
67
+ }
68
+ if !hasEdge(graph, serviceDep, makeDep, EdgeValueCall) {
69
+ t.Fatalf("missing value-call edge Service.dep -> makeDep; edges: %v", graph.Edges)
70
+ }
71
+ if !hasEdge(graph, service, dep, EdgeTypeRef) {
72
+ t.Fatalf("missing owner type-ref edge Service -> Dep; edges: %v", graph.Edges)
73
+ }
74
+ if !hasEdge(graph, service, makeDep, EdgeValueCall) {
75
+ t.Fatalf("missing owner value-call edge Service -> makeDep; edges: %v", graph.Edges)
76
+ }
77
+ if !hasEdge(graph, contractDep, dep, EdgeTypeRef) {
78
+ t.Fatalf("missing type-ref edge Contract.dep -> Dep; edges: %v", graph.Edges)
79
+ }
80
+ if !hasEdge(graph, contract, dep, EdgeTypeRef) {
81
+ t.Fatalf("missing owner type-ref edge Contract -> Dep; edges: %v", graph.Edges)
82
+ }
83
+ }