ttsc 0.9.0 → 0.10.0

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/utility/host.go CHANGED
@@ -13,6 +13,7 @@ import (
13
13
  shimast "github.com/microsoft/typescript-go/shim/ast"
14
14
  shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
15
15
  shimcore "github.com/microsoft/typescript-go/shim/core"
16
+ shimprinter "github.com/microsoft/typescript-go/shim/printer"
16
17
 
17
18
  "github.com/samchon/ttsc/packages/ttsc/driver"
18
19
  )
@@ -108,14 +109,19 @@ func RunTransform(args []string) int {
108
109
  if !ok {
109
110
  return 2
110
111
  }
111
- prog, _, _, _, ok := loadUtilityProgram(opts)
112
+ prog, _, _, sourcePreamble, ok := loadUtilityProgram(opts)
112
113
  if !ok {
113
114
  return 2
114
115
  }
115
116
  defer prog.Close()
117
+ printer := shimprinter.NewPrinter(shimprinter.PrinterOptions{}, shimprinter.PrintHandlers{}, nil)
116
118
  out := transformResult{TypeScript: map[string]string{}}
117
119
  for _, file := range prog.SourceFiles() {
118
- out.TypeScript[apiOutputKey(opts.cwd, file.FileName())] = file.Text()
120
+ text := shimprinter.EmitSourceFile(printer, file)
121
+ if sourcePreamble != "" && !shouldRemoveComments(prog) && !strings.Contains(text, sourcePreamble) {
122
+ text = driver.ApplySourcePreamble(text, sourcePreamble)
123
+ }
124
+ out.TypeScript[apiOutputKey(opts.cwd, file.FileName())] = text
119
125
  }
120
126
  data, err := json.Marshal(out)
121
127
  if err != nil {
@@ -140,6 +146,10 @@ func parseHostOptions(command string, args []string) (hostOptions, bool) {
140
146
  if err := fs.Parse(filterHostArgs(args)); err != nil {
141
147
  return hostOptions{}, false
142
148
  }
149
+ if *emit && *noEmit {
150
+ fmt.Fprintln(os.Stderr, "ttsc utility: --emit and --noEmit are mutually exclusive")
151
+ return hostOptions{}, false
152
+ }
143
153
  resolvedCwd := *cwd
144
154
  if resolvedCwd == "" {
145
155
  var err error
@@ -1,288 +0,0 @@
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
- }
@@ -1,52 +0,0 @@
1
- package driver
2
-
3
- import "testing"
4
-
5
- func TestSpliceCallConsumesRegexLiteralWithClosingParen(t *testing.T) {
6
- got := spliceForTest(t, `const out = plugin.make(/\)/, "ok");`)
7
- want := `const out = replacement;`
8
- if got != want {
9
- t.Fatalf("unexpected rewrite:\nwant: %s\n got: %s", want, got)
10
- }
11
- }
12
-
13
- func TestSpliceCallIgnoresClosingParenInsideBlockComment(t *testing.T) {
14
- got := spliceForTest(t, `const out = plugin.make(1 /* ) */, 2);`)
15
- want := `const out = replacement;`
16
- if got != want {
17
- t.Fatalf("unexpected rewrite:\nwant: %s\n got: %s", want, got)
18
- }
19
- }
20
-
21
- func TestSpliceCallIgnoresClosingParenInsideLineComment(t *testing.T) {
22
- got := spliceForTest(t, "const out = plugin.make(\n 1, // )\n 2\n);")
23
- want := `const out = replacement;`
24
- if got != want {
25
- t.Fatalf("unexpected rewrite:\nwant: %s\n got: %s", want, got)
26
- }
27
- }
28
-
29
- func TestSpliceCallKeepsDivisionOutsideRegexMode(t *testing.T) {
30
- got := spliceForTest(t, `const out = plugin.make(total / divisor, 2);`)
31
- want := `const out = replacement;`
32
- if got != want {
33
- t.Fatalf("unexpected rewrite:\nwant: %s\n got: %s", want, got)
34
- }
35
- }
36
-
37
- func spliceForTest(t *testing.T, text string) string {
38
- t.Helper()
39
- got, _, ok, err := spliceCall(text, Rewrite{
40
- RootName: "plugin",
41
- Method: "make",
42
- Replacement: "replacement",
43
- ConsumeParens: true,
44
- }, 0)
45
- if err != nil {
46
- t.Fatal(err)
47
- }
48
- if !ok {
49
- t.Fatal("rewrite did not match")
50
- }
51
- return got
52
- }
@@ -1,162 +0,0 @@
1
- package utility
2
-
3
- import (
4
- "os"
5
- "path/filepath"
6
- "strings"
7
- "testing"
8
- )
9
-
10
- func TestFilterHostArgsKeepsUtilityFlagsAndDropsHostOnlyFlags(t *testing.T) {
11
- got := filterHostArgs([]string{
12
- "--cwd", "/workspace/project",
13
- "--cache-dir", ".ttsc",
14
- "--emit",
15
- "--binary=/tmp/tsgo",
16
- "--plugins-json", "[]",
17
- "src/main.ts",
18
- "--",
19
- "--cwd", "ignored",
20
- })
21
- want := []string{
22
- "--cwd", "/workspace/project",
23
- "--emit",
24
- "--plugins-json", "[]",
25
- "src/main.ts",
26
- }
27
- if !equalStringSlices(got, want) {
28
- t.Fatalf("filtered args mismatch:\nwant: %#v\n got: %#v", want, got)
29
- }
30
- }
31
-
32
- func TestParseBannerFormatsAndSanitizesJSDoc(t *testing.T) {
33
- banner, err := parseBanner(map[string]any{
34
- "text": "first\r\nsecond */\n\n",
35
- }, t.TempDir(), "tsconfig.json")
36
- if err != nil {
37
- t.Fatal(err)
38
- }
39
- if !strings.Contains(banner, " * first\n * second * /\n") {
40
- t.Fatalf("banner was not normalized and sanitized:\n%s", banner)
41
- }
42
- if strings.Contains(banner, "second */") {
43
- t.Fatalf("banner must not preserve raw JSDoc terminator:\n%s", banner)
44
- }
45
- if !strings.HasSuffix(banner, "*/\n") {
46
- t.Fatalf("banner must end with a closed JSDoc block:\n%s", banner)
47
- }
48
- }
49
-
50
- func TestFindBannerConfigFileUsesTsconfigDirectoryWhenOutsideCwd(t *testing.T) {
51
- cwd := seedHostTestProject(t, map[string]string{
52
- "banner.config.cjs": `module.exports = { text: "cwd banner" };` + "\n",
53
- })
54
- wrapper := seedHostTestProject(t, map[string]string{
55
- "banner.config.cjs": `module.exports = { text: "wrapper banner" };` + "\n",
56
- "tsconfig.json": "{}",
57
- })
58
-
59
- discovered, err := findBannerConfigFile(cwd, filepath.Join(wrapper, "tsconfig.json"))
60
- if err != nil {
61
- t.Fatalf("findBannerConfigFile: %v", err)
62
- }
63
- if discovered != filepath.Join(wrapper, "banner.config.cjs") {
64
- t.Fatalf("unexpected discovery path: %s", discovered)
65
- }
66
- }
67
-
68
- func TestResolveBannerTextRejectsEmptyObjectText(t *testing.T) {
69
- _, err := resolveBannerText(map[string]any{
70
- "text": " ",
71
- }, t.TempDir(), "tsconfig.json")
72
- if err == nil || !strings.Contains(err.Error(), "non-empty string") {
73
- t.Fatalf("expected non-empty string error, got %v", err)
74
- }
75
- }
76
-
77
- func TestPathsRewriterResolvesIndexAndModuleExtensions(t *testing.T) {
78
- root := normalizePath(t.TempDir())
79
- source := normalizePath(filepath.Join(root, "src", "consumer", "main.ts"))
80
- index := normalizePath(filepath.Join(root, "src", "modules", "index.ts"))
81
- module := normalizePath(filepath.Join(root, "src", "modules", "esm.mts"))
82
- rewriter := &pathsRewriter{
83
- basePath: root,
84
- outDir: normalizePath(filepath.Join(root, "dist")),
85
- rootDir: normalizePath(filepath.Join(root, "src")),
86
- patterns: []pathsPattern{
87
- {pattern: "@lib/*", targets: []string{"src/modules/*"}},
88
- },
89
- sourceFiles: map[string]string{
90
- index: index,
91
- stripKnownSourceExtension(index): index,
92
- module: module,
93
- stripKnownSourceExtension(module): module,
94
- },
95
- }
96
-
97
- rewritten, ok := rewriter.rewrite(source, "@lib")
98
- if ok || rewritten != "@lib" {
99
- t.Fatalf("non-matching pattern should stay unchanged, got %q ok=%v", rewritten, ok)
100
- }
101
-
102
- rewritten, ok = rewriter.rewrite(source, "@lib/index")
103
- if !ok || rewritten != "../modules/index.js" {
104
- t.Fatalf("index rewrite mismatch: %q ok=%v", rewritten, ok)
105
- }
106
-
107
- rewritten, ok = rewriter.rewrite(source, "@lib/esm")
108
- if !ok || rewritten != "../modules/esm.mjs" {
109
- t.Fatalf("module extension rewrite mismatch: %q ok=%v", rewritten, ok)
110
- }
111
- }
112
-
113
- func TestStripConfigurationDefaultsAndValidation(t *testing.T) {
114
- strip, err := parseStrip(map[string]any{})
115
- if err != nil {
116
- t.Fatal(err)
117
- }
118
- if !strip.stripDebugger || !strip.matchesCall("console.log") || !strip.matchesCall("assert.equal") {
119
- t.Fatalf("default strip config did not enable expected patterns: %#v", strip)
120
- }
121
- if strip.matchesCall("console.info") || strip.matchesCall("assert") {
122
- t.Fatalf("default strip config matched too broadly")
123
- }
124
-
125
- if _, err := parseStrip(map[string]any{"statements": []any{"debugger", "with"}}); err == nil {
126
- t.Fatal("unsupported statement pattern must fail")
127
- }
128
- if _, err := parseCallPattern("assert.*.deep"); err == nil {
129
- t.Fatal("middle wildcard call pattern must fail")
130
- }
131
- }
132
-
133
- func TestSourceExtensionHelpersPreferCompoundExtensions(t *testing.T) {
134
- cases := map[string]string{
135
- "types.d.ts": "types",
136
- "types.d.mts": "types",
137
- "module.test.ts": "module.test",
138
- "entry.jsx": "entry",
139
- }
140
- for input, want := range cases {
141
- t.Run(input, func(t *testing.T) {
142
- if got := stripKnownSourceExtension(input); got != want {
143
- t.Fatalf("stripKnownSourceExtension(%q) = %q, want %q", input, got, want)
144
- }
145
- })
146
- }
147
- }
148
-
149
- func seedHostTestProject(t *testing.T, files map[string]string) string {
150
- t.Helper()
151
- root := t.TempDir()
152
- for name, text := range files {
153
- file := filepath.Join(root, filepath.FromSlash(name))
154
- if err := os.MkdirAll(filepath.Dir(file), 0o755); err != nil {
155
- t.Fatal(err)
156
- }
157
- if err := os.WriteFile(file, []byte(text), 0o644); err != nil {
158
- t.Fatal(err)
159
- }
160
- }
161
- return root
162
- }