ttsc 0.16.3 → 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 (74) 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/package.json +8 -8
  67. package/shim/ast/shim.go +6 -0
  68. package/shim/checker/shim.go +22 -12
  69. package/src/TtscService.ts +94 -0
  70. package/src/compiler/internal/residentTransformProcess.ts +216 -0
  71. package/src/compiler/internal/startResidentTransform.ts +126 -0
  72. package/src/index.ts +1 -0
  73. package/utility/host.go +8 -2
  74. package/utility/serve.go +158 -0
@@ -0,0 +1,60 @@
1
+ package graph
2
+
3
+ import (
4
+ shimast "github.com/microsoft/typescript-go/shim/ast"
5
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
6
+ )
7
+
8
+ // markExports flags every node reachable through a source file's export table as
9
+ // Exported. It runs on the checker, so it sees the real public surface: an
10
+ // inline `export class`, a separate `export { Foo }`, a re-export
11
+ // `export { Foo } from "./foo"`, and a barrel `export *` all land here, where a
12
+ // purely syntactic `export`-modifier scan would miss the re-export forms that
13
+ // carry most of a monorepo's public API.
14
+ func (g *Graph) markExports(checker *shimchecker.Checker, file *shimast.SourceFile) {
15
+ moduleSymbol := checker.GetSymbolAtLocation(file.AsNode())
16
+ if moduleSymbol == nil {
17
+ return
18
+ }
19
+ for _, export := range shimchecker.Checker_getExportsOfModule(checker, moduleSymbol) {
20
+ symbol := export
21
+ // A re-export is an alias; unwrap it to the declaration it points at so the
22
+ // exported flag lands on the real node, not a re-exporting index file's
23
+ // local alias symbol (which has no node of its own).
24
+ if symbol.Flags&shimast.SymbolFlagsAlias != 0 {
25
+ if aliased := shimchecker.Checker_getAliasedSymbol(checker, symbol); aliased != nil {
26
+ symbol = aliased
27
+ }
28
+ }
29
+ g.markExportedSymbol(symbol)
30
+ }
31
+ }
32
+
33
+ // markExportedSymbol sets Exported on the node a resolved export symbol declares,
34
+ // when the graph recorded one. A symbol whose declaration the graph does not
35
+ // model as a node (a re-exported value from a dependency, a kind it does not
36
+ // track) is skipped rather than fabricated.
37
+ func (g *Graph) markExportedSymbol(symbol *shimast.Symbol) {
38
+ declaration := declarationNode(symbol)
39
+ if declaration == nil {
40
+ return
41
+ }
42
+ file := shimast.GetSourceFileOfNode(declaration)
43
+ if file == nil {
44
+ return
45
+ }
46
+ kind := symbolNodeKind(symbol)
47
+ if kind == "" {
48
+ return
49
+ }
50
+ name := qualifiedName(symbol)
51
+ if kind == NodeMethod {
52
+ name = methodName(symbol)
53
+ }
54
+ if name == "" {
55
+ return
56
+ }
57
+ if node, ok := g.Nodes[nodeID(file.FileName(), name, kind)]; ok {
58
+ node.Exported = true
59
+ }
60
+ }
@@ -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
+ // TestFileDiagnosticsMatchTsgoCodeAndLocation verifies that FileDiagnostics
11
+ // surfaces the same semantic error code and line tsgo emits, scoped to one file.
12
+ // This is validation gate 4 from issue #259: a deliberate type error must yield
13
+ // the exact tsc code/location from the graph's shared Program, proving the
14
+ // diagnostics path is the real checker and not a re-implementation.
15
+ //
16
+ // 1. Compile a fixture whose only file assigns a string to a number binding.
17
+ // 2. Ask FileDiagnostics for that file.
18
+ // 3. Assert a TS2322 (not assignable) diagnostic on line 1, and that an
19
+ // unrelated path yields none.
20
+ func TestFileDiagnosticsMatchTsgoCodeAndLocation(t *testing.T) {
21
+ root := t.TempDir()
22
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
23
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export const broken: number = "not a number";
24
+ `)
25
+
26
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
27
+ if err != nil {
28
+ t.Fatal(err)
29
+ }
30
+ // LoadProgram reports only config/parse diagnostics; the type error is
31
+ // syntactically valid, so it surfaces through Program.Diagnostics() instead.
32
+ if len(diags) != 0 {
33
+ t.Fatalf("unexpected parse diagnostics: %v", diags)
34
+ }
35
+ defer func() { _ = prog.Close() }()
36
+
37
+ main := sourceFile(t, prog, "main.ts").FileName()
38
+ got := FileDiagnostics(prog, main)
39
+
40
+ var match *driver.Diagnostic
41
+ for i := range got {
42
+ if got[i].Code == 2322 {
43
+ match = &got[i]
44
+ break
45
+ }
46
+ }
47
+ if match == nil {
48
+ t.Fatalf("expected a TS2322 diagnostic for the bad assignment, got %v", got)
49
+ }
50
+ if match.Line != 1 {
51
+ t.Fatalf("TS2322 reported on line %d, expected line 1", match.Line)
52
+ }
53
+ if match.File != main {
54
+ t.Fatalf("diagnostic file %q is not the queried file %q", match.File, main)
55
+ }
56
+ if other := FileDiagnostics(prog, filepath.Join(root, "src", "absent.ts")); len(other) != 0 {
57
+ t.Fatalf("a file with no diagnostics returned %d", len(other))
58
+ }
59
+ }
@@ -0,0 +1,69 @@
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
+ // TestFileDiagnosticsReportCodeColumnAndMessage verifies that FileDiagnostics
12
+ // surfaces the full tsgo-reported location and text of a type error, not just
13
+ // its code and line: the column points at the offending initializer and the
14
+ // message carries the human-readable reason.
15
+ //
16
+ // The existing match-code-and-location probe pins Line and Code; this one
17
+ // strengthens the contract to Column and Message so a regression that kept the
18
+ // right code but lost the precise span (or the text) is caught. The oracle is
19
+ // the real checker: tsgo attributes the assignability error to the declared
20
+ // binding, so on `export const broken: number = "nope";` the diagnostic sits at
21
+ // `broken` — 1-based column 14 (`export const ` is 13 characters).
22
+ //
23
+ // 1. Compile a fixture whose only file assigns a string to a number binding.
24
+ // 2. Ask FileDiagnostics for that file.
25
+ // 3. Assert the TS2322 diagnostic sits at line 1 / column 14 and its message
26
+ // contains "not assignable".
27
+ func TestFileDiagnosticsReportCodeColumnAndMessage(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 const broken: number = "nope";
31
+ `)
32
+
33
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
34
+ if err != nil {
35
+ t.Fatal(err)
36
+ }
37
+ // The bad assignment is syntactically valid, so it surfaces through
38
+ // Program.Diagnostics() rather than the config/parse diagnostics LoadProgram
39
+ // returns.
40
+ if len(diags) != 0 {
41
+ t.Fatalf("unexpected parse diagnostics: %v", diags)
42
+ }
43
+ defer func() { _ = prog.Close() }()
44
+
45
+ main := sourceFile(t, prog, "main.ts").FileName()
46
+ got := FileDiagnostics(prog, main)
47
+
48
+ var match *driver.Diagnostic
49
+ for i := range got {
50
+ if got[i].Code == 2322 {
51
+ match = &got[i]
52
+ break
53
+ }
54
+ }
55
+ if match == nil {
56
+ t.Fatalf("expected a TS2322 diagnostic for the bad assignment, got %v", got)
57
+ }
58
+ if match.Line != 1 {
59
+ t.Fatalf("TS2322 reported on line %d, expected line 1", match.Line)
60
+ }
61
+ // Oracle column: tsgo attributes the assignability error to the declared
62
+ // binding `broken`, which begins at the 14th character of the line.
63
+ if match.Column != 14 {
64
+ t.Fatalf("TS2322 reported at column %d, expected column 14 (the binding)", match.Column)
65
+ }
66
+ if !strings.Contains(match.Message, "not assignable") {
67
+ t.Fatalf("TS2322 message %q does not mention 'not assignable'", match.Message)
68
+ }
69
+ }
@@ -0,0 +1,66 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestFunctionLocalsDoNotCollide verifies that a function-local declaration is
11
+ // not minted as a graph node. Build records top-level declarations, namespace
12
+ // members, and class/interface members only; a body-scoped local resolved as an
13
+ // edge target has an unqualified, position-free id, so two same-named locals in
14
+ // different scopes would key the same node and merge — fabricating a false edge
15
+ // from each unrelated caller to one phantom callable.
16
+ func TestFunctionLocalsDoNotCollide(t *testing.T) {
17
+ root := t.TempDir()
18
+ writeFile(t, filepath.Join(root, "tsconfig.json"), `{
19
+ "compilerOptions": {
20
+ "target": "ES2022",
21
+ "module": "commonjs",
22
+ "strict": true,
23
+ "rootDir": "src",
24
+ "outDir": "dist"
25
+ },
26
+ "files": ["src/main.ts"]
27
+ }
28
+ `)
29
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export function outerA(): number {
30
+ function inner(): number {
31
+ return 1;
32
+ }
33
+ return inner();
34
+ }
35
+
36
+ export function outerB(): number {
37
+ function inner(): number {
38
+ return 2;
39
+ }
40
+ return inner();
41
+ }
42
+ `)
43
+
44
+ prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
45
+ if err != nil {
46
+ t.Fatal(err)
47
+ }
48
+ if len(diags) != 0 {
49
+ t.Fatalf("unexpected diagnostics: %v", diags)
50
+ }
51
+ defer func() { _ = prog.Close() }()
52
+
53
+ graph := Build(prog)
54
+ path := sourceFile(t, prog, "main.ts").FileName()
55
+
56
+ // No function-local node is minted...
57
+ if _, ok := graph.Nodes[nodeID(path, "inner", NodeFunction)]; ok {
58
+ t.Fatalf("a function-local 'inner' was minted as a node (would collide across scopes)")
59
+ }
60
+ // ...so no edge points at a phantom shared 'inner'.
61
+ for _, edge := range graph.Edges {
62
+ if to := graph.Nodes[edge.To]; to != nil && to.Name == "inner" {
63
+ t.Fatalf("edge to a function-local 'inner' (false cross-scope merge): %v", edge)
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,51 @@
1
+ package graph
2
+
3
+ import (
4
+ "os/exec"
5
+ "strings"
6
+ )
7
+
8
+ // GitIgnoredFiles returns the graph's source files that git ignores: generated
9
+ // output an author does not navigate, such as a Prisma client or other codegen
10
+ // emitted as real .ts into the source tree (the .d.ts emit is already dropped by
11
+ // driver.SourceFiles, but a generated .ts becomes a node and, being large and
12
+ // highly connected, otherwise dominates ranking and floods responses). Callers
13
+ // de-surface them: the MCP matcher keeps them reachable as edge targets and by
14
+ // exact name; the full-graph dump drops them from the viewer payload entirely.
15
+ //
16
+ // Empty when cwd is unset, the tree is not a git work tree, or git is
17
+ // unavailable, so a non-git project is unaffected.
18
+ func GitIgnoredFiles(cwd string, g *Graph) map[string]bool {
19
+ if cwd == "" || g == nil {
20
+ return nil
21
+ }
22
+ seen := make(map[string]bool)
23
+ var paths []string
24
+ for _, node := range g.Nodes {
25
+ f := node.File
26
+ if f == "" || strings.HasPrefix(f, "bundled:///") || seen[f] {
27
+ continue
28
+ }
29
+ seen[f] = true
30
+ paths = append(paths, f)
31
+ }
32
+ if len(paths) == 0 {
33
+ return nil
34
+ }
35
+ cmd := exec.Command("git", "-C", cwd, "check-ignore", "--stdin")
36
+ cmd.Stdin = strings.NewReader(strings.Join(paths, "\n") + "\n")
37
+ // check-ignore exits 0 with the ignored paths on stdout, 1 (an error to
38
+ // Output) with no output when none match, and 128 when git cannot run. Only
39
+ // stdout matters: parse it whenever it is non-empty, ignore the exit code.
40
+ out, _ := cmd.Output()
41
+ ignored := make(map[string]bool)
42
+ for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
43
+ if line != "" {
44
+ ignored[line] = true
45
+ }
46
+ }
47
+ if len(ignored) == 0 {
48
+ return nil
49
+ }
50
+ return ignored
51
+ }
@@ -0,0 +1,119 @@
1
+ package graph
2
+
3
+ // NodeKind classifies a graph node by what its symbol declares.
4
+ type NodeKind string
5
+
6
+ const (
7
+ NodeFunction NodeKind = "function"
8
+ NodeClass NodeKind = "class"
9
+ NodeInterface NodeKind = "interface"
10
+ NodeTypeAlias NodeKind = "type"
11
+ NodeEnum NodeKind = "enum"
12
+ NodeVariable NodeKind = "variable"
13
+ // NodeMethod is a class or interface member (a method, constructor, or
14
+ // accessor). Its id is class-qualified ("path#Class.method:method") so a
15
+ // resolved method call lands on the same node the build pass recorded.
16
+ NodeMethod NodeKind = "method"
17
+ )
18
+
19
+ // Node is one declared symbol. Its ID is position-invariant, built from the file
20
+ // realpath, the declared name, and the kind, so inserting a line above a
21
+ // declaration does not re-key it. That keeps a future incremental layer from
22
+ // churning the whole graph on every edit, which a byte-offset key would force.
23
+ type Node struct {
24
+ ID string
25
+ Name string
26
+ // Simple is the unqualified declared name (`create`, `OrderService`), taken
27
+ // straight from the declaration's symbol. Name may join an owner chain to a
28
+ // member with a single dot, and a quoted member name can itself contain a dot
29
+ // (`"a.b"` → Name `C.a.b`), so the simple/qualified boundary cannot be
30
+ // recovered from Name by splitting on a dot. Recording it here keeps the dump
31
+ // split exact instead of guessing.
32
+ Simple string
33
+ Kind NodeKind
34
+ File string
35
+ External bool
36
+ // Exported marks a node that is part of its module's export surface, resolved
37
+ // through the checker's export table so a re-export (`export { Foo } from`) or
38
+ // a barrel (`export *`) counts, not only an inline `export` modifier. It is
39
+ // the signal a public-API projection filters on.
40
+ Exported bool
41
+ // Modifiers holds the declaration's syntactic modifiers as wire strings (a
42
+ // subset of the TtscGraphNodeModifier union: export/default/declare/abstract/
43
+ // static/readonly/async/const/public/private/protected). It is recorded from
44
+ // the declaration's combined modifier flags during the build pass and emitted
45
+ // for projections that filter on visibility and shape.
46
+ Modifiers []string
47
+ // Pos and End bound the declaration in its source file (byte offsets). They
48
+ // are for display, never identity, so an edit that shifts them does not re-key
49
+ // the node.
50
+ Pos int
51
+ End int
52
+ }
53
+
54
+ // EdgeKind classifies a relationship between two nodes.
55
+ type EdgeKind string
56
+
57
+ const (
58
+ // EdgeHeritage is an `extends` / `implements` relationship from a class or
59
+ // interface to a base it derives from.
60
+ EdgeHeritage EdgeKind = "heritage"
61
+ // EdgeValueCall is a runtime use from one declaration of the function, method,
62
+ // or constructor it invokes: a call, a `new T()`, a `<Component/>` JSX use, or
63
+ // a tagged-template tag. Uses of a dependency's method are not modeled (the
64
+ // boundary stops at the external type).
65
+ EdgeValueCall EdgeKind = "value-call"
66
+ // EdgeValueAccess is a runtime property/accessor read or write. It is kept
67
+ // separate from calls so architecture flows can follow lazy getter/property
68
+ // behavior without pretending those reads invoke a function.
69
+ EdgeValueAccess EdgeKind = "value-access"
70
+ // EdgeTypeRef is a type-position reference from one declaration to a named
71
+ // type it mentions (a parameter, return, property, or alias type). It is not a
72
+ // runtime call, so an impact query can filter value edges from type edges.
73
+ EdgeTypeRef EdgeKind = "type-ref"
74
+ )
75
+
76
+ // Edge is a directed, checker-resolved relationship from one node to another,
77
+ // both referenced by Node.ID. Pos and End bound the source expression in the
78
+ // From node's file that produced the edge. They are evidence, not identity; a
79
+ // duplicate relationship keeps the first source-order span.
80
+ type Edge struct {
81
+ From string
82
+ To string
83
+ Kind EdgeKind
84
+ // Origin records the syntactic form a value-call or heritage edge came from,
85
+ // so the JSON dump can split one internal kind into the finer schema kinds
86
+ // (calls / instantiates / renders, extends / implements) without the
87
+ // MCP-facing model losing the distinction. It is "" for kinds that need no
88
+ // split (type-ref, value-access). For EdgeValueCall it is "call", "new",
89
+ // "jsx", or "tagged"; for EdgeHeritage it is "extends" or "implements".
90
+ Origin string
91
+ Pos int
92
+ End int
93
+ }
94
+
95
+ // Graph is the in-memory adjacency the MCP tools query. Edges are added by the
96
+ // resolution pass on top of the declaration nodes Build records.
97
+ type Graph struct {
98
+ Nodes map[string]*Node
99
+ Edges []*Edge
100
+ // Decorators holds the decorators written on the workspace's declarations,
101
+ // captured syntactically so the JSON dump can emit `decorates` edges and a
102
+ // consumer can interpret `@Controller`/`@Get` conventions without re-parsing
103
+ // source. It is dump-only metadata, separate from Edges so the existing
104
+ // checker-resolved relationships are untouched.
105
+ Decorators []*Decorator
106
+ // bodyNodes tracks whether a callable node's display span is the overload
107
+ // implementation rather than an overload signature. It is build-only metadata
108
+ // and intentionally stays out of JSON dumps.
109
+ bodyNodes map[string]bool
110
+ // seen deduplicates edges in O(1) during construction, so building a graph
111
+ // with N edges is O(N), not O(N²). Keyed by from\x00to\x00kind.
112
+ seen map[string]struct{}
113
+ }
114
+
115
+ // nodeID builds the position-invariant identity for a symbol named name,
116
+ // declared as kind in the source file at path.
117
+ func nodeID(path string, name string, kind NodeKind) string {
118
+ return path + "#" + name + ":" + string(kind)
119
+ }
@@ -0,0 +1,76 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestHeritageEdgesCoverImplementsAndInterfaceExtends verifies that
11
+ // collectHeritage spans both heritage-bearing declaration kinds and both clause
12
+ // keywords: an interface `extends` and a class `implements` each yield a
13
+ // heritage edge to the same base, while a class that only mentions the base in a
14
+ // parameter type position yields a type-ref edge, never a heritage edge.
15
+ //
16
+ // The negative twin pins the boundary collectHeritage must hold: heritage is an
17
+ // `extends`/`implements` relationship, so a base reached only through a method
18
+ // parameter type (`greet(b: Base)`) must not leak into the heritage set; a path
19
+ // heuristic that treated any mention of Base as inheritance would over-match
20
+ // here.
21
+ //
22
+ // 1. Compile a fixture with `interface Derived extends Base`,
23
+ // `class Impl implements Base`, and `class Unrelated` whose only Base
24
+ // reference is the parameter type of a method.
25
+ // 2. Build the graph.
26
+ // 3. Assert heritage edges Derived->Base and Impl->Base exist, and that
27
+ // Unrelated->Base is a type-ref edge but not a heritage edge.
28
+ func TestHeritageEdgesCoverImplementsAndInterfaceExtends(t *testing.T) {
29
+ root := t.TempDir()
30
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
31
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export interface Base {}
32
+ export interface Derived extends Base {}
33
+ export class Impl implements Base {}
34
+ export class Unrelated {
35
+ greet(b: Base): void {
36
+ void b;
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
+ base := nodeID(path, "Base", NodeInterface)
54
+ derived := nodeID(path, "Derived", NodeInterface)
55
+ impl := nodeID(path, "Impl", NodeClass)
56
+ unrelated := nodeID(path, "Unrelated", NodeClass)
57
+ greet := nodeID(path, "Unrelated.greet", NodeMethod)
58
+
59
+ // Interface `extends`: Derived inherits Base.
60
+ if !hasEdge(graph, derived, base, EdgeHeritage) {
61
+ t.Fatalf("missing heritage edge Derived -> Base (interface extends); edges: %v", graph.Edges)
62
+ }
63
+ // Class `implements`: Impl declares Base as a heritage base.
64
+ if !hasEdge(graph, impl, base, EdgeHeritage) {
65
+ t.Fatalf("missing heritage edge Impl -> Base (class implements); edges: %v", graph.Edges)
66
+ }
67
+ // Negative twin: the base named only in a method parameter type is a type-ref
68
+ // dependency of that method (Unrelated.greet -> Base), never a heritage edge —
69
+ // not from the class and not from the method.
70
+ if hasEdge(graph, unrelated, base, EdgeHeritage) || hasEdge(graph, greet, base, EdgeHeritage) {
71
+ t.Fatalf("a parameter-type reference to Base was misclassified as a heritage edge; edges: %v", graph.Edges)
72
+ }
73
+ if !hasEdge(graph, greet, base, EdgeTypeRef) {
74
+ t.Fatalf("missing type-ref edge Unrelated.greet -> Base (parameter type); edges: %v", graph.Edges)
75
+ }
76
+ }
@@ -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
+ // TestHeritageEdgesKeepExtendsAndImplementsToSameBase verifies that a class which
11
+ // both extends and implements the same base records two distinct heritage edges
12
+ // — `extends` and `implements` — rather than collapsing to one. The edge dedup
13
+ // keys on the emitted wire kind, so two relationships to one target that mean
14
+ // different things both survive; a dedup on the internal edge kind alone would
15
+ // drop the second clause and the dump would claim only inheritance or only
16
+ // conformance, never both.
17
+ //
18
+ // 1. Compile a fixture with `class Derived extends Base implements Base`.
19
+ // 2. Build the graph.
20
+ // 3. Assert both an `extends` and an `implements` heritage edge Derived -> Base,
21
+ // and that they map to the distinct wire kinds the dump emits.
22
+ func TestHeritageEdgesKeepExtendsAndImplementsToSameBase(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 class Base {
26
+ value(): number {
27
+ return 1;
28
+ }
29
+ }
30
+ export class Derived extends Base implements Base {}
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
+ derived := nodeID(path, "Derived", NodeClass)
46
+ base := nodeID(path, "Base", NodeClass)
47
+
48
+ wireKinds := map[string]bool{}
49
+ for _, edge := range graph.Edges {
50
+ if edge.From != derived || edge.To != base || edge.Kind != EdgeHeritage {
51
+ continue
52
+ }
53
+ wireKinds[dumpEdgeKind(edge)] = true
54
+ }
55
+ if !wireKinds["extends"] || !wireKinds["implements"] {
56
+ t.Fatalf(
57
+ "Derived -> Base heritage edges = %v; want both extends and implements; edges: %v",
58
+ wireKinds, graph.Edges,
59
+ )
60
+ }
61
+ }
@@ -0,0 +1,54 @@
1
+ package graph
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // TestHeritageEdgesRecordExtendsVersusImplementsOrigin verifies that a heritage
11
+ // edge carries the clause keyword as Origin, so the dump can split the single
12
+ // internal heritage kind into the schema's `extends` and `implements`: a class
13
+ // superclass records "extends", a class interface list records "implements".
14
+ //
15
+ // One class declaration carries both clauses at once, so the test pins that the
16
+ // keyword — not the base's declaration kind — selects the origin: `extends Sup`
17
+ // and `implements Iface` on the same class must come out as different origins.
18
+ //
19
+ // 1. Compile `class Sub extends Sup implements Iface` plus the base class and
20
+ // interface.
21
+ // 2. Build the graph.
22
+ // 3. Assert the Sub->Sup heritage edge has Origin "extends" and the Sub->Iface
23
+ // heritage edge has Origin "implements".
24
+ func TestHeritageEdgesRecordExtendsVersusImplementsOrigin(t *testing.T) {
25
+ root := t.TempDir()
26
+ writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
27
+ writeFile(t, filepath.Join(root, "src", "main.ts"), `export interface Iface {}
28
+ export class Sup {}
29
+ export class Sub extends Sup implements Iface {}
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
+ graph := Build(prog)
42
+ path := sourceFile(t, prog, "main.ts").FileName()
43
+
44
+ sub := nodeID(path, "Sub", NodeClass)
45
+ sup := nodeID(path, "Sup", NodeClass)
46
+ iface := nodeID(path, "Iface", NodeInterface)
47
+
48
+ if got := edgeOrigin(graph, sub, sup, EdgeHeritage); got != "extends" {
49
+ t.Fatalf("Sub -> Sup: want heritage Origin \"extends\", got %q; edges: %v", got, graph.Edges)
50
+ }
51
+ if got := edgeOrigin(graph, sub, iface, EdgeHeritage); got != "implements" {
52
+ t.Fatalf("Sub -> Iface: want heritage Origin \"implements\", got %q; edges: %v", got, graph.Edges)
53
+ }
54
+ }