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.
- package/README.md +96 -25
- package/cmd/graphbench/main.go +135 -0
- package/cmd/graphdump/main.go +60 -0
- package/cmd/ttscgraph/dump.go +69 -0
- package/cmd/ttscgraph/main.go +91 -0
- package/cmd/ttscgraph/run_dump_prints_the_graph_as_json_test.go +80 -0
- package/cmd/ttscgraph/run_help_version_and_bad_flags_exit_codes_test.go +75 -0
- package/cmd/utility-host/main.go +4 -1
- package/driver/overlay.go +64 -0
- package/driver/program.go +8 -1
- package/driver/session.go +85 -0
- package/internal/graph/build.go +330 -0
- package/internal/graph/build_records_a_node_per_top_level_declaration_test.go +96 -0
- package/internal/graph/decorators.go +122 -0
- package/internal/graph/decorators_capture_name_and_literal_arguments_test.go +98 -0
- package/internal/graph/diagnostics.go +23 -0
- package/internal/graph/dump.go +390 -0
- package/internal/graph/dump_evidence_skips_leading_trivia_test.go +92 -0
- package/internal/graph/dump_serializes_the_full_graph_test.go +146 -0
- package/internal/graph/edge_spans_point_to_resolved_usage_test.go +90 -0
- package/internal/graph/edges.go +538 -0
- package/internal/graph/edges_cover_class_level_references_test.go +93 -0
- package/internal/graph/enum_initializer_calls_are_edges_test.go +63 -0
- package/internal/graph/exports.go +60 -0
- package/internal/graph/file_diagnostics_match_tsgo_code_and_location_test.go +59 -0
- package/internal/graph/file_diagnostics_report_code_column_and_message_test.go +69 -0
- package/internal/graph/function_locals_do_not_collide_test.go +66 -0
- package/internal/graph/gitignore.go +51 -0
- package/internal/graph/graph.go +119 -0
- package/internal/graph/heritage_edges_cover_implements_and_interface_extends_test.go +76 -0
- package/internal/graph/heritage_edges_keep_extends_and_implements_to_same_base_test.go +61 -0
- package/internal/graph/heritage_edges_record_extends_versus_implements_origin_test.go +54 -0
- package/internal/graph/heritage_edges_traverse_barrels_and_stop_at_external_leaves_test.go +112 -0
- package/internal/graph/namespace_dotted_form_qualifies_deeply_test.go +60 -0
- package/internal/graph/namespace_members_become_qualified_nodes_and_edges_test.go +100 -0
- package/internal/graph/node_identity_is_position_invariant_test.go +84 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +118 -0
- package/internal/graph/nodes_are_marked_exported_through_the_export_table_test.go +61 -0
- package/internal/graph/overload_implementation_replaces_signature_test.go +54 -0
- package/internal/graph/property_member_edges_remain_owner_visible_test.go +83 -0
- package/internal/graph/resolve.go +102 -0
- package/internal/graph/resolve_prefers_implementation_declaration_test.go +50 -0
- package/internal/graph/resolve_unwraps_barrel_reexport_to_real_declaration_test.go +230 -0
- package/internal/graph/type_ref_edges_cover_typeof_as_satisfies_test.go +79 -0
- package/internal/graph/type_ref_edges_resolve_named_types_across_files_test.go +52 -0
- package/internal/graph/value_access_edges_cover_properties_test.go +96 -0
- package/internal/graph/value_call_edges_cover_jsx_components_test.go +82 -0
- package/internal/graph/value_call_edges_cover_methods_and_constructors_test.go +70 -0
- package/internal/graph/value_call_edges_cover_variable_bound_callables_test.go +46 -0
- package/internal/graph/value_call_edges_do_not_double_record_invoked_member_access_test.go +65 -0
- package/internal/graph/value_call_edges_record_construct_versus_call_origin_test.go +75 -0
- package/internal/graph/value_call_edges_resolve_across_files_and_dedupe_test.go +55 -0
- package/internal/graph/value_call_edges_skip_self_and_unresolved_test.go +74 -0
- package/lib/TtscService.d.ts +55 -0
- package/lib/TtscService.js +93 -0
- package/lib/TtscService.js.map +1 -0
- package/lib/compiler/internal/residentTransformProcess.d.ts +47 -0
- package/lib/compiler/internal/residentTransformProcess.js +187 -0
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -0
- package/lib/compiler/internal/startResidentTransform.d.ts +26 -0
- package/lib/compiler/internal/startResidentTransform.js +98 -0
- package/lib/compiler/internal/startResidentTransform.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/package.json +8 -8
- package/shim/ast/shim.go +6 -0
- package/shim/checker/shim.go +22 -12
- package/src/TtscService.ts +94 -0
- package/src/compiler/internal/residentTransformProcess.ts +216 -0
- package/src/compiler/internal/startResidentTransform.ts +126 -0
- package/src/index.ts +1 -0
- package/utility/host.go +8 -2
- package/utility/serve.go +158 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestDumpEvidenceSkipsLeadingTrivia verifies that a node's and an edge's
|
|
12
|
+
// evidence line/column point at the code, not the leading trivia, because
|
|
13
|
+
// tsgo's Pos() is the full-start (it includes the doc comment and indentation
|
|
14
|
+
// before a token).
|
|
15
|
+
//
|
|
16
|
+
// A declaration preceded by a doc comment and a call indented under its function
|
|
17
|
+
// are the two shapes that break a naive Pos->line/col: the node would land on the
|
|
18
|
+
// comment line and the edge column on the indentation. The dump skips leading
|
|
19
|
+
// whitespace and `//` / `/* */` comments first, so this pins both to the real
|
|
20
|
+
// token start.
|
|
21
|
+
//
|
|
22
|
+
// 1. Compile a fixture where `main` carries a block doc comment and calls
|
|
23
|
+
// `helper()` indented two spaces.
|
|
24
|
+
// 2. Build and marshal the dump.
|
|
25
|
+
// 3. Assert the `main` node's evidence starts on the `export function main`
|
|
26
|
+
// line, and the calls edge's evidence is the indented call's line and column.
|
|
27
|
+
func TestDumpEvidenceSkipsLeadingTrivia(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 function helper(): void {}
|
|
31
|
+
/**
|
|
32
|
+
* doc for main
|
|
33
|
+
*/
|
|
34
|
+
export function main(): void {
|
|
35
|
+
helper();
|
|
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
|
+
g := Build(prog)
|
|
49
|
+
data, err := MarshalDump(g, root, "tsconfig.json", nil, SourceTexts(prog), false)
|
|
50
|
+
if err != nil {
|
|
51
|
+
t.Fatalf("MarshalDump: %v", err)
|
|
52
|
+
}
|
|
53
|
+
var dump Dump
|
|
54
|
+
if err := json.Unmarshal(data, &dump); err != nil {
|
|
55
|
+
t.Fatalf("dump is not valid JSON: %v", err)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The `main` declaration starts on line 5 — past its three-line doc comment —
|
|
59
|
+
// not on line 2 where the comment (its leading trivia) begins.
|
|
60
|
+
var main *DumpNode
|
|
61
|
+
for i := range dump.Nodes {
|
|
62
|
+
if dump.Nodes[i].Kind == "function" && dump.Nodes[i].Name == "main" {
|
|
63
|
+
main = &dump.Nodes[i]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if main == nil || main.Evidence == nil {
|
|
67
|
+
t.Fatalf("missing main node with evidence: %+v", dump.Nodes)
|
|
68
|
+
}
|
|
69
|
+
if main.Evidence.StartLine != 5 {
|
|
70
|
+
t.Fatalf("main evidence startLine = %d, want 5 (past the doc comment)", main.Evidence.StartLine)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// The `helper()` call sits on line 6, indented two spaces, so the edge
|
|
74
|
+
// evidence is line 6 column 3 — not line 5 (where the trivia begins) or
|
|
75
|
+
// column 1 (the indentation).
|
|
76
|
+
var call *DumpEdge
|
|
77
|
+
for i := range dump.Edges {
|
|
78
|
+
e := &dump.Edges[i]
|
|
79
|
+
if e.Kind == "calls" && e.From == "src/main.ts#main:function" {
|
|
80
|
+
call = e
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if call == nil || call.Evidence == nil {
|
|
84
|
+
t.Fatalf("missing calls edge with evidence: %+v", dump.Edges)
|
|
85
|
+
}
|
|
86
|
+
if call.Evidence.StartLine != 6 || call.Evidence.StartCol != 3 {
|
|
87
|
+
t.Fatalf(
|
|
88
|
+
"call evidence = line %d col %d, want line 6 col 3 (the indented call)",
|
|
89
|
+
call.Evidence.StartLine, call.Evidence.StartCol,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
|
|
9
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// TestMarshalDumpSerializesTheFullGraph verifies that MarshalDump projects a
|
|
13
|
+
// built graph onto the IGraphDump wire contract the `ttscgraph dump` command
|
|
14
|
+
// prints and the @ttsc/graph engine loads: the project envelope, every node and
|
|
15
|
+
// edge, project-relative paths, line/col evidence, and the lowercase wire keys.
|
|
16
|
+
//
|
|
17
|
+
// The Node/Edge structs carry no json tags, so dump.go's projection is the only
|
|
18
|
+
// thing standing between the Go fields and the wire. A regression there would
|
|
19
|
+
// ship keys or kinds the engine does not read, so the key and kind assertions
|
|
20
|
+
// are load-bearing, not cosmetic.
|
|
21
|
+
//
|
|
22
|
+
// 1. Build a two-function fixture with one call, so the dump has a node set and
|
|
23
|
+
// a value-call edge.
|
|
24
|
+
// 2. Marshal it with source texts and assert the envelope, counts, and that the
|
|
25
|
+
// call edge maps to kind "calls" with a line/col evidence span.
|
|
26
|
+
// 3. Assert paths are project-relative, the wire keys are the lowercase json
|
|
27
|
+
// tags, every edge endpoint resolves to a dumped node, and --pretty indents.
|
|
28
|
+
func TestMarshalDumpSerializesTheFullGraph(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 function helper(): void {}
|
|
32
|
+
export function main(): void {
|
|
33
|
+
helper();
|
|
34
|
+
}
|
|
35
|
+
`)
|
|
36
|
+
|
|
37
|
+
prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
|
|
38
|
+
if err != nil {
|
|
39
|
+
t.Fatal(err)
|
|
40
|
+
}
|
|
41
|
+
if len(diags) != 0 {
|
|
42
|
+
t.Fatalf("unexpected diagnostics: %v", diags)
|
|
43
|
+
}
|
|
44
|
+
defer func() { _ = prog.Close() }()
|
|
45
|
+
|
|
46
|
+
g := Build(prog)
|
|
47
|
+
sources := SourceTexts(prog)
|
|
48
|
+
|
|
49
|
+
data, err := MarshalDump(g, root, "tsconfig.json", nil, sources, false)
|
|
50
|
+
if err != nil {
|
|
51
|
+
t.Fatalf("MarshalDump: %v", err)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
var dump Dump
|
|
55
|
+
if err := json.Unmarshal(data, &dump); err != nil {
|
|
56
|
+
t.Fatalf("dump is not valid JSON: %v\n%s", err, data)
|
|
57
|
+
}
|
|
58
|
+
if dump.Project != root || dump.Tsconfig != "tsconfig.json" {
|
|
59
|
+
t.Fatalf("project/tsconfig not echoed: %q / %q", dump.Project, dump.Tsconfig)
|
|
60
|
+
}
|
|
61
|
+
if len(dump.Nodes) != len(g.Nodes) {
|
|
62
|
+
t.Fatalf("dumped %d nodes, graph has %d", len(dump.Nodes), len(g.Nodes))
|
|
63
|
+
}
|
|
64
|
+
if len(dump.Edges) != len(g.Edges) {
|
|
65
|
+
t.Fatalf("dumped %d edges, graph has %d", len(dump.Edges), len(g.Edges))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Locate the two function nodes by their wire shape, and confirm paths and ids
|
|
69
|
+
// are project-relative (no temp-dir prefix leaked).
|
|
70
|
+
byID := make(map[string]DumpNode, len(dump.Nodes))
|
|
71
|
+
var mainID, helperID string
|
|
72
|
+
for _, n := range dump.Nodes {
|
|
73
|
+
byID[n.ID] = n
|
|
74
|
+
if n.Kind == "function" && n.Name == "main" {
|
|
75
|
+
mainID = n.ID
|
|
76
|
+
}
|
|
77
|
+
if n.Kind == "function" && n.Name == "helper" {
|
|
78
|
+
helperID = n.ID
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if mainID == "" || helperID == "" {
|
|
82
|
+
t.Fatalf("missing main/helper function nodes: %v", dump.Nodes)
|
|
83
|
+
}
|
|
84
|
+
if mainID != "src/main.ts#main:function" {
|
|
85
|
+
t.Fatalf("node id not project-relative: %q", mainID)
|
|
86
|
+
}
|
|
87
|
+
if byID[mainID].File != "src/main.ts" {
|
|
88
|
+
t.Fatalf("node file not project-relative: %q", byID[mainID].File)
|
|
89
|
+
}
|
|
90
|
+
if byID[mainID].Evidence == nil || byID[mainID].Evidence.StartLine == 0 {
|
|
91
|
+
t.Fatalf("main node missing line/col evidence: %+v", byID[mainID].Evidence)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The main -> helper call maps to kind "calls" with a located evidence span.
|
|
95
|
+
var call *DumpEdge
|
|
96
|
+
for i := range dump.Edges {
|
|
97
|
+
e := &dump.Edges[i]
|
|
98
|
+
if e.From == mainID && e.To == helperID && e.Kind == "calls" {
|
|
99
|
+
call = e
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if call == nil {
|
|
103
|
+
t.Fatalf("no calls edge main -> helper in dump:\n%s", data)
|
|
104
|
+
}
|
|
105
|
+
if call.Evidence == nil || call.Evidence.StartLine == 0 || call.Evidence.File != "src/main.ts" {
|
|
106
|
+
t.Fatalf("call edge missing project-relative line/col evidence: %+v", call.Evidence)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Every edge endpoint resolves to a dumped node.
|
|
110
|
+
for _, e := range dump.Edges {
|
|
111
|
+
if _, ok := byID[e.From]; !ok {
|
|
112
|
+
t.Fatalf("edge from %q has no dumped node", e.From)
|
|
113
|
+
}
|
|
114
|
+
if _, ok := byID[e.To]; !ok {
|
|
115
|
+
t.Fatalf("edge to %q has no dumped node", e.To)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// The wire keys are the lowercase json tags, not the Go field names.
|
|
120
|
+
s := string(data)
|
|
121
|
+
for _, key := range []string{`"id":`, `"kind":`, `"name":`, `"file":`, `"external":`, `"from":`, `"to":`} {
|
|
122
|
+
if !strings.Contains(s, key) {
|
|
123
|
+
t.Fatalf("dump missing wire key %s:\n%s", key, s)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for _, leaked := range []string{`"ID":`, `"Kind":`, `"From":`, `"External":`} {
|
|
127
|
+
if strings.Contains(s, leaked) {
|
|
128
|
+
t.Fatalf("dump leaked Go field name %s:\n%s", leaked, s)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// The dump carries no ceremony keys: it is wholly checker-resolved, so it has
|
|
132
|
+
// no per-edge trust flags and no schema version to negotiate.
|
|
133
|
+
for _, gone := range []string{`"schemaVersion"`, `"provenance":`, `"confidence":`} {
|
|
134
|
+
if strings.Contains(s, gone) {
|
|
135
|
+
t.Fatalf("dump still emits removed key %s:\n%s", gone, s)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
pretty, err := MarshalDump(g, root, "tsconfig.json", nil, sources, true)
|
|
140
|
+
if err != nil {
|
|
141
|
+
t.Fatalf("MarshalDump pretty: %v", err)
|
|
142
|
+
}
|
|
143
|
+
if !strings.Contains(string(pretty), "\n ") {
|
|
144
|
+
t.Fatalf("--pretty output is not indented:\n%s", pretty)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
// TestEdgeSpansPointToResolvedUsage verifies each checker-resolved edge keeps
|
|
12
|
+
// the source expression that produced it. The edge is still deduped by
|
|
13
|
+
// from/to/kind, but MCP needs the first concrete use span so a truncated
|
|
14
|
+
// declaration can reopen the right line without string-searching for a target
|
|
15
|
+
// name that may appear elsewhere.
|
|
16
|
+
func TestEdgeSpansPointToResolvedUsage(t *testing.T) {
|
|
17
|
+
root := t.TempDir()
|
|
18
|
+
writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
|
|
19
|
+
path := filepath.Join(root, "src", "main.ts")
|
|
20
|
+
writeFile(t, path, `export function helper(): number {
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function caller(): number {
|
|
25
|
+
const helperShadow = 0;
|
|
26
|
+
return helper() + helperShadow;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class Box {
|
|
30
|
+
value = 1;
|
|
31
|
+
|
|
32
|
+
read(): number {
|
|
33
|
+
return this.value;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
`)
|
|
37
|
+
|
|
38
|
+
prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
|
|
39
|
+
if err != nil {
|
|
40
|
+
t.Fatal(err)
|
|
41
|
+
}
|
|
42
|
+
if len(diags) != 0 {
|
|
43
|
+
t.Fatalf("unexpected diagnostics: %v", diags)
|
|
44
|
+
}
|
|
45
|
+
defer func() { _ = prog.Close() }()
|
|
46
|
+
|
|
47
|
+
graph := Build(prog)
|
|
48
|
+
caller := nodeByName(graph, "caller", NodeFunction)
|
|
49
|
+
helper := nodeByName(graph, "helper", NodeFunction)
|
|
50
|
+
read := nodeByName(graph, "Box.read", NodeMethod)
|
|
51
|
+
value := nodeByName(graph, "Box.value", NodeVariable)
|
|
52
|
+
if caller == nil || helper == nil || read == nil || value == nil {
|
|
53
|
+
t.Fatalf("missing fixture nodes: caller=%v helper=%v read=%v value=%v", caller, helper, read, value)
|
|
54
|
+
}
|
|
55
|
+
source := prog.SourceFile(caller.File).Text()
|
|
56
|
+
|
|
57
|
+
call := findEdge(graph, caller.ID, helper.ID, EdgeValueCall)
|
|
58
|
+
if call == nil {
|
|
59
|
+
t.Fatalf("missing caller -> helper value-call edge; edges: %v", graph.Edges)
|
|
60
|
+
}
|
|
61
|
+
if got := strings.TrimSpace(source[call.Pos:call.End]); got != "helper" {
|
|
62
|
+
t.Fatalf("value-call span = %q, want helper", got)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
access := findEdge(graph, read.ID, value.ID, EdgeValueAccess)
|
|
66
|
+
if access == nil {
|
|
67
|
+
t.Fatalf("missing Box.read -> Box.value value-access edge; edges: %v", graph.Edges)
|
|
68
|
+
}
|
|
69
|
+
if got := source[access.Pos:access.End]; !strings.Contains(got, "this.value") {
|
|
70
|
+
t.Fatalf("value-access span = %q, want this.value expression", got)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func nodeByName(graph *Graph, name string, kind NodeKind) *Node {
|
|
75
|
+
for _, node := range graph.Nodes {
|
|
76
|
+
if node.Name == name && node.Kind == kind {
|
|
77
|
+
return node
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return nil
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func findEdge(graph *Graph, from, to string, kind EdgeKind) *Edge {
|
|
84
|
+
for _, edge := range graph.Edges {
|
|
85
|
+
if edge.From == from && edge.To == to && edge.Kind == kind {
|
|
86
|
+
return edge
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return nil
|
|
90
|
+
}
|