typia 13.0.0-dev.20260510 → 13.0.0-dev.20260514

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.
@@ -31,6 +31,48 @@ func CleanupTransformedText(text string) string {
31
31
  return injectRuntimeImports(text)
32
32
  }
33
33
 
34
+ func CleanupTypeScriptTransformText(text string) string {
35
+ text = CleanupTransformedText(text)
36
+ text = normalizeParenthesizedTypeAnnotations(text)
37
+ text = regexp.MustCompile(`(?m)^import type \{([^{}\n]+)\} from`).ReplaceAllStringFunc(text, func(line string) string {
38
+ return regexp.MustCompile(`^import type \{\s*([^{}\n]+?)\s*\} from`).ReplaceAllString(line, "import type { $1 } from")
39
+ })
40
+ text = regexp.MustCompile(`(?m)(^import [^\n]+;\n)\n+(const |let |var |export )`).ReplaceAllString(text, "$1$2")
41
+ text = strings.ReplaceAll(text, "=(() =>", "= (() =>")
42
+ text = strings.ReplaceAll(text, ": (any) =>", ": any =>")
43
+ text = strings.ReplaceAll(text, ": (boolean) =>", ": boolean =>")
44
+ text = regexp.MustCompile(`input is \(([A-Za-z_$][A-Za-z0-9_$.]*)\)`).ReplaceAllString(text, "input is $1")
45
+ text = strings.ReplaceAll(text, "return (success ? ", "return success ? ")
46
+ text = strings.ReplaceAll(text, "}) as any;", "} as any;")
47
+ text = strings.ReplaceAll(text, "(() => {\n const ", "(() => { const ")
48
+ text = strings.ReplaceAll(text, "(() => {\n let ", "(() => { let ")
49
+ text = strings.ReplaceAll(text, "(() => {\n return ", "(() => { return ")
50
+ text = strings.ReplaceAll(text, ";\n const ", "; const ")
51
+ text = strings.ReplaceAll(text, ";\n let ", "; let ")
52
+ text = strings.ReplaceAll(text, ";\n return ", "; return ")
53
+ text = strings.ReplaceAll(text, "\n };\n})()", "\n}; })()")
54
+ text = strings.ReplaceAll(text, "\n });\n})()", "\n}); })()")
55
+ text = strings.ReplaceAll(text, "\n }); let ", "\n}); let ")
56
+ text = strings.ReplaceAll(text, ";\n})()", "; })()")
57
+ text = strings.ReplaceAll(text, "\n ", "\n ")
58
+ text = regexp.MustCompile(`\n\n([A-Za-z_$][A-Za-z0-9_$]*\([^;\n]*\);?)`).ReplaceAllString(text, "\n$1")
59
+ trimmed := strings.TrimRight(text, " \t\r\n")
60
+ if strings.HasSuffix(trimmed, ")") && !strings.HasSuffix(trimmed, ";") {
61
+ return trimmed + ";\n"
62
+ }
63
+ if text != "" && !strings.HasSuffix(text, "\n") {
64
+ return text + "\n"
65
+ }
66
+ return text
67
+ }
68
+
69
+ func normalizeParenthesizedTypeAnnotations(text string) string {
70
+ typeAtom := `([A-Za-z_$][A-Za-z0-9_$.]*(<[^()\n;{}]*>)?)`
71
+ text = regexp.MustCompile(`: \(`+typeAtom+`\)(\s*=>)`).ReplaceAllString(text, ": $1$3")
72
+ text = regexp.MustCompile(`\| \((null|undefined)\)`).ReplaceAllString(text, "| $1")
73
+ return text
74
+ }
75
+
34
76
  type unusedImportPattern struct {
35
77
  regex *regexp.Regexp
36
78
  alias func(string) string
@@ -4,15 +4,25 @@ import (
4
4
  "path/filepath"
5
5
  "strconv"
6
6
  "strings"
7
+ "sync"
7
8
  "unicode"
8
9
 
9
10
  shimast "github.com/microsoft/typescript-go/shim/ast"
10
11
  )
11
12
 
13
+ type commonJSImportIdentifierSubstitutionsCacheEntry struct {
14
+ value map[string]string
15
+ }
16
+
17
+ var commonJSImportIdentifierSubstitutionsCache sync.Map
18
+
12
19
  func commonJSImportIdentifierSubstitutions(file *shimast.SourceFile) map[string]string {
13
20
  if file == nil || file.Statements == nil {
14
21
  return nil
15
22
  }
23
+ if cached, ok := commonJSImportIdentifierSubstitutionsCache.Load(file); ok {
24
+ return cached.(commonJSImportIdentifierSubstitutionsCacheEntry).value
25
+ }
16
26
  output := map[string]string{}
17
27
  counts := map[string]int{}
18
28
  for _, stmt := range file.Statements.Nodes {
@@ -61,8 +71,9 @@ func commonJSImportIdentifierSubstitutions(file *shimast.SourceFile) map[string]
61
71
  }
62
72
  }
63
73
  if len(output) == 0 {
64
- return nil
74
+ output = nil
65
75
  }
76
+ commonJSImportIdentifierSubstitutionsCache.Store(file, commonJSImportIdentifierSubstitutionsCacheEntry{value: output})
66
77
  return output
67
78
  }
