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,102 @@
1
+ // Package graph builds a checker-resolved code reference graph over a tsgo
2
+ // Program: symbols as nodes, type-resolved relationships as edges. Because the
3
+ // graph rides ttsc's in-process Checker, every edge is resolved by the real
4
+ // type checker rather than a syntactic heuristic, so an edge can be tagged
5
+ // "checker-resolved" instead of "guessed".
6
+ //
7
+ // resolve.go holds the load-bearing primitive the rest of the graph depends on:
8
+ // following a reference to the true declaration the checker binds it to. The
9
+ // hard case is the barrel re-export, where `pkg/index.ts` re-exports a sibling's
10
+ // symbol; that shape carries almost every cross-package edge in a monorepo.
11
+ // Stopping at GetSymbolAtLocation lands on the local import alias and severs the
12
+ // edge at the index file, which collapses the output back to tree-sitter
13
+ // quality. Unwrapping the alias chain (Checker_getAliasedSymbol) lands on the
14
+ // sibling source that actually declares the symbol. Resolve does that unwrap,
15
+ // then classifies where the declaration lives so a node_modules / `.d.ts`
16
+ // boundary becomes an external leaf instead of pulling a dependency's internals
17
+ // into the graph.
18
+ package graph
19
+
20
+ import (
21
+ "strings"
22
+
23
+ shimast "github.com/microsoft/typescript-go/shim/ast"
24
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
25
+ )
26
+
27
+ // Target is the resolved endpoint of a reference: the declaration symbol the
28
+ // checker binds it to, the source file that declares it, and whether that file
29
+ // sits outside the workspace (a node_modules or `.d.ts` boundary leaf).
30
+ type Target struct {
31
+ Symbol *shimast.Symbol
32
+ File string
33
+ External bool
34
+ Pos int
35
+ End int
36
+ }
37
+
38
+ // Resolve follows ref to the real declaration the checker binds it to. It
39
+ // unwraps import/export alias chains so a reference through a barrel re-export
40
+ // lands on the sibling source that declares the symbol, not the re-exporting
41
+ // index file. It returns nil when the checker cannot bind ref to a symbol (a
42
+ // numeric literal, a punctuation token, an unresolved name).
43
+ func Resolve(checker *shimchecker.Checker, ref *shimast.Node) *Target {
44
+ symbol := checker.GetSymbolAtLocation(ref)
45
+ if symbol == nil {
46
+ return nil
47
+ }
48
+ if symbol.Flags&shimast.SymbolFlagsAlias != 0 {
49
+ if aliased := shimchecker.Checker_getAliasedSymbol(checker, symbol); aliased != nil {
50
+ symbol = aliased
51
+ }
52
+ }
53
+ target := &Target{Symbol: symbol}
54
+ if declaration := declarationNode(symbol); declaration != nil {
55
+ target.Pos = declaration.Pos()
56
+ target.End = declaration.End()
57
+ if file := shimast.GetSourceFileOfNode(declaration); file != nil {
58
+ target.File = file.FileName()
59
+ target.External = file.IsDeclarationFile ||
60
+ strings.Contains(target.File, "/node_modules/")
61
+ }
62
+ }
63
+ return target
64
+ }
65
+
66
+ // declarationFile returns the source file of symbol's first declaration, or nil
67
+ // when the symbol carries no declaration (an intrinsic or synthesized symbol).
68
+ // The checker resolves symlinks to realpath because preserveSymlinks defaults to
69
+ // false, so a pnpm `workspace:*` sibling resolves to its real source here and is
70
+ // not misclassified as external by Resolve.
71
+ func declarationFile(symbol *shimast.Symbol) *shimast.SourceFile {
72
+ if declaration := declarationNode(symbol); declaration != nil {
73
+ return shimast.GetSourceFileOfNode(declaration)
74
+ }
75
+ return nil
76
+ }
77
+
78
+ func declarationNode(symbol *shimast.Symbol) *shimast.Node {
79
+ if len(symbol.Declarations) == 0 {
80
+ return nil
81
+ }
82
+ // Prefer a non-declaration-file declaration. A declaration-merged symbol (a
83
+ // class paired with an interface, or a function with a namespace) can list a
84
+ // `.d.ts` declaration first; classifying by it would mark a real workspace
85
+ // symbol external and sever it from the graph.
86
+ for _, declaration := range symbol.Declarations {
87
+ if file := shimast.GetSourceFileOfNode(declaration); file != nil && !file.IsDeclarationFile && declaration.Body() != nil {
88
+ return declaration
89
+ }
90
+ }
91
+ for _, declaration := range symbol.Declarations {
92
+ if file := shimast.GetSourceFileOfNode(declaration); file != nil && !file.IsDeclarationFile {
93
+ return declaration
94
+ }
95
+ }
96
+ for _, declaration := range symbol.Declarations {
97
+ if declaration.Body() != nil {
98
+ return declaration
99
+ }
100
+ }
101
+ return symbol.Declarations[0]
102
+ }
@@ -0,0 +1,50 @@
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
+ // TestResolvePrefersImplementationDeclarationSpan verifies overloaded callables
12
+ // resolve to the executable implementation span, not the first signature.
13
+ //
14
+ // Without this, MCP answers can show only an overload header and omit the body
15
+ // that explains downstream calls, pushing agents back to raw file reads.
16
+ //
17
+ // 1. Compile an overloaded function whose implementation contains a unique
18
+ // expression.
19
+ // 2. Resolve the function symbol.
20
+ // 3. Assert the selected target span includes the implementation body.
21
+ func TestResolvePrefersImplementationDeclarationSpan(t *testing.T) {
22
+ root := t.TempDir()
23
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
24
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export function api(value: string): string
25
+ export function api(value: string): string {
26
+ return value.toUpperCase()
27
+ }
28
+
29
+ api("x")
30
+ `)
31
+
32
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
33
+ if err != nil {
34
+ t.Fatal(err)
35
+ }
36
+ if len(diags) != 0 {
37
+ t.Fatalf("unexpected diagnostics: %v", diags)
38
+ }
39
+ defer func() { _ = prog.Close() }()
40
+
41
+ file := sourceFile(t, prog, "main.ts")
42
+ target := Resolve(prog.Checker, identifier(t, file, "api"))
43
+ if target == nil {
44
+ t.Fatal("Resolve returned nil for api")
45
+ }
46
+ source := file.Text()[target.Pos:target.End]
47
+ if !strings.Contains(source, "toUpperCase") {
48
+ t.Fatalf("expected Resolve to report the implementation span, got:\n%s", source)
49
+ }
50
+ }
@@ -0,0 +1,230 @@
1
+ package graph
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+
9
+ shimast "github.com/microsoft/typescript-go/shim/ast"
10
+
11
+ "github.com/samchon/ttsc/packages/ttsc/driver"
12
+ )
13
+
14
+ // TestResolveUnwrapsBarrelReexportToRealDeclaration is a traversal-completeness
15
+ // probe, not a unit test of a single branch: it runs a real Checker over a
16
+ // ttsc-owned fixture and asserts the load-bearing graph primitive
17
+ // (GetSymbolAtLocation -> Checker_getAliasedSymbol -> declaration) reaches the
18
+ // sibling source that actually declares a symbol, instead of dead-ending on the
19
+ // barrel re-export. This is the differentiator the whole package rests on: a
20
+ // path-heuristic tool stops at the index file and severs every cross-package
21
+ // edge, so if this surface ever regresses the graph silently collapses back to
22
+ // tree-sitter quality. Mirrors the naive-vs-bridged shape of the base-chain
23
+ // probe in packages/lint/test/shim.
24
+ //
25
+ // 1. Compile a fixture where main.ts imports `target` through a barrel
26
+ // (`index.ts` re-exports it from `impl.ts`).
27
+ // 2. Resolve the call-site reference two ways: the naive `GetSymbolAtLocation`
28
+ // stop (the local import alias) and the bridged `Resolve` (alias unwrapped).
29
+ // 3. Assert the naive stop lands on the import in main.ts (the edge is severed
30
+ // there) while `Resolve` lands on the real declaration in impl.ts, and that
31
+ // a workspace declaration is not classified as an external boundary leaf.
32
+ func TestResolveUnwrapsBarrelReexportToRealDeclaration(t *testing.T) {
33
+ root := t.TempDir()
34
+ writeFile(t, filepath.Join(root, "tsconfig.json"), `{
35
+ "compilerOptions": {
36
+ "target": "ES2022",
37
+ "module": "commonjs",
38
+ "strict": true,
39
+ "rootDir": "src",
40
+ "outDir": "dist"
41
+ },
42
+ "files": ["src/main.ts"]
43
+ }
44
+ `)
45
+ writeFile(t, filepath.Join(root, "src", "impl.ts"), `export function target(): number {
46
+ return 1;
47
+ }
48
+ `)
49
+ // The barrel: re-exports the sibling's symbol without redeclaring it. This is
50
+ // the alias hop that severs the edge for a path-string resolver.
51
+ writeFile(t, filepath.Join(root, "src", "index.ts"), `export { target } from "./impl";
52
+ `)
53
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `import { target } from "./index";
54
+ const value = 1;
55
+ const echoed = value;
56
+ target();
57
+ void echoed;
58
+ `)
59
+
60
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
61
+ if err != nil {
62
+ t.Fatal(err)
63
+ }
64
+ if len(diags) != 0 {
65
+ t.Fatalf("unexpected diagnostics: %v", diags)
66
+ }
67
+ defer func() { _ = prog.Close() }()
68
+
69
+ checker := prog.Checker
70
+ if checker == nil {
71
+ t.Fatal("LoadProgram did not acquire a checker")
72
+ }
73
+ main := sourceFile(t, prog, "main.ts")
74
+
75
+ // The naive stop: GetSymbolAtLocation alone binds the call-site reference to
76
+ // the LOCAL import alias, whose declaration is the import specifier in main.ts.
77
+ // A resolver that stops here severs the edge at the import and never reaches
78
+ // the package that declares target.
79
+ callee := calleeIdentifier(t, main)
80
+ raw := checker.GetSymbolAtLocation(callee)
81
+ if raw == nil {
82
+ t.Fatal("GetSymbolAtLocation returned nil for the call-site reference")
83
+ }
84
+ if raw.Flags&shimast.SymbolFlagsAlias == 0 {
85
+ t.Fatal("premise broken: the call-site reference is not an alias, so there is no barrel hop to unwrap")
86
+ }
87
+ if rawFile := declarationFile(raw); rawFile == nil || !strings.HasSuffix(rawFile.FileName(), "main.ts") {
88
+ t.Fatalf("naive stop did not land on the import in main.ts: %v", raw.Declarations)
89
+ }
90
+
91
+ // The bridged walk: Resolve unwraps the alias chain to the real declaration.
92
+ resolved := Resolve(checker, callee)
93
+ if resolved == nil {
94
+ t.Fatal("Resolve returned nil for the call-site reference")
95
+ }
96
+ if resolved.Symbol == nil || resolved.Symbol.Name != "target" {
97
+ t.Fatalf("Resolve did not land on the target symbol: %+v", resolved.Symbol)
98
+ }
99
+ if !strings.HasSuffix(resolved.File, "impl.ts") {
100
+ t.Fatalf("Resolve dead-ended before impl.ts: got %q (a barrel-severed resolver would report index.ts)", resolved.File)
101
+ }
102
+ if strings.HasSuffix(resolved.File, "index.ts") {
103
+ t.Fatalf("Resolve stopped at the re-exporting barrel index.ts instead of the declaration")
104
+ }
105
+ if resolved.External {
106
+ t.Fatalf("a workspace declaration (%q) was misclassified as an external boundary leaf", resolved.File)
107
+ }
108
+
109
+ // A directly-declared local reference skips the alias unwrap and is a
110
+ // workspace node, not an external leaf.
111
+ local := Resolve(checker, identifier(t, main, "value"))
112
+ if local == nil || local.Symbol == nil || local.Symbol.Name != "value" {
113
+ t.Fatalf("Resolve did not bind the local const reference: %+v", local)
114
+ }
115
+ if !strings.HasSuffix(local.File, "main.ts") || local.External {
116
+ t.Fatalf("local const resolved to the wrong place: %+v", local)
117
+ }
118
+
119
+ // A node the checker cannot bind to a symbol resolves to nil rather than
120
+ // panicking, so the extraction walk can skip it.
121
+ if got := Resolve(checker, numericLiteral(t, main)); got != nil {
122
+ t.Fatalf("Resolve bound a numeric literal to a symbol: %+v", got)
123
+ }
124
+ }
125
+
126
+ // sourceFile returns the resident program's source file whose path ends with
127
+ // suffix, failing the test when no such file is loaded.
128
+ func sourceFile(t *testing.T, prog *driver.Program, suffix string) *shimast.SourceFile {
129
+ t.Helper()
130
+ for _, file := range prog.SourceFiles() {
131
+ if strings.HasSuffix(file.FileName(), suffix) {
132
+ return file
133
+ }
134
+ }
135
+ t.Fatalf("source file %q not found in program", suffix)
136
+ return nil
137
+ }
138
+
139
+ // calleeIdentifier returns the expression identifier of the first call
140
+ // expression in file (the `target` in `target()`), failing when absent.
141
+ func calleeIdentifier(t *testing.T, file *shimast.SourceFile) *shimast.Node {
142
+ t.Helper()
143
+ var found *shimast.Node
144
+ walkNodes(file, func(node *shimast.Node) bool {
145
+ if node.Kind == shimast.KindCallExpression {
146
+ if call := node.AsCallExpression(); call != nil {
147
+ found = call.Expression
148
+ return true
149
+ }
150
+ }
151
+ return false
152
+ })
153
+ if found == nil {
154
+ t.Fatal("could not locate a call-site identifier in the fixture")
155
+ }
156
+ return found
157
+ }
158
+
159
+ // identifier returns the first identifier node in file whose text is name,
160
+ // failing when absent.
161
+ func identifier(t *testing.T, file *shimast.SourceFile, name string) *shimast.Node {
162
+ t.Helper()
163
+ var found *shimast.Node
164
+ walkNodes(file, func(node *shimast.Node) bool {
165
+ if node.Kind == shimast.KindIdentifier && shimast.NodeText(node) == name {
166
+ found = node
167
+ return true
168
+ }
169
+ return false
170
+ })
171
+ if found == nil {
172
+ t.Fatalf("identifier %q not found in the fixture", name)
173
+ }
174
+ return found
175
+ }
176
+
177
+ // numericLiteral returns the first numeric literal node in file, failing when
178
+ // absent.
179
+ func numericLiteral(t *testing.T, file *shimast.SourceFile) *shimast.Node {
180
+ t.Helper()
181
+ var found *shimast.Node
182
+ walkNodes(file, func(node *shimast.Node) bool {
183
+ if node.Kind == shimast.KindNumericLiteral {
184
+ found = node
185
+ return true
186
+ }
187
+ return false
188
+ })
189
+ if found == nil {
190
+ t.Fatal("numeric literal not found in the fixture")
191
+ }
192
+ return found
193
+ }
194
+
195
+ // walkNodes performs a pre-order walk of file's statement subtrees, calling
196
+ // visit on each node and stopping the whole walk as soon as visit returns true.
197
+ func walkNodes(file *shimast.SourceFile, visit func(*shimast.Node) bool) {
198
+ if file.Statements == nil {
199
+ return
200
+ }
201
+ var recur func(node *shimast.Node) bool
202
+ recur = func(node *shimast.Node) bool {
203
+ if node == nil {
204
+ return false
205
+ }
206
+ if visit(node) {
207
+ return true
208
+ }
209
+ return node.ForEachChild(func(child *shimast.Node) bool {
210
+ return recur(child)
211
+ })
212
+ }
213
+ for _, stmt := range file.Statements.Nodes {
214
+ if recur(stmt) {
215
+ return
216
+ }
217
+ }
218
+ }
219
+
220
+ // writeFile writes content to path, creating parent directories, failing the
221
+ // test on any error.
222
+ func writeFile(t *testing.T, path, content string) {
223
+ t.Helper()
224
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
225
+ t.Fatal(err)
226
+ }
227
+ if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
228
+ t.Fatal(err)
229
+ }
230
+ }
@@ -0,0 +1,79 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestTypeRefEdgesCoverTypeofAsSatisfies verifies that the type-position shapes
11
+ // beyond a plain `Foo` annotation are all resolved to type-ref edges:
12
+ //
13
+ // - `typeof value` -> the value it queries (an EntityName, not a TypeReference)
14
+ // - `x as Target` -> Target (the assertion's type)
15
+ // - `expr satisfies T` -> T (the satisfies type)
16
+ //
17
+ // `as` and `satisfies` carry an ordinary TypeReference the recursion already
18
+ // reaches; `typeof` names its value through an EntityName, which a
19
+ // TypeReference-only walk would miss. All three must produce an edge so an
20
+ // impact query sees the dependency.
21
+ func TestTypeRefEdgesCoverTypeofAsSatisfies(t *testing.T) {
22
+ root := t.TempDir()
23
+ writeFile(t, filepath.Join(root, "tsconfig.json"), `{
24
+ "compilerOptions": {
25
+ "target": "ES2022",
26
+ "module": "commonjs",
27
+ "strict": true,
28
+ "rootDir": "src",
29
+ "outDir": "dist"
30
+ },
31
+ "files": ["src/main.ts"]
32
+ }
33
+ `)
34
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export interface Config {}
35
+ export interface Target {}
36
+
37
+ export const settings = { mode: "fast" };
38
+
39
+ export type SettingsShape = typeof settings;
40
+
41
+ export function coerce(x: unknown): Target {
42
+ return x as Target;
43
+ }
44
+
45
+ export const config = { a: 1 } satisfies Config;
46
+ `)
47
+
48
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
49
+ if err != nil {
50
+ t.Fatal(err)
51
+ }
52
+ if len(diags) != 0 {
53
+ t.Fatalf("unexpected diagnostics: %v", diags)
54
+ }
55
+ defer func() { _ = prog.Close() }()
56
+
57
+ graph := Build(prog)
58
+ path := sourceFile(t, prog, "main.ts").FileName()
59
+
60
+ settingsShape := nodeID(path, "SettingsShape", NodeTypeAlias)
61
+ settings := nodeID(path, "settings", NodeVariable)
62
+ coerce := nodeID(path, "coerce", NodeFunction)
63
+ target := nodeID(path, "Target", NodeInterface)
64
+ config := nodeID(path, "config", NodeVariable)
65
+ configType := nodeID(path, "Config", NodeInterface)
66
+
67
+ if !hasEdge(graph, settingsShape, settings, EdgeTypeRef) {
68
+ t.Errorf("missing type-ref edge SettingsShape -> settings (typeof query)")
69
+ }
70
+ if !hasEdge(graph, coerce, target, EdgeTypeRef) {
71
+ t.Errorf("missing type-ref edge coerce -> Target (as assertion)")
72
+ }
73
+ if !hasEdge(graph, config, configType, EdgeTypeRef) {
74
+ t.Errorf("missing type-ref edge config -> Config (satisfies)")
75
+ }
76
+ if t.Failed() {
77
+ t.Logf("edges: %v", graph.Edges)
78
+ }
79
+ }
@@ -0,0 +1,52 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestTypeRefEdgesResolveNamedTypesAcrossFiles verifies that a type-position
11
+ // reference to a named type in another file is recorded as a type-ref edge to
12
+ // that type's real declaration, and is kept distinct from a value-call. Treating
13
+ // type references as first-class edges lets an impact query separate "uses this
14
+ // at runtime" from "depends on this type", which fits the ttsc thesis.
15
+ //
16
+ // 1. Compile a fixture where use(c: Config) annotates a parameter with an
17
+ // interface declared in another file.
18
+ // 2. Build the graph.
19
+ // 3. Assert a use -> Config type-ref edge exists and is not a value-call.
20
+ func TestTypeRefEdgesResolveNamedTypesAcrossFiles(t *testing.T) {
21
+ root := t.TempDir()
22
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
23
+ writeFile(t, filepath.Join(root, "src", "types.ts"), `export interface Config {
24
+ name: string;
25
+ }
26
+ `)
27
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `import { Config } from "./types";
28
+ export function use(c: Config): string {
29
+ return c.name;
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
+ use := nodeID(sourceFile(t, prog, "main.ts").FileName(), "use", NodeFunction)
44
+ config := nodeID(sourceFile(t, prog, "types.ts").FileName(), "Config", NodeInterface)
45
+
46
+ if !hasEdge(graph, use, config, EdgeTypeRef) {
47
+ t.Fatalf("missing type-ref edge use -> Config; edges: %v", graph.Edges)
48
+ }
49
+ if hasEdge(graph, use, config, EdgeValueCall) {
50
+ t.Fatalf("a type reference was misclassified as a value-call edge")
51
+ }
52
+ }
@@ -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,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
+ }