ttsc 0.3.1 → 0.4.0-dev.20260425
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/LICENSE +1 -1
- package/README.md +93 -362
- package/cmd/ttsc/build.go +128 -0
- package/cmd/ttsc/main.go +222 -0
- package/cmd/ttsc/main_test.go +288 -0
- package/cmd/ttsc/transform.go +166 -0
- package/driver/host.go +31 -0
- package/driver/program.go +298 -0
- package/driver/rewrite.go +315 -0
- package/go.mod +48 -0
- package/go.sum +22 -0
- package/lib/api.d.ts +91 -0
- package/lib/api.js +325 -0
- package/lib/api.js.map +1 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +481 -0
- package/lib/cli.js.map +1 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +35 -0
- package/lib/index.js.map +1 -0
- package/lib/native.d.ts +15 -0
- package/lib/native.js +34 -0
- package/lib/native.js.map +1 -0
- package/lib/platform.d.ts +66 -0
- package/lib/platform.js +170 -0
- package/lib/platform.js.map +1 -0
- package/lib/plugin.d.ts +47 -0
- package/lib/plugin.js +150 -0
- package/lib/plugin.js.map +1 -0
- package/lib/project.d.ts +32 -0
- package/lib/project.js +303 -0
- package/lib/project.js.map +1 -0
- package/lib/runner/cli.d.ts +1 -0
- package/lib/runner/cli.js +284 -0
- package/lib/runner/cli.js.map +1 -0
- package/lib/runner/register.d.ts +13 -0
- package/lib/runner/register.js +424 -0
- package/lib/runner/register.js.map +1 -0
- package/package.json +72 -63
- package/shim/ast/extra-shim.json +4 -0
- package/shim/ast/go.mod +5 -0
- package/shim/ast/shim.go +117 -0
- package/shim/bundled/extra-shim.json +1 -0
- package/shim/bundled/go.mod +5 -0
- package/shim/bundled/shim.go +19 -0
- package/shim/checker/extra-shim.json +31 -0
- package/shim/checker/go.mod +5 -0
- package/shim/checker/shim.go +146 -0
- package/shim/compiler/extra-shim.json +4 -0
- package/shim/compiler/go.mod +5 -0
- package/shim/compiler/shim.go +50 -0
- package/shim/core/extra-shim.json +4 -0
- package/shim/core/go.mod +5 -0
- package/shim/core/shim.go +11 -0
- package/shim/diagnosticwriter/go.mod +5 -0
- package/shim/diagnosticwriter/shim.go +29 -0
- package/shim/parser/extra-shim.json +4 -0
- package/shim/parser/go.mod +5 -0
- package/shim/parser/shim.go +55 -0
- package/shim/scanner/extra-shim.json +4 -0
- package/shim/scanner/go.mod +5 -0
- package/shim/scanner/shim.go +96 -0
- package/shim/tsoptions/extra-shim.json +4 -0
- package/shim/tsoptions/go.mod +5 -0
- package/shim/tsoptions/shim.go +123 -0
- package/shim/tspath/extra-shim.json +4 -0
- package/shim/tspath/go.mod +5 -0
- package/shim/tspath/shim.go +165 -0
- package/shim/vfs/cachedvfs/extra-shim.json +4 -0
- package/shim/vfs/cachedvfs/go.mod +5 -0
- package/shim/vfs/cachedvfs/shim.go +12 -0
- package/shim/vfs/extra-shim.json +4 -0
- package/shim/vfs/go.mod +5 -0
- package/shim/vfs/osvfs/extra-shim.json +4 -0
- package/shim/vfs/osvfs/go.mod +5 -0
- package/shim/vfs/osvfs/shim.go +13 -0
- package/shim/vfs/shim.go +19 -0
- package/src/api.ts +416 -0
- package/src/cli.ts +492 -0
- package/src/index.ts +19 -0
- package/src/launcher/ttsc-dev.js +42 -0
- package/src/launcher/ttsc.js +19 -0
- package/src/launcher/ttsx.js +19 -0
- package/src/native.ts +60 -0
- package/src/platform.ts +163 -0
- package/src/plugin.ts +241 -0
- package/src/project.ts +330 -0
- package/src/runner/cli.ts +286 -0
- package/src/runner/register.ts +487 -0
- package/test/native.test.ts +60 -0
- package/test/package.test.ts +131 -0
- package/test/platform.test.ts +58 -0
- package/test/project.test.ts +195 -0
- package/tools/gen_shims/go.mod +14 -0
- package/tools/gen_shims/go.sum +12 -0
- package/tools/gen_shims/main.go +446 -0
- package/CHANGELOG.md +0 -2
- package/bin/tsc +0 -2
- package/bin/tsserver +0 -2
- package/lib/PluginCreator.d.ts +0 -67
- package/lib/PluginCreator.js +0 -190
- package/lib/cancelationToken.d.ts +0 -0
- package/lib/cancelationToken.js +0 -2
- package/lib/loadTypescript.d.ts +0 -7
- package/lib/loadTypescript.js +0 -56
- package/lib/patchCreateProgram.d.ts +0 -21
- package/lib/patchCreateProgram.js +0 -190
- package/lib/tsc.d.ts +0 -1
- package/lib/tsc.js +0 -24
- package/lib/tsserver.d.ts +0 -0
- package/lib/tsserver.js +0 -2
- package/lib/tsserverlibrary.d.ts +0 -3
- package/lib/tsserverlibrary.js +0 -4
- package/lib/typescript.d.ts +0 -3
- package/lib/typescript.js +0 -4
- package/lib/typescriptServices.d.ts +0 -3
- package/lib/typescriptServices.js +0 -4
- package/lib/typingsInstaller.d.ts +0 -0
- package/lib/typingsInstaller.js +0 -2
- package/lib/watchGuard.d.ts +0 -0
- package/lib/watchGuard.js +0 -2
package/cmd/ttsc/main.go
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// Command ttsc is the Go CLI entrypoint for ttsc.
|
|
2
|
+
//
|
|
3
|
+
// Current responsibilities:
|
|
4
|
+
// - Report version and platform (`ttsc --version`).
|
|
5
|
+
// - Exercise a native rewrite backend end-to-end (`ttsc demo --type=string`)
|
|
6
|
+
// so smoke tests can verify the Go binary really runs.
|
|
7
|
+
// - Act as a placeholder for the eventual `build` / `dev` / `check` / `setup`
|
|
8
|
+
// subcommands that will land in Week 3+.
|
|
9
|
+
//
|
|
10
|
+
// Everything below is deliberately dependency-free so that the standalone binary
|
|
11
|
+
// stays small and its behavior is trivial to audit.
|
|
12
|
+
package main
|
|
13
|
+
|
|
14
|
+
import (
|
|
15
|
+
"flag"
|
|
16
|
+
"fmt"
|
|
17
|
+
"io"
|
|
18
|
+
"os"
|
|
19
|
+
"runtime"
|
|
20
|
+
"strings"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// These are overridden via `-ldflags "-X main.version=... -X main.commit=..."`
|
|
24
|
+
// in CI. Sensible defaults keep local `go run ./cmd/ttsc` usable.
|
|
25
|
+
var (
|
|
26
|
+
version = "0.0.0-dev"
|
|
27
|
+
commit = "dev"
|
|
28
|
+
date = "unknown"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// stdout / stderr are package-level to keep the CLI testable.
|
|
32
|
+
var (
|
|
33
|
+
stdout io.Writer = os.Stdout
|
|
34
|
+
stderr io.Writer = os.Stderr
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
func main() {
|
|
38
|
+
os.Exit(run(os.Args[1:]))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func run(args []string) int {
|
|
42
|
+
if len(args) == 0 {
|
|
43
|
+
return runBuild(nil)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
switch args[0] {
|
|
47
|
+
case "-h", "--help", "help":
|
|
48
|
+
printHelp(stdout)
|
|
49
|
+
return 0
|
|
50
|
+
case "-v", "--version", "version":
|
|
51
|
+
printVersion(stdout)
|
|
52
|
+
return 0
|
|
53
|
+
case "demo":
|
|
54
|
+
return runDemo(args[1:])
|
|
55
|
+
case "build":
|
|
56
|
+
return runBuild(args[1:])
|
|
57
|
+
case "check":
|
|
58
|
+
// `ttsc check` runs the analyze pipeline without emitting JS — useful
|
|
59
|
+
// in CI and pre-commit hooks that only need schema validation.
|
|
60
|
+
return runBuild(append([]string{"--noEmit"}, args[1:]...))
|
|
61
|
+
case "transform":
|
|
62
|
+
// Single-file transform for bundler plugin integration (unplugin,
|
|
63
|
+
// vite, esbuild, webpack, …). Emits only the requested file's JS to
|
|
64
|
+
// stdout or a chosen path — no project-wide write.
|
|
65
|
+
return runTransform(args[1:])
|
|
66
|
+
case "-p", "--project":
|
|
67
|
+
if len(args) < 2 {
|
|
68
|
+
fmt.Fprintln(stderr, "ttsc: -p/--project requires a path argument")
|
|
69
|
+
return 2
|
|
70
|
+
}
|
|
71
|
+
return runBuild(append([]string{"--tsconfig=" + args[1]}, args[2:]...))
|
|
72
|
+
default:
|
|
73
|
+
if isBuildAlias(args[0]) {
|
|
74
|
+
return runBuild(args)
|
|
75
|
+
}
|
|
76
|
+
fmt.Fprintf(stderr, "ttsc: unknown command %q\n", args[0])
|
|
77
|
+
fmt.Fprintln(stderr, `ttsc: run "ttsc --help" to see supported commands`)
|
|
78
|
+
return 2
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func isBuildAlias(arg string) bool {
|
|
83
|
+
if strings.HasPrefix(arg, "-") {
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
switch {
|
|
87
|
+
case strings.HasSuffix(arg, ".json"),
|
|
88
|
+
strings.HasSuffix(arg, ".ts"),
|
|
89
|
+
strings.HasSuffix(arg, ".tsx"),
|
|
90
|
+
strings.HasSuffix(arg, ".mts"),
|
|
91
|
+
strings.HasSuffix(arg, ".cts"):
|
|
92
|
+
return true
|
|
93
|
+
default:
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// appendUnique appends `extra` to `args` only when it's not already present.
|
|
99
|
+
func appendUnique(args []string, extra ...string) []string {
|
|
100
|
+
out := append([]string{}, args...)
|
|
101
|
+
for _, e := range extra {
|
|
102
|
+
seen := false
|
|
103
|
+
for _, a := range out {
|
|
104
|
+
if a == e {
|
|
105
|
+
seen = true
|
|
106
|
+
break
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if !seen {
|
|
110
|
+
out = append(out, e)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return out
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func printVersion(w io.Writer) {
|
|
117
|
+
fmt.Fprintf(
|
|
118
|
+
w,
|
|
119
|
+
"ttsc %s (commit %s, built %s, %s/%s, go %s)\n",
|
|
120
|
+
version,
|
|
121
|
+
commit,
|
|
122
|
+
date,
|
|
123
|
+
runtime.GOOS,
|
|
124
|
+
runtime.GOARCH,
|
|
125
|
+
runtime.Version(),
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
func printHelp(w io.Writer) {
|
|
130
|
+
fmt.Fprintln(w, strings.TrimSpace(`
|
|
131
|
+
ttsc — standalone typescript-go host.
|
|
132
|
+
|
|
133
|
+
Usage:
|
|
134
|
+
ttsc
|
|
135
|
+
ttsc -p tsconfig.json
|
|
136
|
+
ttsc --watch
|
|
137
|
+
ttsc --noEmit
|
|
138
|
+
ttsc transform --file=src/main.ts
|
|
139
|
+
|
|
140
|
+
Project build:
|
|
141
|
+
ttsc compiles the current tsconfig.json, matching the tsc/tsgo shape.
|
|
142
|
+
build Compatibility alias for the same project build lane.
|
|
143
|
+
check Compatibility alias for --noEmit validation.
|
|
144
|
+
transform Emit a single file's rewritten JS (bundler plugin hook).
|
|
145
|
+
demo Run a native backend smoke pipeline with synthetic input.
|
|
146
|
+
version Print version, build info, and platform.
|
|
147
|
+
help Show this help.
|
|
148
|
+
|
|
149
|
+
Build options:
|
|
150
|
+
--tsconfig=FILE Path to tsconfig.json (default: tsconfig.json).
|
|
151
|
+
--cwd=DIR Override working directory.
|
|
152
|
+
--emit Force emitted .js files even when tsconfig has noEmit.
|
|
153
|
+
--noEmit Force analysis-only run even when tsconfig would emit.
|
|
154
|
+
--quiet Suppress the per-call summary banner (default).
|
|
155
|
+
--verbose Print the per-call summary banner and emitted file list.
|
|
156
|
+
--rewrite-mode=M Native rewrite backend id.
|
|
157
|
+
--manifest=FILE Write emitted file paths as JSON to FILE after build --emit.
|
|
158
|
+
|
|
159
|
+
Transform options:
|
|
160
|
+
--file=PATH Absolute or cwd-relative path of the .ts file to transform.
|
|
161
|
+
--tsconfig=FILE tsconfig.json owning --file (default: tsconfig.json).
|
|
162
|
+
--out=PATH Write output JS to PATH. Default: stdout.
|
|
163
|
+
--rewrite-mode=M Native rewrite backend id.
|
|
164
|
+
|
|
165
|
+
Demo options:
|
|
166
|
+
--type=T Atomic TypeScript type to simulate. One of:
|
|
167
|
+
string, number, boolean, bigint, any (default: string).
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
ttsc --version
|
|
171
|
+
ttsc
|
|
172
|
+
ttsc -p ./tsconfig.json
|
|
173
|
+
ttsc --noEmit
|
|
174
|
+
ttsc transform --file=src/main.ts
|
|
175
|
+
ttsc demo --type=number
|
|
176
|
+
|
|
177
|
+
Integration guide (bundlers):
|
|
178
|
+
- vite / webpack / rollup / esbuild: shell out to "ttsc transform" per file,
|
|
179
|
+
forward stdout as the bundler transform result. A bundler adapter can wrap
|
|
180
|
+
this command without depending on the Go internals.
|
|
181
|
+
- Next.js / Nuxt / Bun: "ttsc" in your pipeline replaces tsc and
|
|
182
|
+
the rewritten .js feeds the runtime directly.
|
|
183
|
+
- Monorepo / pnpm workspace: share one ttsc binary via a root script;
|
|
184
|
+
per-package tsconfig.json references work unchanged.
|
|
185
|
+
`))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func runDemo(args []string) int {
|
|
189
|
+
fs := flag.NewFlagSet("demo", flag.ContinueOnError)
|
|
190
|
+
fs.SetOutput(stderr)
|
|
191
|
+
typ := fs.String("type", "string", "atomic type to simulate")
|
|
192
|
+
if err := fs.Parse(args); err != nil {
|
|
193
|
+
return 2
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
arrow, err := demoArrow(*typ)
|
|
197
|
+
if err != nil {
|
|
198
|
+
fmt.Fprintf(stderr, "ttsc demo: %v\n", err)
|
|
199
|
+
return 2
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fmt.Fprintf(stdout, "// demo<%s> -> emitted by ttsc %s\n", *typ, version)
|
|
203
|
+
fmt.Fprintln(stdout, arrow)
|
|
204
|
+
return 0
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
func demoArrow(name string) (string, error) {
|
|
208
|
+
switch strings.ToLower(name) {
|
|
209
|
+
case "any":
|
|
210
|
+
return "(input) => true", nil
|
|
211
|
+
case "boolean":
|
|
212
|
+
return `(input) => "boolean" === typeof input`, nil
|
|
213
|
+
case "number":
|
|
214
|
+
return `(input) => "number" === typeof input`, nil
|
|
215
|
+
case "bigint":
|
|
216
|
+
return `(input) => "bigint" === typeof input`, nil
|
|
217
|
+
case "string", "":
|
|
218
|
+
return `(input) => "string" === typeof input`, nil
|
|
219
|
+
default:
|
|
220
|
+
return "", fmt.Errorf("unknown --type value %q (want: string|number|boolean|bigint|any)", name)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// withCapture replaces stdout/stderr with buffers for the duration of fn.
|
|
12
|
+
// Keeps each test hermetic.
|
|
13
|
+
func withCapture(fn func()) (out, errOut string) {
|
|
14
|
+
var outBuf, errBuf bytes.Buffer
|
|
15
|
+
prevOut, prevErr := stdout, stderr
|
|
16
|
+
stdout = &outBuf
|
|
17
|
+
stderr = &errBuf
|
|
18
|
+
defer func() {
|
|
19
|
+
stdout = prevOut
|
|
20
|
+
stderr = prevErr
|
|
21
|
+
}()
|
|
22
|
+
fn()
|
|
23
|
+
return outBuf.String(), errBuf.String()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func TestRunNoArgsAttemptsProjectBuild(t *testing.T) {
|
|
27
|
+
out, errOut := withCapture(func() {
|
|
28
|
+
if code := run(nil); code == 0 {
|
|
29
|
+
t.Errorf("expected no-args run without a local tsconfig to fail")
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
if strings.Contains(out, "Usage:") {
|
|
33
|
+
t.Errorf("no-args ttsc must build the project, not print help; got %q", out)
|
|
34
|
+
}
|
|
35
|
+
if !strings.Contains(errOut, "tsconfig") {
|
|
36
|
+
t.Errorf("expected no-args build to look for tsconfig, got stderr %q", errOut)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func TestRunHelpVariants(t *testing.T) {
|
|
41
|
+
for _, flag := range []string{"-h", "--help", "help"} {
|
|
42
|
+
t.Run(flag, func(t *testing.T) {
|
|
43
|
+
out, _ := withCapture(func() {
|
|
44
|
+
if code := run([]string{flag}); code != 0 {
|
|
45
|
+
t.Errorf("expected exit 0, got %d", code)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
if !strings.Contains(out, "demo") {
|
|
49
|
+
t.Errorf("help text should list demo subcommand, got %q", out)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func TestRunVersion(t *testing.T) {
|
|
56
|
+
for _, flag := range []string{"-v", "--version", "version"} {
|
|
57
|
+
t.Run(flag, func(t *testing.T) {
|
|
58
|
+
out, _ := withCapture(func() {
|
|
59
|
+
if code := run([]string{flag}); code != 0 {
|
|
60
|
+
t.Errorf("expected exit 0, got %d", code)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
if !strings.Contains(out, "ttsc ") {
|
|
64
|
+
t.Errorf("expected version banner, got %q", out)
|
|
65
|
+
}
|
|
66
|
+
// Include useful debug bits
|
|
67
|
+
for _, tag := range []string{"commit", "go"} {
|
|
68
|
+
if !strings.Contains(out, tag) {
|
|
69
|
+
t.Errorf("expected %q substring in version output, got %q", tag, out)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func TestRunUnknownCommandExits2(t *testing.T) {
|
|
77
|
+
_, errOut := withCapture(func() {
|
|
78
|
+
if code := run([]string{"fly-to-mars"}); code != 2 {
|
|
79
|
+
t.Errorf("expected exit 2, got %d", code)
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
if !strings.Contains(errOut, "unknown command") {
|
|
83
|
+
t.Errorf("expected error text, got %q", errOut)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func TestRunBuildBlocksSemanticDiagnosticsBeforeEmit(t *testing.T) {
|
|
88
|
+
root := t.TempDir()
|
|
89
|
+
writeProjectFile(t, root, "tsconfig.json", `{
|
|
90
|
+
"compilerOptions": {
|
|
91
|
+
"module": "commonjs",
|
|
92
|
+
"target": "es2020",
|
|
93
|
+
"outDir": "bin",
|
|
94
|
+
"strict": true
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
`)
|
|
98
|
+
writeProjectFile(t, root, "index.ts", `const value: string = 123;
|
|
99
|
+
console.log(value);
|
|
100
|
+
`)
|
|
101
|
+
|
|
102
|
+
_, errOut := withCapture(func() {
|
|
103
|
+
if code := runBuild([]string{"--cwd", root, "--emit"}); code != 2 {
|
|
104
|
+
t.Errorf("expected exit 2, got %d", code)
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
if !strings.Contains(errOut, "Type 'number' is not assignable to type 'string'") {
|
|
108
|
+
t.Errorf("expected semantic diagnostic, got %q", errOut)
|
|
109
|
+
}
|
|
110
|
+
if !strings.Contains(errOut, "\x1b[91merror\x1b[0m") || !strings.Contains(errOut, "TS2322") {
|
|
111
|
+
t.Errorf("expected colored TypeScript-style diagnostic header, got %q", errOut)
|
|
112
|
+
}
|
|
113
|
+
if !strings.Contains(errOut, "\x1b[7m1\x1b[0m") || !strings.Contains(errOut, "~~~~~") {
|
|
114
|
+
t.Errorf("expected source context with highlighted span, got %q", errOut)
|
|
115
|
+
}
|
|
116
|
+
if _, err := os.Stat(filepath.Join(root, "bin", "index.js")); !os.IsNotExist(err) {
|
|
117
|
+
t.Errorf("expected no emitted JS, stat err=%v", err)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
func TestRunBuildAllowsUnusedTypeParametersOnOverloadSignatures(t *testing.T) {
|
|
122
|
+
root := t.TempDir()
|
|
123
|
+
writeProjectFile(t, root, "tsconfig.json", `{
|
|
124
|
+
"compilerOptions": {
|
|
125
|
+
"module": "commonjs",
|
|
126
|
+
"target": "es2020",
|
|
127
|
+
"outDir": "bin",
|
|
128
|
+
"strict": true,
|
|
129
|
+
"noUnusedLocals": true,
|
|
130
|
+
"noUnusedParameters": true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
`)
|
|
134
|
+
writeProjectFile(t, root, "index.ts", `export function marker<T>(input: unknown): string;
|
|
135
|
+
export function marker(input: unknown): string {
|
|
136
|
+
return String(input);
|
|
137
|
+
}
|
|
138
|
+
`)
|
|
139
|
+
|
|
140
|
+
_, errOut := withCapture(func() {
|
|
141
|
+
if code := runBuild([]string{"--cwd", root, "--emit", "--quiet"}); code != 0 {
|
|
142
|
+
t.Errorf("expected exit 0, got %d", code)
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
if strings.Contains(errOut, "declared but never used") || strings.Contains(errOut, "All type parameters are unused") {
|
|
146
|
+
t.Errorf("overload signature type parameters must not fail noUnused checks, got %q", errOut)
|
|
147
|
+
}
|
|
148
|
+
if _, err := os.Stat(filepath.Join(root, "bin", "index.js")); err != nil {
|
|
149
|
+
t.Errorf("expected emitted JS, stat err=%v", err)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
func TestRunBuildBlocksUnusedParametersBeforeEmit(t *testing.T) {
|
|
154
|
+
root := t.TempDir()
|
|
155
|
+
writeProjectFile(t, root, "tsconfig.json", `{
|
|
156
|
+
"compilerOptions": {
|
|
157
|
+
"module": "commonjs",
|
|
158
|
+
"target": "es2020",
|
|
159
|
+
"outDir": "bin",
|
|
160
|
+
"strict": true,
|
|
161
|
+
"noUnusedParameters": true
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
`)
|
|
165
|
+
writeProjectFile(t, root, "index.ts", `export function run(unused: string): number {
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
`)
|
|
169
|
+
|
|
170
|
+
_, errOut := withCapture(func() {
|
|
171
|
+
if code := runBuild([]string{"--cwd", root, "--emit"}); code != 2 {
|
|
172
|
+
t.Errorf("expected exit 2, got %d", code)
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
if !strings.Contains(errOut, "'unused' is declared but its value is never read") &&
|
|
176
|
+
!strings.Contains(errOut, "'unused' is declared but never used") {
|
|
177
|
+
t.Errorf("expected unused parameter diagnostic, got %q", errOut)
|
|
178
|
+
}
|
|
179
|
+
if _, err := os.Stat(filepath.Join(root, "bin", "index.js")); !os.IsNotExist(err) {
|
|
180
|
+
t.Errorf("expected no emitted JS, stat err=%v", err)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
func TestRunBuildBlocksSyntacticDiagnosticsBeforeEmit(t *testing.T) {
|
|
185
|
+
root := t.TempDir()
|
|
186
|
+
writeProjectFile(t, root, "tsconfig.json", `{
|
|
187
|
+
"compilerOptions": {
|
|
188
|
+
"module": "commonjs",
|
|
189
|
+
"target": "es2020",
|
|
190
|
+
"outDir": "bin"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
`)
|
|
194
|
+
writeProjectFile(t, root, "index.ts", `console.log("before");
|
|
195
|
+
const = ;
|
|
196
|
+
`)
|
|
197
|
+
|
|
198
|
+
_, errOut := withCapture(func() {
|
|
199
|
+
if code := runBuild([]string{"--cwd", root, "--emit"}); code != 2 {
|
|
200
|
+
t.Errorf("expected exit 2, got %d", code)
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
if !strings.Contains(errOut, "Variable declaration expected") {
|
|
204
|
+
t.Errorf("expected syntactic diagnostic, got %q", errOut)
|
|
205
|
+
}
|
|
206
|
+
if _, err := os.Stat(filepath.Join(root, "bin", "index.js")); !os.IsNotExist(err) {
|
|
207
|
+
t.Errorf("expected no emitted JS, stat err=%v", err)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func writeProjectFile(t *testing.T, root string, name string, contents string) {
|
|
212
|
+
t.Helper()
|
|
213
|
+
file := filepath.Join(root, name)
|
|
214
|
+
if err := os.MkdirAll(filepath.Dir(file), 0o755); err != nil {
|
|
215
|
+
t.Fatal(err)
|
|
216
|
+
}
|
|
217
|
+
if err := os.WriteFile(file, []byte(contents), 0o644); err != nil {
|
|
218
|
+
t.Fatal(err)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
func TestRunDemoString(t *testing.T) {
|
|
223
|
+
out, _ := withCapture(func() {
|
|
224
|
+
if code := run([]string{"demo", "--type=string"}); code != 0 {
|
|
225
|
+
t.Errorf("expected exit 0, got %d", code)
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
want := `(input) => "string" === typeof input`
|
|
229
|
+
if !strings.Contains(out, want) {
|
|
230
|
+
t.Errorf("expected demo output to contain %q, got:\n%s", want, out)
|
|
231
|
+
}
|
|
232
|
+
if !strings.Contains(out, "demo<string>") {
|
|
233
|
+
t.Errorf("expected comment prefix in demo output, got:\n%s", out)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
func TestRunDemoEveryAtomic(t *testing.T) {
|
|
238
|
+
cases := map[string]string{
|
|
239
|
+
"boolean": `"boolean" === typeof input`,
|
|
240
|
+
"number": `"number" === typeof input`,
|
|
241
|
+
"bigint": `"bigint" === typeof input`,
|
|
242
|
+
"any": `(input) => true`,
|
|
243
|
+
}
|
|
244
|
+
for typ, expected := range cases {
|
|
245
|
+
t.Run(typ, func(t *testing.T) {
|
|
246
|
+
out, _ := withCapture(func() {
|
|
247
|
+
if code := run([]string{"demo", "--type=" + typ}); code != 0 {
|
|
248
|
+
t.Errorf("expected exit 0 for %s, got %d", typ, code)
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
if !strings.Contains(out, expected) {
|
|
252
|
+
t.Errorf("for --type=%s, want %q in output:\n%s", typ, expected, out)
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
func TestRunDemoUnknownTypeExits2(t *testing.T) {
|
|
259
|
+
_, errOut := withCapture(func() {
|
|
260
|
+
if code := run([]string{"demo", "--type=object"}); code != 2 {
|
|
261
|
+
t.Errorf("expected exit 2 for unknown type, got %d", code)
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
if !strings.Contains(errOut, "unknown --type") {
|
|
265
|
+
t.Errorf("expected diagnostic text, got %q", errOut)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
func TestDemoArrowEveryAtomic(t *testing.T) {
|
|
270
|
+
cases := map[string]string{
|
|
271
|
+
"string": `"string" === typeof input`,
|
|
272
|
+
"number": `"number" === typeof input`,
|
|
273
|
+
"boolean": `"boolean" === typeof input`,
|
|
274
|
+
"bigint": `"bigint" === typeof input`,
|
|
275
|
+
"any": `(input) => true`,
|
|
276
|
+
}
|
|
277
|
+
for name, want := range cases {
|
|
278
|
+
t.Run(name, func(t *testing.T) {
|
|
279
|
+
arrow, err := demoArrow(name)
|
|
280
|
+
if err != nil {
|
|
281
|
+
t.Fatalf("unexpected err: %v", err)
|
|
282
|
+
}
|
|
283
|
+
if !strings.Contains(arrow, want) {
|
|
284
|
+
t.Errorf("for %s want %q, got %q", name, want, arrow)
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"flag"
|
|
6
|
+
"fmt"
|
|
7
|
+
"os"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
"strings"
|
|
10
|
+
|
|
11
|
+
shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
|
|
12
|
+
|
|
13
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
// runTransform implements `ttsc transform` — a single-file transform used by
|
|
17
|
+
// bundler plugin adapters (unplugin, vite, webpack, rollup, esbuild, rspack,
|
|
18
|
+
// farm). Behavior:
|
|
19
|
+
//
|
|
20
|
+
// - Loads the enclosing tsconfig so tsgo resolves imports and types.
|
|
21
|
+
// - Runs tsgo Emit, filtering WriteFile so only the requested source
|
|
22
|
+
// file's JS leaves the binary.
|
|
23
|
+
// - Applies the ttsc rewrite + import cleanup just like `build --emit`.
|
|
24
|
+
// - Writes the result to stdout (or `--out` when provided).
|
|
25
|
+
//
|
|
26
|
+
// The contract matches a per-file bundler transform hook: bundlers
|
|
27
|
+
// hand the plugin a source file path, the plugin shells out here, and the
|
|
28
|
+
// returned text replaces the file in the bundle graph.
|
|
29
|
+
func runTransform(args []string) int {
|
|
30
|
+
fs := flag.NewFlagSet("transform", flag.ContinueOnError)
|
|
31
|
+
fs.SetOutput(stderr)
|
|
32
|
+
file := fs.String("file", "", "absolute or cwd-relative path of the .ts file to transform")
|
|
33
|
+
tsconfigPath := fs.String("tsconfig", "tsconfig.json", "tsconfig.json owning --file")
|
|
34
|
+
cwdOverride := fs.String("cwd", "", "override the working directory")
|
|
35
|
+
out := fs.String("out", "", "write output JS to PATH (default: stdout)")
|
|
36
|
+
rewriteMode := fs.String("rewrite-mode", "none", "native rewrite backend id")
|
|
37
|
+
if err := fs.Parse(args); err != nil {
|
|
38
|
+
return 2
|
|
39
|
+
}
|
|
40
|
+
if *file == "" {
|
|
41
|
+
fmt.Fprintln(stderr, "ttsc transform: --file is required")
|
|
42
|
+
return 2
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
cwd := *cwdOverride
|
|
46
|
+
if cwd == "" {
|
|
47
|
+
var err error
|
|
48
|
+
cwd, err = os.Getwd()
|
|
49
|
+
if err != nil {
|
|
50
|
+
fmt.Fprintf(stderr, "ttsc transform: cwd: %v\n", err)
|
|
51
|
+
return 2
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
prog, diags, err := driver.LoadProgram(cwd, *tsconfigPath, driver.LoadProgramOptions{
|
|
56
|
+
ForceEmit: true,
|
|
57
|
+
})
|
|
58
|
+
if err != nil {
|
|
59
|
+
fmt.Fprintf(stderr, "ttsc transform: %v\n", err)
|
|
60
|
+
return 2
|
|
61
|
+
}
|
|
62
|
+
if len(diags) > 0 {
|
|
63
|
+
driver.WritePrettyDiagnostics(stderr, diags, cwd)
|
|
64
|
+
return 2
|
|
65
|
+
}
|
|
66
|
+
defer prog.Close()
|
|
67
|
+
if diags := prog.Diagnostics(); len(diags) > 0 {
|
|
68
|
+
driver.WritePrettyDiagnostics(stderr, diags, cwd)
|
|
69
|
+
return 2
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
absFile := *file
|
|
73
|
+
if !filepath.IsAbs(absFile) {
|
|
74
|
+
absFile = filepath.Join(cwd, absFile)
|
|
75
|
+
}
|
|
76
|
+
absFile = filepath.ToSlash(absFile)
|
|
77
|
+
|
|
78
|
+
rewrites := driver.NewRewriteSet()
|
|
79
|
+
if *rewriteMode != "none" {
|
|
80
|
+
fmt.Fprintf(stderr, "ttsc transform: rewrite backend %q is not built into the standalone ttsc binary\n", *rewriteMode)
|
|
81
|
+
fmt.Fprintln(stderr, "ttsc transform: run through the JS host with the matching compiler plugin so it can select the consumer-provided native binary")
|
|
82
|
+
return 2
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
target := prog.SourceFile(absFile)
|
|
86
|
+
if target == nil {
|
|
87
|
+
fmt.Fprintf(stderr, "ttsc transform: source file is not in program: %s\n", absFile)
|
|
88
|
+
return 2
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Capture the requested source file's JS output.
|
|
92
|
+
var captured []byte
|
|
93
|
+
capture := func(name, text string, _ *shimcompiler.WriteFileData) error {
|
|
94
|
+
rel := filepath.ToSlash(name)
|
|
95
|
+
if !isJavaScriptOutput(rel) {
|
|
96
|
+
return nil
|
|
97
|
+
}
|
|
98
|
+
captured = []byte(text)
|
|
99
|
+
return nil
|
|
100
|
+
}
|
|
101
|
+
_, eDiags, err := prog.EmitFile(rewrites, target, capture)
|
|
102
|
+
if err != nil {
|
|
103
|
+
fmt.Fprintf(stderr, "ttsc transform: emit: %v\n", err)
|
|
104
|
+
return 3
|
|
105
|
+
}
|
|
106
|
+
for _, d := range eDiags {
|
|
107
|
+
fmt.Fprintln(stderr, " -", d.String())
|
|
108
|
+
}
|
|
109
|
+
if captured == nil {
|
|
110
|
+
fmt.Fprintf(stderr, "ttsc transform: no output produced for %s\n", absFile)
|
|
111
|
+
return 3
|
|
112
|
+
}
|
|
113
|
+
if *out == "" {
|
|
114
|
+
if _, werr := bytes.NewReader(captured).WriteTo(stdout); werr != nil {
|
|
115
|
+
fmt.Fprintf(stderr, "ttsc transform: write stdout: %v\n", werr)
|
|
116
|
+
return 3
|
|
117
|
+
}
|
|
118
|
+
return 0
|
|
119
|
+
}
|
|
120
|
+
if dir := filepath.Dir(*out); dir != "" {
|
|
121
|
+
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
122
|
+
fmt.Fprintf(stderr, "ttsc transform: mkdir: %v\n", err)
|
|
123
|
+
return 3
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if err := os.WriteFile(*out, captured, 0o644); err != nil {
|
|
127
|
+
fmt.Fprintf(stderr, "ttsc transform: write %s: %v\n", *out, err)
|
|
128
|
+
return 3
|
|
129
|
+
}
|
|
130
|
+
return 0
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// sharedSourceStemSegments counts the shared trailing path segments between the
|
|
134
|
+
// emitted output path and the original source path after stripping extensions.
|
|
135
|
+
// `src/foo/bar.ts` ↔ `dist/foo/bar.js` returns 2, while unrelated paths return
|
|
136
|
+
// 0. `ttsc transform` uses the highest score to pick the emitted JS that
|
|
137
|
+
// belongs to the requested source file even when a project contains multiple
|
|
138
|
+
// `index.ts` files.
|
|
139
|
+
func sharedSourceStemSegments(outPath, srcPath string) int {
|
|
140
|
+
trim := func(p string) []string {
|
|
141
|
+
p = strings.TrimSuffix(p, filepath.Ext(p))
|
|
142
|
+
return strings.Split(p, "/")
|
|
143
|
+
}
|
|
144
|
+
a, b := trim(outPath), trim(srcPath)
|
|
145
|
+
n := len(a)
|
|
146
|
+
if len(b) < n {
|
|
147
|
+
n = len(b)
|
|
148
|
+
}
|
|
149
|
+
shared := 0
|
|
150
|
+
for i := 1; i <= n; i++ {
|
|
151
|
+
if a[len(a)-i] != b[len(b)-i] {
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
shared++
|
|
155
|
+
}
|
|
156
|
+
return shared
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
func isJavaScriptOutput(name string) bool {
|
|
160
|
+
switch strings.ToLower(filepath.Ext(name)) {
|
|
161
|
+
case ".js", ".mjs", ".cjs":
|
|
162
|
+
return true
|
|
163
|
+
default:
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
}
|
package/driver/host.go
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Package driver wraps the typescript-go compiler so the rest of ttsc doesn't
|
|
2
|
+
// need to know about shim packages directly. It is the only part of the engine
|
|
3
|
+
// that imports `shim/*` by design — everything downstream consumes a small,
|
|
4
|
+
// shim-agnostic interface (`*Program`).
|
|
5
|
+
//
|
|
6
|
+
// Structural note: this file is deliberately adapted from tsgonest's
|
|
7
|
+
// `internal/compiler/host.go` (MIT, github.com/tsgonest/tsgonest).
|
|
8
|
+
// The helper surface is the same; we keep names in sync to make future
|
|
9
|
+
// upstream changes easy to compare.
|
|
10
|
+
package driver
|
|
11
|
+
|
|
12
|
+
import (
|
|
13
|
+
"github.com/microsoft/typescript-go/shim/bundled"
|
|
14
|
+
shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
|
|
15
|
+
"github.com/microsoft/typescript-go/shim/vfs"
|
|
16
|
+
"github.com/microsoft/typescript-go/shim/vfs/cachedvfs"
|
|
17
|
+
"github.com/microsoft/typescript-go/shim/vfs/osvfs"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
// DefaultFS returns an OS-backed filesystem wrapped with tsgo's bundled libs so
|
|
21
|
+
// built-in definitions (lib.es*.d.ts, dom, etc.) resolve without a network
|
|
22
|
+
// fetch. Mirrors tsgonest/tsgolint behavior.
|
|
23
|
+
func DefaultFS() vfs.FS {
|
|
24
|
+
return bundled.WrapFS(cachedvfs.From(osvfs.FS()))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// DefaultHost returns a CompilerHost anchored at cwd that can find tsgo's
|
|
28
|
+
// bundled library files via `bundled.LibPath()`.
|
|
29
|
+
func DefaultHost(cwd string, fs vfs.FS) shimcompiler.CompilerHost {
|
|
30
|
+
return shimcompiler.NewCompilerHost(cwd, fs, bundled.LibPath(), nil, nil)
|
|
31
|
+
}
|