68
79
 
@@ -1,3 +1,5 @@
1
+ //go:build !js
2
+
1
3
  package main
2
4
 
3
5
  import (
@@ -0,0 +1,226 @@
1
+ //go:build js && wasm
2
+
3
+ package main
4
+
5
+ import (
6
+ "bytes"
7
+ "fmt"
8
+ "io"
9
+ "os"
10
+ "runtime"
11
+ "syscall/js"
12
+ "time"
13
+ )
14
+
15
+ // Reuse the same package-level identifiers the native CLI declares. Under
16
+ // `js && wasm`, the native `main.go` is excluded, so we re-declare them here.
17
+ var (
18
+ version = "0.1.0-dev"
19
+ commit = "dev"
20
+ date = "unknown"
21
+ )
22
+
23
+ var (
24
+ stdout io.Writer = os.Stdout
25
+ stderr io.Writer = os.Stderr
26
+ )
27
+
28
+ // main keeps the Go runtime alive forever after registering the JS entry
29
+ // points. JS code calls `globalThis.ttscTypia.transform({...})` repeatedly;
30
+ // each call reuses the same Go process (no re-instantiation cost).
31
+ func main() {
32
+ api := map[string]any{
33
+ "transform": js.FuncOf(jsTransform),
34
+ "build": js.FuncOf(jsBuild),
35
+ "version": js.FuncOf(jsVersion),
36
+ }
37
+ js.Global().Set("ttscTypia", js.ValueOf(api))
38
+
39
+ // Signal readiness so the JS host can resolve a "ready" promise.
40
+ if ready := js.Global().Get("ttscTypiaReady"); ready.Type() == js.TypeFunction {
41
+ ready.Invoke()
42
+ }
43
+
44
+ // A perpetual idle goroutine prevents Go's wasm deadlock detector from
45
+ // tripping while syscall.fsCall callbacks are in flight. `select {}` alone
46
+ // is wrongly classified as "all goroutines asleep" the moment the FS path
47
+ // hands the request off to JS.
48
+ go func() {
49
+ for {
50
+ time.Sleep(time.Hour)
51
+ }
52
+ }()
53
+ select {}
54
+ }
55
+
56
+ // jsVersion returns the build metadata for this wasm binary.
57
+ func jsVersion(this js.Value, args []js.Value) any {
58
+ return js.ValueOf(map[string]any{
59
+ "version": version,
60
+ "commit": commit,
61
+ "date": date,
62
+ "go": runtime.Version(),
63
+ "goos": runtime.GOOS,
64
+ "goarch": runtime.GOARCH,
65
+ })
66
+ }
67
+
68
+ // jsTransform runs the project transform pipeline on a single file.
69
+ //
70
+ // JS input shape:
71
+ // {
72
+ // cwd: string, // absolute virtual path
73
+ // tsconfig: string, // absolute path to tsconfig.json
74
+ // file?: string, // absolute path of the .ts file to rewrite. Omit
75
+ // // for project-wide JSON output.
76
+ // output?: "ts"|"js",
77
+ // }
78
+ //
79
+ // JS output: Promise<{ code: number, stdout: string, stderr: string }>.
80
+ //
81
+ // The handler returns a Promise so the JS event loop can drain while Go
82
+ // services its own asynchronous file-system callbacks. Returning a synchronous
83
+ // value would re-enter JS and deadlock on `fs.stat` callbacks.
84
+ //
85
+ // Caller is responsible for populating an in-memory filesystem (via the
86
+ // `globalThis.fs` shim picked up by wasm_exec.js) with every path referenced
87
+ // here before invoking transform.
88
+ func jsTransform(this js.Value, args []js.Value) any {
89
+ if len(args) == 0 || args[0].Type() != js.TypeObject {
90
+ return errorPromise(2, "ttsc-typia: transform requires an options object")
91
+ }
92
+ argv := transformArgs(args[0])
93
+ return makePromise(func() any {
94
+ return runWithCapturedIO(func() int { return runTransform(argv) })
95
+ })
96
+ }
97
+
98
+ // jsBuild runs the project build pipeline. JS input mirrors jsTransform but
99
+ // has no `file`/`output`. Returns a Promise.
100
+ func jsBuild(this js.Value, args []js.Value) any {
101
+ if len(args) == 0 || args[0].Type() != js.TypeObject {
102
+ return errorPromise(2, "ttsc-typia: build requires an options object")
103
+ }
104
+ argv := buildArgs(args[0])
105
+ return makePromise(func() any {
106
+ return runWithCapturedIO(func() int { return runBuild(argv) })
107
+ })
108
+ }
109
+
110
+ // makePromise wraps a Go computation as a JS Promise. The returned object is
111
+ // `new Promise((resolve, reject) => { ... })`. The executor synchronously
112
+ // captures the callbacks; the work itself runs in a goroutine so the JS event
113
+ // loop can drive `fs.stat` and friends to completion before we ask Go to
114
+ // receive the callback.
115
+ func makePromise(work func() any) js.Value {
116
+ var executor js.Func
117
+ executor = js.FuncOf(func(this js.Value, args []js.Value) any {
118
+ resolve := args[0]
119
+ reject := args[1]
120
+ go func() {
121
+ defer func() {
122
+ if r := recover(); r != nil {
123
+ err := js.Global().Get("Error").New(fmt.Sprintf("%v", r))
124
+ reject.Invoke(err)
125
+ }
126
+ executor.Release()
127
+ }()
128
+ resolve.Invoke(work())
129
+ }()
130
+ return nil
131
+ })
132
+ return js.Global().Get("Promise").New(executor)
133
+ }
134
+
135
+ func errorPromise(code int, message string) js.Value {
136
+ return makePromise(func() any { return errorResponse(code, message) })
137
+ }
138
+
139
+ func transformArgs(opts js.Value) []string {
140
+ out := []string{}
141
+ if v := stringProp(opts, "cwd"); v != "" {
142
+ out = append(out, "--cwd="+v)
143
+ }
144
+ if v := stringProp(opts, "tsconfig"); v != "" {
145
+ out = append(out, "--tsconfig="+v)
146
+ } else {
147
+ out = append(out, "--tsconfig=tsconfig.json")
148
+ }
149
+ if v := stringProp(opts, "file"); v != "" {
150
+ out = append(out, "--file="+v)
151
+ }
152
+ if v := stringProp(opts, "output"); v != "" {
153
+ out = append(out, "--output="+v)
154
+ } else {
155
+ out = append(out, "--output=ts")
156
+ }
157
+ return out
158
+ }
159
+
160
+ func buildArgs(opts js.Value) []string {
161
+ out := []string{}
162
+ if v := stringProp(opts, "cwd"); v != "" {
163
+ out = append(out, "--cwd="+v)
164
+ }
165
+ if v := stringProp(opts, "tsconfig"); v != "" {
166
+ out = append(out, "--tsconfig="+v)
167
+ } else {
168
+ out = append(out, "--tsconfig=tsconfig.json")
169
+ }
170
+ if outDir := stringProp(opts, "outDir"); outDir != "" {
171
+ out = append(out, "--outDir="+outDir)
172
+ }
173
+ if boolProp(opts, "emit") {
174
+ out = append(out, "--emit")
175
+ }
176
+ if boolProp(opts, "noEmit") {
177
+ out = append(out, "--noEmit")
178
+ }
179
+ return out
180
+ }
181
+
182
+ func runWithCapturedIO(task func() int) any {
183
+ // Swap the package-level writers so the inner Go functions feel like a
184
+ // normal CLI invocation but their output lands in JS-visible buffers.
185
+ // This is safe under the contract that JS calls are serialized (no
186
+ // concurrent invocations from JS).
187
+ oldOut, oldErr := stdout, stderr
188
+ var outBuf, errBuf bytes.Buffer
189
+ stdout = &outBuf
190
+ stderr = &errBuf
191
+ defer func() {
192
+ stdout = oldOut
193
+ stderr = oldErr
194
+ }()
195
+
196
+ code := task()
197
+ return js.ValueOf(map[string]any{
198
+ "code": code,
199
+ "stdout": outBuf.String(),
200
+ "stderr": errBuf.String(),
201
+ })
202
+ }
203
+
204
+ func errorResponse(code int, message string) any {
205
+ return js.ValueOf(map[string]any{
206
+ "code": code,
207
+ "stdout": "",
208
+ "stderr": fmt.Sprintf("%s\n", message),
209
+ })
210
+ }
211
+
212
+ func stringProp(obj js.Value, key string) string {
213
+ v := obj.Get(key)
214
+ if v.Type() != js.TypeString {
215
+ return ""
216
+ }
217
+ return v.String()
218
+ }
219
+
220
+ func boolProp(obj js.Value, key string) bool {
221
+ v := obj.Get(key)
222
+ if v.Type() != js.TypeBoolean {
223
+ return false
224
+ }
225
+ return v.Bool()
226
+ }
@@ -7,7 +7,6 @@ import (
7
7
  "fmt"
8
8
  "os"
9
9
  "path/filepath"
10
- "regexp"
11
10
  "sort"
12
11
  "strings"
13
12
 
@@ -373,45 +372,7 @@ func applySourceRewrites(source string, rewrites []transformSourceRewrite) (stri
373
372
  }
374
373
 
375
374
  func cleanupTypeScriptTransformText(text string) string {
376
- text = typiaadapter.CleanupTransformedText(text)
377
- text = normalizeParenthesizedTypeAnnotations(text)
378
- text = regexp.MustCompile(`(?m)^import type \{([^{}\n]+)\} from`).ReplaceAllStringFunc(text, func(line string) string {
379
- return regexp.MustCompile(`^import type \{\s*([^{}\n]+?)\s*\} from`).ReplaceAllString(line, "import type { $1 } from")
380
- })
381
- text = regexp.MustCompile(`(?m)(^import [^\n]+;\n)\n+(const |let |var |export )`).ReplaceAllString(text, "$1$2")
382
- text = strings.ReplaceAll(text, "=(() =>", "= (() =>")
383
- text = strings.ReplaceAll(text, ": (any) =>", ": any =>")
384
- text = strings.ReplaceAll(text, ": (boolean) =>", ": boolean =>")
385
- text = regexp.MustCompile(`input is \(([A-Za-z_$][A-Za-z0-9_$.]*)\)`).ReplaceAllString(text, "input is $1")
386
- text = strings.ReplaceAll(text, "return (success ? ", "return success ? ")
387
- text = strings.ReplaceAll(text, "}) as any;", "} as any;")
388
- text = strings.ReplaceAll(text, "(() => {\n const ", "(() => { const ")
389
- text = strings.ReplaceAll(text, "(() => {\n let ", "(() => { let ")
390
- text = strings.ReplaceAll(text, "(() => {\n return ", "(() => { return ")
391
- text = strings.ReplaceAll(text, ";\n const ", "; const ")
392
- text = strings.ReplaceAll(text, ";\n let ", "; let ")
393
- text = strings.ReplaceAll(text, ";\n return ", "; return ")
394
- text = strings.ReplaceAll(text, "\n };\n})()", "\n}; })()")
395
- text = strings.ReplaceAll(text, "\n });\n})()", "\n}); })()")
396
- text = strings.ReplaceAll(text, "\n }); let ", "\n}); let ")
397
- text = strings.ReplaceAll(text, ";\n})()", "; })()")
398
- text = strings.ReplaceAll(text, "\n ", "\n ")
399
- text = regexp.MustCompile(`\n\n([A-Za-z_$][A-Za-z0-9_$]*\([^;\n]*\);?)`).ReplaceAllString(text, "\n$1")
400
- trimmed := strings.TrimRight(text, " \t\r\n")
401
- if strings.HasSuffix(trimmed, ")") && !strings.HasSuffix(trimmed, ";") {
402
- return trimmed + ";\n"
403
- }
404
- if text != "" && !strings.HasSuffix(text, "\n") {
405
- return text + "\n"
406
- }
407
- return text
408
- }
409
-
410
- func normalizeParenthesizedTypeAnnotations(text string) string {
411
- typeAtom := `([A-Za-z_$][A-Za-z0-9_$.]*(<[^()\n;{}]*>)?)`
412
- text = regexp.MustCompile(`: \(`+typeAtom+`\)(\s*=>)`).ReplaceAllString(text, ": $1$3")
413
- text = regexp.MustCompile(`\| \((null|undefined)\)`).ReplaceAllString(text, "| $1")
414
- return text
375
+ return typiaadapter.CleanupTypeScriptTransformText(text)
415
376
  }
416
377
 
417
378
  func sourceFileKey(cwd string, file string) string {