ttsc 0.3.0 → 0.4.0-dev.20260424
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 +216 -302
- 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 +17 -0
- package/lib/index.js +34 -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/package.json +70 -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 +18 -0
- package/src/launcher/ttsc-dev.js +42 -0
- package/src/launcher/ttsc.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/test/native.test.ts +60 -0
- package/test/package.test.ts +123 -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 -184
- 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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"flag"
|
|
6
|
+
"fmt"
|
|
7
|
+
"os"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
|
|
10
|
+
shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
|
|
11
|
+
|
|
12
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// runBuild implements the `ttsc` project-build lane. Two modes:
|
|
16
|
+
//
|
|
17
|
+
// - default (dry run): analyze quietly, surfacing only diagnostics/errors.
|
|
18
|
+
// - --emit: also run tsgo's emit pipeline, patching every recognized native
|
|
19
|
+
// consumer call in the resulting .js files.
|
|
20
|
+
func runBuild(args []string) int {
|
|
21
|
+
fs := flag.NewFlagSet("build", flag.ContinueOnError)
|
|
22
|
+
fs.SetOutput(stderr)
|
|
23
|
+
tsconfigPath := fs.String("tsconfig", "tsconfig.json", "path to tsconfig.json")
|
|
24
|
+
cwdOverride := fs.String("cwd", "", "override the working directory (defaults to process cwd)")
|
|
25
|
+
quiet := fs.Bool("quiet", true, "suppress the per-call diagnostic summary")
|
|
26
|
+
verbose := fs.Bool("verbose", false, "print the per-call diagnostic summary")
|
|
27
|
+
emit := fs.Bool("emit", false, "force emitted .js files (runs tsgo + ttsc rewrite)")
|
|
28
|
+
noEmit := fs.Bool("noEmit", false, "force analysis-only run with no file writes")
|
|
29
|
+
outDir := fs.String("outDir", "", "override compilerOptions.outDir for this build")
|
|
30
|
+
manifestPath := fs.String("manifest", "", "write emitted file list as JSON to this path")
|
|
31
|
+
rewriteMode := fs.String("rewrite-mode", "none", "native rewrite backend id")
|
|
32
|
+
if err := fs.Parse(args); err != nil {
|
|
33
|
+
return 2
|
|
34
|
+
}
|
|
35
|
+
if *emit && *noEmit {
|
|
36
|
+
fmt.Fprintln(stderr, "ttsc: --emit and --noEmit are mutually exclusive")
|
|
37
|
+
return 2
|
|
38
|
+
}
|
|
39
|
+
if *verbose {
|
|
40
|
+
*quiet = false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
cwd := *cwdOverride
|
|
44
|
+
if cwd == "" {
|
|
45
|
+
var err error
|
|
46
|
+
cwd, err = os.Getwd()
|
|
47
|
+
if err != nil {
|
|
48
|
+
fmt.Fprintf(stderr, "ttsc: could not get working directory: %v\n", err)
|
|
49
|
+
return 2
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
prog, diags, err := driver.LoadProgram(cwd, *tsconfigPath, driver.LoadProgramOptions{
|
|
54
|
+
ForceEmit: *emit,
|
|
55
|
+
ForceNoEmit: *noEmit,
|
|
56
|
+
OutDir: *outDir,
|
|
57
|
+
})
|
|
58
|
+
if err != nil {
|
|
59
|
+
fmt.Fprintf(stderr, "ttsc: %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
|
+
rewrites := driver.NewRewriteSet()
|
|
73
|
+
shouldEmit := !prog.ParsedConfig.ParsedConfig.CompilerOptions.NoEmit.IsTrue()
|
|
74
|
+
if *rewriteMode != "none" {
|
|
75
|
+
fmt.Fprintf(stderr, "ttsc: rewrite backend %q is not built into the standalone ttsc binary\n", *rewriteMode)
|
|
76
|
+
fmt.Fprintln(stderr, "ttsc: run through the JS host with the matching compiler plugin so it can select the consumer-provided native binary")
|
|
77
|
+
return 2
|
|
78
|
+
}
|
|
79
|
+
if !*quiet {
|
|
80
|
+
fmt.Fprintf(stdout, "// ttsc: tsconfig=%s cwd=%s sites=%d emit=%v rewrite=%s\n", *tsconfigPath, cwd, 0, shouldEmit, *rewriteMode)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if shouldEmit {
|
|
84
|
+
writeFile := shimcompiler.WriteFile(
|
|
85
|
+
func(fileName, text string, _ *shimcompiler.WriteFileData) error {
|
|
86
|
+
return driver.DefaultWriteFile(fileName, text)
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
res, eDiags, err := prog.EmitAll(rewrites, writeFile)
|
|
90
|
+
if err != nil {
|
|
91
|
+
fmt.Fprintf(stderr, "ttsc: emit failed: %v\n", err)
|
|
92
|
+
return 3
|
|
93
|
+
}
|
|
94
|
+
for _, d := range eDiags {
|
|
95
|
+
fmt.Fprintln(stderr, " -", d.String())
|
|
96
|
+
}
|
|
97
|
+
if !*quiet {
|
|
98
|
+
fmt.Fprintf(stdout, "// ttsc: emitted=%d files\n", len(res.EmittedFiles))
|
|
99
|
+
for _, f := range res.EmittedFiles {
|
|
100
|
+
rel := f
|
|
101
|
+
if abs, err := filepath.Rel(cwd, f); err == nil {
|
|
102
|
+
rel = abs
|
|
103
|
+
}
|
|
104
|
+
fmt.Fprintln(stdout, " +", rel)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if *manifestPath != "" {
|
|
108
|
+
data, err := json.Marshal(res.EmittedFiles)
|
|
109
|
+
if err != nil {
|
|
110
|
+
fmt.Fprintf(stderr, "ttsc: manifest marshal failed: %v\n", err)
|
|
111
|
+
return 3
|
|
112
|
+
}
|
|
113
|
+
if err := os.MkdirAll(filepath.Dir(*manifestPath), 0o755); err != nil {
|
|
114
|
+
fmt.Fprintf(stderr, "ttsc: manifest mkdir failed: %v\n", err)
|
|
115
|
+
return 3
|
|
116
|
+
}
|
|
117
|
+
if err := os.WriteFile(*manifestPath, data, 0o644); err != nil {
|
|
118
|
+
fmt.Fprintf(stderr, "ttsc: manifest write failed: %v\n", err)
|
|
119
|
+
return 3
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if !*quiet {
|
|
125
|
+
fmt.Fprintf(stdout, "// ttsc: recognized=%d total=%d rewrites=%d\n", 0, 0, rewrites.Len())
|
|
126
|
+
}
|
|
127
|
+
return 0
|
|
128
|
+
}
|
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
|
+
}
|