ttsc 0.16.3 → 0.16.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -25
- package/cmd/graphbench/main.go +135 -0
- package/cmd/graphdump/main.go +60 -0
- package/cmd/ttscgraph/dump.go +69 -0
- package/cmd/ttscgraph/main.go +91 -0
- package/cmd/ttscgraph/run_dump_prints_the_graph_as_json_test.go +80 -0
- package/cmd/ttscgraph/run_help_version_and_bad_flags_exit_codes_test.go +75 -0
- package/cmd/utility-host/main.go +4 -1
- package/driver/overlay.go +64 -0
- package/driver/program.go +8 -1
- package/driver/session.go +85 -0
- package/internal/graph/build.go +330 -0
- package/internal/graph/build_records_a_node_per_top_level_declaration_test.go +96 -0
- package/internal/graph/decorators.go +120 -0
- package/internal/graph/decorators_capture_name_and_literal_arguments_test.go +98 -0
- package/internal/graph/diagnostics.go +23 -0
- package/internal/graph/dump.go +374 -0
- package/internal/graph/dump_evidence_skips_leading_trivia_test.go +92 -0
- package/internal/graph/dump_serializes_the_full_graph_test.go +146 -0
- package/internal/graph/edge_spans_point_to_resolved_usage_test.go +90 -0
- package/internal/graph/edges.go +581 -0
- package/internal/graph/edges_cover_class_level_references_test.go +93 -0
- package/internal/graph/enum_initializer_calls_are_edges_test.go +63 -0
- package/internal/graph/exports.go +60 -0
- package/internal/graph/file_diagnostics_match_tsgo_code_and_location_test.go +59 -0
- package/internal/graph/file_diagnostics_report_code_column_and_message_test.go +69 -0
- package/internal/graph/function_locals_do_not_collide_test.go +66 -0
- package/internal/graph/gitignore.go +51 -0
- package/internal/graph/graph.go +122 -0
- package/internal/graph/heritage_edges_cover_implements_and_interface_extends_test.go +76 -0
- package/internal/graph/heritage_edges_keep_extends_and_implements_to_same_base_test.go +61 -0
- package/internal/graph/heritage_edges_record_extends_versus_implements_origin_test.go +54 -0
- package/internal/graph/heritage_edges_traverse_barrels_and_stop_at_external_leaves_test.go +112 -0
- package/internal/graph/namespace_dotted_form_qualifies_deeply_test.go +60 -0
- package/internal/graph/namespace_members_become_qualified_nodes_and_edges_test.go +100 -0
- package/internal/graph/node_identity_is_position_invariant_test.go +84 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +118 -0
- package/internal/graph/nodes_are_marked_exported_through_the_export_table_test.go +61 -0
- package/internal/graph/overload_implementation_replaces_signature_test.go +54 -0
- package/internal/graph/property_member_edges_remain_owner_visible_test.go +83 -0
- package/internal/graph/resolve.go +102 -0
- package/internal/graph/resolve_prefers_implementation_declaration_test.go +50 -0
- package/internal/graph/resolve_unwraps_barrel_reexport_to_real_declaration_test.go +230 -0
- package/internal/graph/type_ref_edges_cover_typeof_as_satisfies_test.go +79 -0
- package/internal/graph/type_ref_edges_resolve_named_types_across_files_test.go +52 -0
- package/internal/graph/value_access_edges_cover_properties_test.go +96 -0
- package/internal/graph/value_call_edges_attribute_function_assignments_to_members_test.go +59 -0
- package/internal/graph/value_call_edges_cover_jsx_components_test.go +82 -0
- package/internal/graph/value_call_edges_cover_methods_and_constructors_test.go +70 -0
- package/internal/graph/value_call_edges_cover_variable_bound_callables_test.go +46 -0
- package/internal/graph/value_call_edges_do_not_double_record_invoked_member_access_test.go +65 -0
- package/internal/graph/value_call_edges_record_construct_versus_call_origin_test.go +75 -0
- package/internal/graph/value_call_edges_resolve_across_files_and_dedupe_test.go +55 -0
- package/internal/graph/value_call_edges_skip_self_and_unresolved_test.go +74 -0
- package/lib/TtscService.d.ts +55 -0
- package/lib/TtscService.js +93 -0
- package/lib/TtscService.js.map +1 -0
- package/lib/compiler/internal/residentTransformProcess.d.ts +47 -0
- package/lib/compiler/internal/residentTransformProcess.js +187 -0
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -0
- package/lib/compiler/internal/startResidentTransform.d.ts +26 -0
- package/lib/compiler/internal/startResidentTransform.js +98 -0
- package/lib/compiler/internal/startResidentTransform.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/package.json +8 -8
- package/shim/ast/shim.go +6 -0
- package/shim/checker/shim.go +22 -12
- package/src/TtscService.ts +94 -0
- package/src/compiler/internal/residentTransformProcess.ts +216 -0
- package/src/compiler/internal/startResidentTransform.ts +126 -0
- package/src/index.ts +1 -0
- package/utility/host.go +8 -2
- package/utility/serve.go +158 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
A `typescript-go` toolchain for compiler-powered plugins and type-safe execution.
|
|
8
8
|
|
|
9
|
-
Benchmarked against the legacy `tsc` + `eslint`/`prettier` path on real repositories; see the [benchmark guide](https://ttsc.dev/docs/benchmark) for per-project ratios.
|
|
9
|
+
Benchmarked against the legacy `tsc` + `eslint`/`prettier` path on real repositories; see the [benchmark guide](https://ttsc.dev/docs/benchmark/performance) for per-project ratios.
|
|
10
10
|
|
|
11
11
|
- **`ttsc`**: build, check, and transform.
|
|
12
12
|
- **`ttsx`**: execute TypeScript with type checking.
|
|
@@ -15,6 +15,8 @@ Benchmarked against the legacy `tsc` + `eslint`/`prettier` path on real reposito
|
|
|
15
15
|
- **`@ttsc/lint`**: replaces `eslint` and `prettier`.
|
|
16
16
|
- lint violations as TS compile errors.
|
|
17
17
|
- format autofixes via `ttsc format`.
|
|
18
|
+
- **`@ttsc/graph`**: a code map for coding agents, over MCP.
|
|
19
|
+
- what calls what, and a change's blast radius, from the type checker.
|
|
18
20
|
- **plugin support**: compiler-powered libraries, such as `typia`.
|
|
19
21
|
|
|
20
22
|
## Setup
|
|
@@ -49,29 +51,6 @@ Rewrite source files in place with the `@ttsc/lint` format rules:
|
|
|
49
51
|
npx ttsc format
|
|
50
52
|
```
|
|
51
53
|
|
|
52
|
-
### VS Code Extension
|
|
53
|
-
|
|
54
|
-
Install the VS Code extension for live TypeScript-Go editor features plus saved-state ttsc plugin diagnostics and actions.
|
|
55
|
-
|
|
56
|
-
Install it from the VS Code Marketplace by searching `ttsc`, or run:
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
npx @ttsc/vscode
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Then turn on format-on-save in `.vscode/settings.json`:
|
|
63
|
-
|
|
64
|
-
```jsonc
|
|
65
|
-
"[typescript][typescriptreact]": {
|
|
66
|
-
"editor.defaultFormatter": "samchon.ttsc",
|
|
67
|
-
"editor.formatOnSave": true
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Lint fixes stay off-save by default; opt in with `"editor.codeActionsOnSave": { "source.fixAll.ttsc": "explicit" }`.
|
|
72
|
-
|
|
73
|
-
See [`@ttsc/vscode`](https://github.com/samchon/ttsc/tree/master/packages/vscode) for requirements and settings.
|
|
74
|
-
|
|
75
54
|
### Bundlers
|
|
76
55
|
|
|
77
56
|
Use `@ttsc/unplugin` when a bundler owns your build.
|
|
@@ -109,6 +88,79 @@ Supported bundlers:
|
|
|
109
88
|
|
|
110
89
|
See [`@ttsc/unplugin`](https://github.com/samchon/ttsc/tree/master/packages/unplugin) for full setup and adapter options.
|
|
111
90
|
|
|
91
|
+
### React Native / Expo
|
|
92
|
+
|
|
93
|
+
React Native and Expo bundle with Metro, so the `ttsc` CLI and `@ttsc/unplugin` never run. `@ttsc/metro` is a Metro transformer that runs `ttsc` plugins on each TypeScript file, then hands the result to your existing Expo or React-Native Babel transformer.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm install -D ttsc @ttsc/lint typescript@rc
|
|
97
|
+
npm install -D @ttsc/metro
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Wrap your Metro config (CommonJS, the standard for `metro.config.js`):
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
// metro.config.js (Expo)
|
|
104
|
+
const { getDefaultConfig } = require("expo/metro-config");
|
|
105
|
+
const { withTtsc } = require("@ttsc/metro");
|
|
106
|
+
|
|
107
|
+
module.exports = withTtsc(getDefaultConfig(__dirname));
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For bare React Native, wrap `getDefaultConfig` from `@react-native/metro-config` instead. See [`@ttsc/metro`](https://github.com/samchon/ttsc/tree/master/packages/metro) for options and the v1 caveats.
|
|
111
|
+
|
|
112
|
+
### VS Code Extension
|
|
113
|
+
|
|
114
|
+
Install the VS Code extension for live TypeScript-Go editor features plus saved-state ttsc plugin diagnostics and actions.
|
|
115
|
+
|
|
116
|
+
Install it from the VS Code Marketplace by searching `ttsc`, or run:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx @ttsc/vscode
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Then turn on format-on-save in `.vscode/settings.json`:
|
|
123
|
+
|
|
124
|
+
```jsonc
|
|
125
|
+
"[typescript][typescriptreact]": {
|
|
126
|
+
"editor.defaultFormatter": "samchon.ttsc",
|
|
127
|
+
"editor.formatOnSave": true
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Lint fixes stay off-save by default; opt in with `"editor.codeActionsOnSave": { "source.fixAll.ttsc": "explicit" }`.
|
|
132
|
+
|
|
133
|
+
See [`@ttsc/vscode`](https://github.com/samchon/ttsc/tree/master/packages/vscode) for requirements and settings.
|
|
134
|
+
|
|
135
|
+
### Coding Agents (MCP)
|
|
136
|
+
|
|
137
|
+
`@ttsc/graph` gives a coding agent a checker-resolved map of your project, over MCP.
|
|
138
|
+
|
|
139
|
+
It answers what relates to a symbol and what a change affects, straight from the type checker, so the agent stops grepping and re-reading files.
|
|
140
|
+
|
|
141
|
+
It also carries the project's full diagnostics: type errors, `@ttsc/lint` violations, and plugin findings. They are fused onto the graph, so an agent sees a change's reach over what is already broken before editing.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npm install -D ttsc @ttsc/graph typescript@rc
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Point your agent's MCP client at it. For Claude Code:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"mcpServers": {
|
|
152
|
+
"ttsc-graph": {
|
|
153
|
+
"command": "npx",
|
|
154
|
+
"args": ["-y", "@ttsc/graph"]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
On codegraph's own agent-cost benchmark, Claude agents answer reading zero files, cutting tokens by 77% to 86% and tool calls by 94% to 95%. See [`@ttsc/graph`](https://github.com/samchon/ttsc/tree/master/packages/graph) and the [benchmark](https://ttsc.dev/docs/benchmark/graph).
|
|
161
|
+
|
|
162
|
+
Your agent picks the tools up from the MCP handshake and uses them on its own. See [Setup](https://ttsc.dev/docs/setup#coding-agents-ttscgraph) for the full walk-through.
|
|
163
|
+
|
|
112
164
|
## Plugins
|
|
113
165
|
|
|
114
166
|
Plugins let libraries add compile-time checks, transforms, and type-driven code generation to normal `ttsc` and `ttsx` runs.
|
|
@@ -196,6 +248,23 @@ if (result.type === "success") {
|
|
|
196
248
|
}
|
|
197
249
|
```
|
|
198
250
|
|
|
251
|
+
When one process transforms a project's files repeatedly (a watch server, an editor, a codegen tool), `TtscService` keeps the compiler host warm instead of recompiling per call. It compiles the project once, then answers per-file transform requests, and reflects in-memory edits through `updateFile`:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import { TtscService } from "ttsc";
|
|
255
|
+
|
|
256
|
+
const service = new TtscService({ cwd: "./project" });
|
|
257
|
+
try {
|
|
258
|
+
const code = await service.transformFile("src/index.ts");
|
|
259
|
+
await service.updateFile("src/index.ts", "export const x: number = 2;\n");
|
|
260
|
+
const next = await service.transformFile("src/index.ts");
|
|
261
|
+
} finally {
|
|
262
|
+
service.dispose();
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`TtscService` runs through the linked-plugin host, so the project must declare at least one transform-stage plugin.
|
|
267
|
+
|
|
199
268
|
See the [Programmatic API guide](https://ttsc.dev/docs/ttsc/api) for the full lifecycle, plugin overrides, and patterns. For browser embedding, see [`@ttsc/wasm`](https://github.com/samchon/ttsc/tree/master/packages/wasm) and the higher-level [`@ttsc/playground`](https://github.com/samchon/ttsc/tree/master/packages/playground) package.
|
|
200
269
|
|
|
201
270
|
### List of Plugins
|
|
@@ -204,9 +273,11 @@ See the [Programmatic API guide](https://ttsc.dev/docs/ttsc/api) for the full li
|
|
|
204
273
|
|
|
205
274
|
- [`@ttsc/banner`](https://github.com/samchon/ttsc/tree/master/packages/banner): adds `@packageDocumentation` JSDoc banners.
|
|
206
275
|
- [`@ttsc/lint`](https://github.com/samchon/ttsc/tree/master/packages/lint): lints and formats TypeScript source.
|
|
276
|
+
- [`@ttsc/graph`](https://github.com/samchon/ttsc/tree/master/packages/graph): MCP server exposing a checker-resolved code graph and diagnostics to coding agents.
|
|
207
277
|
- [`@ttsc/paths`](https://github.com/samchon/ttsc/tree/master/packages/paths): rewrites source path aliases so JS and declaration emit receive relative imports.
|
|
208
278
|
- [`@ttsc/strip`](https://github.com/samchon/ttsc/tree/master/packages/strip): removes configured calls and `debugger` statements.
|
|
209
279
|
- [`@ttsc/unplugin`](https://github.com/samchon/ttsc/tree/master/packages/unplugin): runs `ttsc` plugins inside bundlers supported by `unplugin`.
|
|
280
|
+
- [`@ttsc/metro`](https://github.com/samchon/ttsc/tree/master/packages/metro): runs `ttsc` plugins inside Metro for React Native and Expo.
|
|
210
281
|
|
|
211
282
|
Plugin authors should start from the [`Guide Documents`](https://ttsc.dev/docs).
|
|
212
283
|
|
|
@@ -227,4 +298,4 @@ Your [donation](https://github.com/sponsors/samchon) encourages `ttsc` developme
|
|
|
227
298
|
|
|
228
299
|
- TypeScript runners: [`ts-node`](https://github.com/TypeStrong/ts-node) and [`tsx`](https://github.com/privatenumber/tsx)
|
|
229
300
|
- Transformer tooling: [`ttypescript`](https://github.com/cevek/ttypescript) and [`ts-patch`](https://github.com/nonara/ts-patch)
|
|
230
|
-
- Inspired by: [`typical`](https://github.com/elliots/typical)
|
|
301
|
+
- Inspired by: [`typical`](https://github.com/elliots/typical), [`tsgonest`](https://github.com/tsgonest/tsgonest) and [`codegraph`](https://github.com/colbymchenry/codegraph).
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Command graphbench measures @ttsc/graph against a real project: how long the
|
|
2
|
+
// resident Program takes to load, how long graph extraction takes on top of that
|
|
3
|
+
// already-built Program, how many nodes and edges result, and the codegraph-style
|
|
4
|
+
// "fair coverage" (share of symbol-bearing source files with at least one
|
|
5
|
+
// resolved cross-file edge). It prints one JSON line so an orchestrator can run
|
|
6
|
+
// it N times and take medians.
|
|
7
|
+
//
|
|
8
|
+
// Counts and coverage are deterministic; timings are indicative and only
|
|
9
|
+
// meaningful on a quiet host (see .codex/skills/benchmark).
|
|
10
|
+
package main
|
|
11
|
+
|
|
12
|
+
import (
|
|
13
|
+
"encoding/json"
|
|
14
|
+
"flag"
|
|
15
|
+
"fmt"
|
|
16
|
+
"os"
|
|
17
|
+
"time"
|
|
18
|
+
|
|
19
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
20
|
+
"github.com/samchon/ttsc/packages/ttsc/internal/graph"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
type metrics struct {
|
|
24
|
+
Project string `json:"project"`
|
|
25
|
+
LoadMs float64 `json:"loadMs"`
|
|
26
|
+
BuildMs float64 `json:"buildMs"`
|
|
27
|
+
BuildShare float64 `json:"buildShareOfLoad"`
|
|
28
|
+
SourceFiles int `json:"sourceFiles"`
|
|
29
|
+
Nodes int `json:"nodes"`
|
|
30
|
+
ExternalNodes int `json:"externalNodes"`
|
|
31
|
+
Edges map[string]int `json:"edges"`
|
|
32
|
+
TotalEdges int `json:"totalEdges"`
|
|
33
|
+
SymbolFiles int `json:"symbolFiles"`
|
|
34
|
+
CoveredFiles int `json:"coveredFiles"`
|
|
35
|
+
Coverage float64 `json:"coverage"`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func main() {
|
|
39
|
+
os.Exit(run())
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func run() int {
|
|
43
|
+
cwd := flag.String("cwd", ".", "project root")
|
|
44
|
+
tsconfig := flag.String("tsconfig", "tsconfig.json", "tsconfig path, relative to cwd")
|
|
45
|
+
flag.Parse()
|
|
46
|
+
|
|
47
|
+
loadStart := time.Now()
|
|
48
|
+
prog, _, err := driver.LoadProgram(*cwd, *tsconfig, driver.LoadProgramOptions{})
|
|
49
|
+
loadMs := time.Since(loadStart).Seconds() * 1000
|
|
50
|
+
if err != nil {
|
|
51
|
+
fmt.Fprintf(os.Stderr, "graphbench: could not load %s/%s: %v\n", *cwd, *tsconfig, err)
|
|
52
|
+
return 1
|
|
53
|
+
}
|
|
54
|
+
if prog == nil {
|
|
55
|
+
fmt.Fprintf(os.Stderr, "graphbench: could not load %s/%s\n", *cwd, *tsconfig)
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
defer func() { _ = prog.Close() }()
|
|
59
|
+
|
|
60
|
+
buildStart := time.Now()
|
|
61
|
+
g := graph.Build(prog)
|
|
62
|
+
buildMs := time.Since(buildStart).Seconds() * 1000
|
|
63
|
+
|
|
64
|
+
m := summarize(g, len(prog.SourceFiles()))
|
|
65
|
+
m.Project = *cwd
|
|
66
|
+
m.LoadMs = loadMs
|
|
67
|
+
m.BuildMs = buildMs
|
|
68
|
+
if loadMs > 0 {
|
|
69
|
+
m.BuildShare = buildMs / loadMs
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
out, err := json.Marshal(m)
|
|
73
|
+
if err != nil {
|
|
74
|
+
fmt.Fprintf(os.Stderr, "graphbench: %v\n", err)
|
|
75
|
+
return 1
|
|
76
|
+
}
|
|
77
|
+
fmt.Println(string(out))
|
|
78
|
+
return 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// summarize reduces a built graph to the reported metrics: node and external
|
|
82
|
+
// counts, edges per kind, and fair coverage (workspace files holding at least one
|
|
83
|
+
// node that an edge connects to a node in another file).
|
|
84
|
+
func summarize(g *graph.Graph, sourceFiles int) metrics {
|
|
85
|
+
edges := map[string]int{
|
|
86
|
+
string(graph.EdgeHeritage): 0,
|
|
87
|
+
string(graph.EdgeValueCall): 0,
|
|
88
|
+
string(graph.EdgeTypeRef): 0,
|
|
89
|
+
}
|
|
90
|
+
for _, edge := range g.Edges {
|
|
91
|
+
edges[string(edge.Kind)]++
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fileOf := make(map[string]string, len(g.Nodes))
|
|
95
|
+
symbolFiles := map[string]bool{}
|
|
96
|
+
external := 0
|
|
97
|
+
for id, node := range g.Nodes {
|
|
98
|
+
fileOf[id] = node.File
|
|
99
|
+
if node.External {
|
|
100
|
+
external++
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
symbolFiles[node.File] = true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
coveredFiles := map[string]bool{}
|
|
107
|
+
for _, edge := range g.Edges {
|
|
108
|
+
from, to := fileOf[edge.From], fileOf[edge.To]
|
|
109
|
+
if from == "" || to == "" || from == to {
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
if symbolFiles[from] {
|
|
113
|
+
coveredFiles[from] = true
|
|
114
|
+
}
|
|
115
|
+
if symbolFiles[to] {
|
|
116
|
+
coveredFiles[to] = true
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
coverage := 0.0
|
|
121
|
+
if len(symbolFiles) > 0 {
|
|
122
|
+
coverage = float64(len(coveredFiles)) / float64(len(symbolFiles))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return metrics{
|
|
126
|
+
SourceFiles: sourceFiles,
|
|
127
|
+
Nodes: len(g.Nodes),
|
|
128
|
+
ExternalNodes: external,
|
|
129
|
+
Edges: edges,
|
|
130
|
+
TotalEdges: len(g.Edges),
|
|
131
|
+
SymbolFiles: len(symbolFiles),
|
|
132
|
+
CoveredFiles: len(coveredFiles),
|
|
133
|
+
Coverage: coverage,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Command graphdump builds the @ttsc/graph code graph for a project and prints
|
|
2
|
+
// the entire graph (every node and every edge, with none of the MCP response
|
|
3
|
+
// caps) as one JSON document. It is the repo-internal one-shot used by the
|
|
4
|
+
// benchmark graph viewer pipeline; the shipped equivalent is `ttscgraph dump`. Both
|
|
5
|
+
// serialize through graph.MarshalDump.
|
|
6
|
+
package main
|
|
7
|
+
|
|
8
|
+
import (
|
|
9
|
+
"flag"
|
|
10
|
+
"fmt"
|
|
11
|
+
"os"
|
|
12
|
+
"path/filepath"
|
|
13
|
+
|
|
14
|
+
shimtspath "github.com/microsoft/typescript-go/shim/tspath"
|
|
15
|
+
|
|
16
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
17
|
+
"github.com/samchon/ttsc/packages/ttsc/internal/graph"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
func main() {
|
|
21
|
+
os.Exit(run())
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func run() int {
|
|
25
|
+
cwd := flag.String("cwd", ".", "project root")
|
|
26
|
+
tsconfig := flag.String("tsconfig", "tsconfig.json", "tsconfig path, relative to cwd")
|
|
27
|
+
pretty := flag.Bool("pretty", false, "indent the JSON output")
|
|
28
|
+
flag.Parse()
|
|
29
|
+
|
|
30
|
+
// Resolve the project root the same way LoadProgram does (absolute, then
|
|
31
|
+
// tsgo-normalized) so it shares the node file paths' drive-letter case on
|
|
32
|
+
// Windows; otherwise the dump's prefix-based relativization would miss and
|
|
33
|
+
// leave every path absolute.
|
|
34
|
+
root := *cwd
|
|
35
|
+
if abs, err := filepath.Abs(root); err == nil {
|
|
36
|
+
root = abs
|
|
37
|
+
}
|
|
38
|
+
root = shimtspath.ResolvePath(root)
|
|
39
|
+
|
|
40
|
+
prog, _, err := driver.LoadProgram(root, *tsconfig, driver.LoadProgramOptions{})
|
|
41
|
+
if err != nil {
|
|
42
|
+
fmt.Fprintf(os.Stderr, "graphdump: could not load %s/%s: %v\n", root, *tsconfig, err)
|
|
43
|
+
return 1
|
|
44
|
+
}
|
|
45
|
+
if prog == nil {
|
|
46
|
+
fmt.Fprintf(os.Stderr, "graphdump: could not load %s/%s\n", root, *tsconfig)
|
|
47
|
+
return 1
|
|
48
|
+
}
|
|
49
|
+
defer func() { _ = prog.Close() }()
|
|
50
|
+
|
|
51
|
+
g := graph.Build(prog)
|
|
52
|
+
ignored := graph.GitIgnoredFiles(root, g)
|
|
53
|
+
data, err := graph.MarshalDump(g, root, *tsconfig, ignored, graph.SourceTexts(prog), *pretty)
|
|
54
|
+
if err != nil {
|
|
55
|
+
fmt.Fprintf(os.Stderr, "graphdump: %v\n", err)
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
fmt.Println(string(data))
|
|
59
|
+
return 0
|
|
60
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"flag"
|
|
5
|
+
"fmt"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
shimtspath "github.com/microsoft/typescript-go/shim/tspath"
|
|
10
|
+
|
|
11
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
12
|
+
"github.com/samchon/ttsc/packages/ttsc/internal/graph"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// runDump builds the full code graph for a project and prints it as JSON to
|
|
16
|
+
// stdout, then exits. Unlike serve it does not stay resident: it is the one-shot
|
|
17
|
+
// `ttscgraph dump` a user pipes into a file to feed the 3D viewer or any other
|
|
18
|
+
// tooling. Every node and edge is included, with none of the MCP response caps.
|
|
19
|
+
// Returns 0 on success, 1 on a load or serialize error, 2 on invalid invocation.
|
|
20
|
+
func runDump(args []string) int {
|
|
21
|
+
fs := flag.NewFlagSet("ttscgraph dump", flag.ContinueOnError)
|
|
22
|
+
fs.SetOutput(stderr)
|
|
23
|
+
cwdFlag := fs.String("cwd", "", "project root (defaults to process cwd)")
|
|
24
|
+
tsconfigFlag := fs.String("tsconfig", "tsconfig.json", "project tsconfig path")
|
|
25
|
+
prettyFlag := fs.Bool("pretty", false, "indent the JSON output")
|
|
26
|
+
if err := fs.Parse(args); err != nil {
|
|
27
|
+
return 2
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
cwd := strings.TrimSpace(*cwdFlag)
|
|
31
|
+
if cwd == "" {
|
|
32
|
+
resolved, err := getwd()
|
|
33
|
+
if err != nil {
|
|
34
|
+
fmt.Fprintf(stderr, "ttscgraph: could not resolve working directory: %v\n", err)
|
|
35
|
+
return 2
|
|
36
|
+
}
|
|
37
|
+
cwd = resolved
|
|
38
|
+
}
|
|
39
|
+
// Resolve the project root the same way LoadProgram does (absolute, then
|
|
40
|
+
// tsgo-normalized) so it shares the node file paths' drive-letter case on
|
|
41
|
+
// Windows; otherwise the dump's prefix-based relativization would miss and
|
|
42
|
+
// leave every path absolute.
|
|
43
|
+
if abs, err := filepath.Abs(cwd); err == nil {
|
|
44
|
+
cwd = abs
|
|
45
|
+
}
|
|
46
|
+
cwd = shimtspath.ResolvePath(cwd)
|
|
47
|
+
tsconfig := strings.TrimSpace(*tsconfigFlag)
|
|
48
|
+
|
|
49
|
+
prog, _, err := driver.LoadProgram(cwd, tsconfig, driver.LoadProgramOptions{})
|
|
50
|
+
if err != nil {
|
|
51
|
+
fmt.Fprintf(stderr, "ttscgraph: could not load %s/%s: %v\n", cwd, tsconfig, err)
|
|
52
|
+
return 1
|
|
53
|
+
}
|
|
54
|
+
if prog == nil {
|
|
55
|
+
fmt.Fprintf(stderr, "ttscgraph: could not load %s/%s\n", cwd, tsconfig)
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
defer func() { _ = prog.Close() }()
|
|
59
|
+
|
|
60
|
+
g := graph.Build(prog)
|
|
61
|
+
ignored := graph.GitIgnoredFiles(cwd, g)
|
|
62
|
+
data, err := graph.MarshalDump(g, cwd, tsconfig, ignored, graph.SourceTexts(prog), *prettyFlag)
|
|
63
|
+
if err != nil {
|
|
64
|
+
fmt.Fprintf(stderr, "ttscgraph: %v\n", err)
|
|
65
|
+
return 1
|
|
66
|
+
}
|
|
67
|
+
fmt.Fprintln(stdout, string(data))
|
|
68
|
+
return 0
|
|
69
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Command ttscgraph builds the checker-resolved code graph for a project and
|
|
2
|
+
// prints it as JSON (`ttscgraph dump`). It is the native data provider for
|
|
3
|
+
// @ttsc/graph: the JavaScript launcher runs `ttscgraph dump` once, then serves
|
|
4
|
+
// the Model Context Protocol from the in-memory graph itself. The MCP server,
|
|
5
|
+
// its tools, and their schemas live in @ttsc/graph (TypeScript); nothing here
|
|
6
|
+
// answers tool calls.
|
|
7
|
+
package main
|
|
8
|
+
|
|
9
|
+
import (
|
|
10
|
+
"fmt"
|
|
11
|
+
"io"
|
|
12
|
+
"os"
|
|
13
|
+
"runtime"
|
|
14
|
+
"strings"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// Build metadata; overwritten via -ldflags in release builds.
|
|
18
|
+
var (
|
|
19
|
+
version = "0.0.0-dev"
|
|
20
|
+
commit = "dev"
|
|
21
|
+
date = "unknown"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
// Package-level streams so command tests can capture I/O without patching the
|
|
25
|
+
// os globals.
|
|
26
|
+
var (
|
|
27
|
+
stdout io.Writer = os.Stdout
|
|
28
|
+
stderr io.Writer = os.Stderr
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// getwd is the seam command tests use to simulate a working-directory failure.
|
|
32
|
+
var getwd = os.Getwd
|
|
33
|
+
|
|
34
|
+
func main() {
|
|
35
|
+
os.Exit(run(os.Args[1:]))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// run dispatches the top-level command and returns an exit code. ttscgraph is
|
|
39
|
+
// dump-only; anything that is not help/version/dump prints usage and exits 2,
|
|
40
|
+
// since serving MCP is the launcher's job.
|
|
41
|
+
func run(args []string) int {
|
|
42
|
+
if len(args) > 0 {
|
|
43
|
+
switch args[0] {
|
|
44
|
+
case "-h", "--help", "help":
|
|
45
|
+
printHelp(stdout)
|
|
46
|
+
return 0
|
|
47
|
+
case "-v", "--version", "version":
|
|
48
|
+
printVersion(stdout)
|
|
49
|
+
return 0
|
|
50
|
+
case "dump":
|
|
51
|
+
return runDump(args[1:])
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
printHelp(stderr)
|
|
55
|
+
return 2
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func printVersion(w io.Writer) {
|
|
59
|
+
fmt.Fprintf(
|
|
60
|
+
w,
|
|
61
|
+
"ttscgraph %s (commit %s, built %s, %s/%s, go %s)\n",
|
|
62
|
+
version,
|
|
63
|
+
commit,
|
|
64
|
+
date,
|
|
65
|
+
runtime.GOOS,
|
|
66
|
+
runtime.GOARCH,
|
|
67
|
+
runtime.Version(),
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
func printHelp(w io.Writer) {
|
|
72
|
+
fmt.Fprintln(w, strings.TrimSpace(`
|
|
73
|
+
ttscgraph — checker-resolved code graph for ttsc.
|
|
74
|
+
|
|
75
|
+
Builds the project's code graph and prints it as JSON. The @ttsc/graph launcher
|
|
76
|
+
runs this and serves the Model Context Protocol from the result; the MCP tools
|
|
77
|
+
live in @ttsc/graph, not here.
|
|
78
|
+
|
|
79
|
+
Usage:
|
|
80
|
+
ttscgraph dump [--cwd <dir>] [--tsconfig <path>] [--pretty] > graph.json
|
|
81
|
+
ttscgraph --version
|
|
82
|
+
ttscgraph --help
|
|
83
|
+
|
|
84
|
+
Dump:
|
|
85
|
+
dump Build the whole graph and print it as JSON to stdout, then
|
|
86
|
+
exit. Every node and edge, none of the MCP response caps.
|
|
87
|
+
--cwd <dir> Project root (defaults to the process working directory).
|
|
88
|
+
--tsconfig <path> Project tsconfig path (default: tsconfig.json).
|
|
89
|
+
--pretty Indent the JSON output.
|
|
90
|
+
`))
|
|
91
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// writeGraphFile writes content to path under a fixture project, creating parent
|
|
12
|
+
// directories as needed.
|
|
13
|
+
func writeGraphFile(t *testing.T, path, content string) {
|
|
14
|
+
t.Helper()
|
|
15
|
+
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
16
|
+
t.Fatal(err)
|
|
17
|
+
}
|
|
18
|
+
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
19
|
+
t.Fatal(err)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// TestRunDumpPrintsTheGraphAsJSON verifies the `dump` subcommand end to end: run
|
|
24
|
+
// dispatches it, it loads the project, builds the graph, and prints the export
|
|
25
|
+
// JSON to stdout with exit 0. This is the shipped path a user runs as
|
|
26
|
+
// `npx @ttsc/graph dump > graph.json` to feed the 3D viewer, so a regression that
|
|
27
|
+
// stops it printing, or makes it serve instead of exit, would break that flow
|
|
28
|
+
// silently.
|
|
29
|
+
//
|
|
30
|
+
// 1. Write a two-function fixture (one call) under a temp project.
|
|
31
|
+
// 2. run dump --cwd <root> --tsconfig tsconfig.json, capturing stdout.
|
|
32
|
+
// 3. Assert exit 0 and that stdout is the graph envelope with nodes and edges.
|
|
33
|
+
func TestRunDumpPrintsTheGraphAsJSON(t *testing.T) {
|
|
34
|
+
root := t.TempDir()
|
|
35
|
+
writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
|
|
36
|
+
"compilerOptions": {
|
|
37
|
+
"target": "ES2022",
|
|
38
|
+
"module": "commonjs",
|
|
39
|
+
"strict": true,
|
|
40
|
+
"rootDir": "src",
|
|
41
|
+
"outDir": "dist"
|
|
42
|
+
},
|
|
43
|
+
"files": ["src/main.ts"]
|
|
44
|
+
}
|
|
45
|
+
`)
|
|
46
|
+
writeGraphFile(t, filepath.Join(root, "src", "main.ts"), `export function helper(): void {}
|
|
47
|
+
export function main(): void {
|
|
48
|
+
helper();
|
|
49
|
+
}
|
|
50
|
+
`)
|
|
51
|
+
|
|
52
|
+
oldStdout, oldStderr := stdout, stderr
|
|
53
|
+
defer func() { stdout, stderr = oldStdout, oldStderr }()
|
|
54
|
+
var out, errBuf bytes.Buffer
|
|
55
|
+
stdout, stderr = &out, &errBuf
|
|
56
|
+
|
|
57
|
+
if code := run([]string{"dump", "--cwd", root, "--tsconfig", "tsconfig.json"}); code != 0 {
|
|
58
|
+
t.Fatalf("run dump exit = %d, want 0; stderr:\n%s", code, errBuf.String())
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
var dump struct {
|
|
62
|
+
Nodes []map[string]any `json:"nodes"`
|
|
63
|
+
Edges []map[string]any `json:"edges"`
|
|
64
|
+
}
|
|
65
|
+
if err := json.Unmarshal(out.Bytes(), &dump); err != nil {
|
|
66
|
+
t.Fatalf("dump output is not valid JSON: %v\n%s", err, out.String())
|
|
67
|
+
}
|
|
68
|
+
if len(dump.Nodes) == 0 || len(dump.Edges) == 0 {
|
|
69
|
+
t.Fatalf("expected nodes and edges, got %d/%d", len(dump.Nodes), len(dump.Edges))
|
|
70
|
+
}
|
|
71
|
+
// Each edge carries its endpoints and kind, and nothing more — the graph is
|
|
72
|
+
// wholly checker-resolved, so there is no per-edge trust flag to negotiate.
|
|
73
|
+
edge := dump.Edges[0]
|
|
74
|
+
if _, ok := edge["from"].(string); !ok {
|
|
75
|
+
t.Fatalf("edge missing from: %v", edge)
|
|
76
|
+
}
|
|
77
|
+
if _, ok := edge["kind"].(string); !ok {
|
|
78
|
+
t.Fatalf("edge missing kind: %v", edge)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"errors"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestRunHelpVersionAndBadFlagsExitCodes verifies the top-level run dispatcher
|
|
11
|
+
// returns the documented exit codes for the dump-only command surface: help and
|
|
12
|
+
// version short-circuit to 0, an unknown command prints usage and exits 2, and a
|
|
13
|
+
// dump that cannot resolve its working directory exits 2 with an explanation.
|
|
14
|
+
//
|
|
15
|
+
// These are the non-load paths a user hits with a typo or `--help`; each must
|
|
16
|
+
// resolve to a code without building a Program. The getwd seam stands in for an
|
|
17
|
+
// unreadable working directory so the resolve-failure branch is reachable
|
|
18
|
+
// without a real filesystem fault.
|
|
19
|
+
//
|
|
20
|
+
// 1. run --help and run --version exit 0, printing the command name / version.
|
|
21
|
+
// 2. run --bogus (unknown command) exits 2 and prints usage.
|
|
22
|
+
// 3. run dump --nope exits 2 (dump flag parse failure).
|
|
23
|
+
// 4. With getwd forced to fail, run dump exits 2 and explains why.
|
|
24
|
+
func TestRunHelpVersionAndBadFlagsExitCodes(t *testing.T) {
|
|
25
|
+
oldStdout, oldStderr, oldGetwd := stdout, stderr, getwd
|
|
26
|
+
defer func() { stdout, stderr, getwd = oldStdout, oldStderr, oldGetwd }()
|
|
27
|
+
|
|
28
|
+
// --help exits 0 and names the command on stdout.
|
|
29
|
+
var helpOut bytes.Buffer
|
|
30
|
+
stdout = &helpOut
|
|
31
|
+
if code := run([]string{"--help"}); code != 0 {
|
|
32
|
+
t.Fatalf("run --help exit = %d, want 0", code)
|
|
33
|
+
}
|
|
34
|
+
if !strings.Contains(helpOut.String(), "ttscgraph") {
|
|
35
|
+
t.Fatalf("run --help did not print the command name:\n%s", helpOut.String())
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --version exits 0 and prints a version-ish string.
|
|
39
|
+
var versionOut bytes.Buffer
|
|
40
|
+
stdout = &versionOut
|
|
41
|
+
if code := run([]string{"--version"}); code != 0 {
|
|
42
|
+
t.Fatalf("run --version exit = %d, want 0", code)
|
|
43
|
+
}
|
|
44
|
+
if got := versionOut.String(); !strings.Contains(got, "ttscgraph") || !strings.Contains(got, version) {
|
|
45
|
+
t.Fatalf("run --version did not print a version string:\n%s", got)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// An unknown command is an invalid invocation: print usage and exit 2.
|
|
49
|
+
var badErr bytes.Buffer
|
|
50
|
+
stderr = &badErr
|
|
51
|
+
if code := run([]string{"--bogus"}); code != 2 {
|
|
52
|
+
t.Fatalf("run --bogus exit = %d, want 2", code)
|
|
53
|
+
}
|
|
54
|
+
if !strings.Contains(badErr.String(), "Usage:") {
|
|
55
|
+
t.Fatalf("run --bogus exited 2 but did not print usage:\n%s", badErr.String())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// An unknown dump flag is an invalid invocation: dump's flag.Parse fails -> 2.
|
|
59
|
+
var dumpFlagErr bytes.Buffer
|
|
60
|
+
stderr = &dumpFlagErr
|
|
61
|
+
if code := run([]string{"dump", "--nope"}); code != 2 {
|
|
62
|
+
t.Fatalf("run dump --nope exit = %d, want 2", code)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// A getwd failure (no --cwd given) is an invalid invocation -> 2, explained.
|
|
66
|
+
var wdErr bytes.Buffer
|
|
67
|
+
stderr = &wdErr
|
|
68
|
+
getwd = func() (string, error) { return "", errors.New("boom") }
|
|
69
|
+
if code := run([]string{"dump"}); code != 2 {
|
|
70
|
+
t.Fatalf("run dump with getwd failure exit = %d, want 2", code)
|
|
71
|
+
}
|
|
72
|
+
if !strings.Contains(wdErr.String(), "could not resolve working directory") {
|
|
73
|
+
t.Fatalf("getwd failure did not explain itself:\n%s", wdErr.String())
|
|
74
|
+
}
|
|
75
|
+
}
|