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.
Files changed (121) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +93 -362
  3. package/cmd/ttsc/build.go +128 -0
  4. package/cmd/ttsc/main.go +222 -0
  5. package/cmd/ttsc/main_test.go +288 -0
  6. package/cmd/ttsc/transform.go +166 -0
  7. package/driver/host.go +31 -0
  8. package/driver/program.go +298 -0
  9. package/driver/rewrite.go +315 -0
  10. package/go.mod +48 -0
  11. package/go.sum +22 -0
  12. package/lib/api.d.ts +91 -0
  13. package/lib/api.js +325 -0
  14. package/lib/api.js.map +1 -0
  15. package/lib/cli.d.ts +1 -0
  16. package/lib/cli.js +481 -0
  17. package/lib/cli.js.map +1 -0
  18. package/lib/index.d.ts +18 -0
  19. package/lib/index.js +35 -0
  20. package/lib/index.js.map +1 -0
  21. package/lib/native.d.ts +15 -0
  22. package/lib/native.js +34 -0
  23. package/lib/native.js.map +1 -0
  24. package/lib/platform.d.ts +66 -0
  25. package/lib/platform.js +170 -0
  26. package/lib/platform.js.map +1 -0
  27. package/lib/plugin.d.ts +47 -0
  28. package/lib/plugin.js +150 -0
  29. package/lib/plugin.js.map +1 -0
  30. package/lib/project.d.ts +32 -0
  31. package/lib/project.js +303 -0
  32. package/lib/project.js.map +1 -0
  33. package/lib/runner/cli.d.ts +1 -0
  34. package/lib/runner/cli.js +284 -0
  35. package/lib/runner/cli.js.map +1 -0
  36. package/lib/runner/register.d.ts +13 -0
  37. package/lib/runner/register.js +424 -0
  38. package/lib/runner/register.js.map +1 -0
  39. package/package.json +72 -63
  40. package/shim/ast/extra-shim.json +4 -0
  41. package/shim/ast/go.mod +5 -0
  42. package/shim/ast/shim.go +117 -0
  43. package/shim/bundled/extra-shim.json +1 -0
  44. package/shim/bundled/go.mod +5 -0
  45. package/shim/bundled/shim.go +19 -0
  46. package/shim/checker/extra-shim.json +31 -0
  47. package/shim/checker/go.mod +5 -0
  48. package/shim/checker/shim.go +146 -0
  49. package/shim/compiler/extra-shim.json +4 -0
  50. package/shim/compiler/go.mod +5 -0
  51. package/shim/compiler/shim.go +50 -0
  52. package/shim/core/extra-shim.json +4 -0
  53. package/shim/core/go.mod +5 -0
  54. package/shim/core/shim.go +11 -0
  55. package/shim/diagnosticwriter/go.mod +5 -0
  56. package/shim/diagnosticwriter/shim.go +29 -0
  57. package/shim/parser/extra-shim.json +4 -0
  58. package/shim/parser/go.mod +5 -0
  59. package/shim/parser/shim.go +55 -0
  60. package/shim/scanner/extra-shim.json +4 -0
  61. package/shim/scanner/go.mod +5 -0
  62. package/shim/scanner/shim.go +96 -0
  63. package/shim/tsoptions/extra-shim.json +4 -0
  64. package/shim/tsoptions/go.mod +5 -0
  65. package/shim/tsoptions/shim.go +123 -0
  66. package/shim/tspath/extra-shim.json +4 -0
  67. package/shim/tspath/go.mod +5 -0
  68. package/shim/tspath/shim.go +165 -0
  69. package/shim/vfs/cachedvfs/extra-shim.json +4 -0
  70. package/shim/vfs/cachedvfs/go.mod +5 -0
  71. package/shim/vfs/cachedvfs/shim.go +12 -0
  72. package/shim/vfs/extra-shim.json +4 -0
  73. package/shim/vfs/go.mod +5 -0
  74. package/shim/vfs/osvfs/extra-shim.json +4 -0
  75. package/shim/vfs/osvfs/go.mod +5 -0
  76. package/shim/vfs/osvfs/shim.go +13 -0
  77. package/shim/vfs/shim.go +19 -0
  78. package/src/api.ts +416 -0
  79. package/src/cli.ts +492 -0
  80. package/src/index.ts +19 -0
  81. package/src/launcher/ttsc-dev.js +42 -0
  82. package/src/launcher/ttsc.js +19 -0
  83. package/src/launcher/ttsx.js +19 -0
  84. package/src/native.ts +60 -0
  85. package/src/platform.ts +163 -0
  86. package/src/plugin.ts +241 -0
  87. package/src/project.ts +330 -0
  88. package/src/runner/cli.ts +286 -0
  89. package/src/runner/register.ts +487 -0
  90. package/test/native.test.ts +60 -0
  91. package/test/package.test.ts +131 -0
  92. package/test/platform.test.ts +58 -0
  93. package/test/project.test.ts +195 -0
  94. package/tools/gen_shims/go.mod +14 -0
  95. package/tools/gen_shims/go.sum +12 -0
  96. package/tools/gen_shims/main.go +446 -0
  97. package/CHANGELOG.md +0 -2
  98. package/bin/tsc +0 -2
  99. package/bin/tsserver +0 -2
  100. package/lib/PluginCreator.d.ts +0 -67
  101. package/lib/PluginCreator.js +0 -190
  102. package/lib/cancelationToken.d.ts +0 -0
  103. package/lib/cancelationToken.js +0 -2
  104. package/lib/loadTypescript.d.ts +0 -7
  105. package/lib/loadTypescript.js +0 -56
  106. package/lib/patchCreateProgram.d.ts +0 -21
  107. package/lib/patchCreateProgram.js +0 -190
  108. package/lib/tsc.d.ts +0 -1
  109. package/lib/tsc.js +0 -24
  110. package/lib/tsserver.d.ts +0 -0
  111. package/lib/tsserver.js +0 -2
  112. package/lib/tsserverlibrary.d.ts +0 -3
  113. package/lib/tsserverlibrary.js +0 -4
  114. package/lib/typescript.d.ts +0 -3
  115. package/lib/typescript.js +0 -4
  116. package/lib/typescriptServices.d.ts +0 -3
  117. package/lib/typescriptServices.js +0 -4
  118. package/lib/typingsInstaller.d.ts +0 -0
  119. package/lib/typingsInstaller.js +0 -2
  120. package/lib/watchGuard.d.ts +0 -0
  121. package/lib/watchGuard.js +0 -2
@@ -0,0 +1,298 @@
1
+ package driver
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "fmt"
7
+ "io"
8
+ "path/filepath"
9
+
10
+ "github.com/microsoft/typescript-go/shim/ast"
11
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
12
+ shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
13
+ "github.com/microsoft/typescript-go/shim/core"
14
+ shimdiagnosticwriter "github.com/microsoft/typescript-go/shim/diagnosticwriter"
15
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
16
+ "github.com/microsoft/typescript-go/shim/tsoptions"
17
+ "github.com/microsoft/typescript-go/shim/tspath"
18
+ "github.com/microsoft/typescript-go/shim/vfs"
19
+ )
20
+
21
+ // Diagnostic is the compilation diagnostic shape ttsc passes around. Kept
22
+ // dependency-free (no shim types) so callers can render or inspect freely.
23
+ type Diagnostic struct {
24
+ File string
25
+ Line int
26
+ Column int
27
+ Message string
28
+ raw *ast.Diagnostic
29
+ }
30
+
31
+ // SourceFile returns the program source file matching filename.
32
+ func (p *Program) SourceFile(filename string) *ast.SourceFile {
33
+ if p == nil || p.TSProgram == nil {
34
+ return nil
35
+ }
36
+ normalized := filepath.ToSlash(filename)
37
+ for _, file := range p.TSProgram.SourceFiles() {
38
+ if filepath.ToSlash(file.FileName()) == normalized {
39
+ return file
40
+ }
41
+ }
42
+ return nil
43
+ }
44
+
45
+ // String returns a `path:line:col: message` formatted string.
46
+ func (d Diagnostic) String() string {
47
+ if d.File == "" {
48
+ return d.Message
49
+ }
50
+ if d.Line > 0 {
51
+ return fmt.Sprintf("%s:%d:%d: %s", d.File, d.Line, d.Column, d.Message)
52
+ }
53
+ return fmt.Sprintf("%s: %s", d.File, d.Message)
54
+ }
55
+
56
+ // WritePrettyDiagnostics renders diagnostics with TypeScript-style colors,
57
+ // source snippets and the trailing error summary when raw tsgo diagnostic
58
+ // objects are available. It falls back to the legacy one-line form for
59
+ // diagnostics assembled outside tsgo.
60
+ func WritePrettyDiagnostics(w io.Writer, diagnostics []Diagnostic, cwd string) {
61
+ raw := make([]*ast.Diagnostic, 0, len(diagnostics))
62
+ for _, d := range diagnostics {
63
+ if d.raw == nil {
64
+ for _, d := range diagnostics {
65
+ fmt.Fprintln(w, " -", d.String())
66
+ }
67
+ return
68
+ }
69
+ raw = append(raw, d.raw)
70
+ }
71
+ shimdiagnosticwriter.FormatASTDiagnosticsWithColorAndContext(w, raw, cwd)
72
+ }
73
+
74
+ // Program is the shim-agnostic facade the rest of the engine sees.
75
+ type Program struct {
76
+ TSProgram *shimcompiler.Program
77
+ ParsedConfig *tsoptions.ParsedCommandLine
78
+ Checker *shimchecker.Checker
79
+ checkerRelease func()
80
+ Host shimcompiler.CompilerHost
81
+ FS vfs.FS
82
+ }
83
+
84
+ // LoadProgramOptions controls tsconfig overrides applied before tsgo creates
85
+ // the program. `ForceEmit` is used by `ttsc --emit` and `ttsc transform`
86
+ // so runtime compilation still works when the project defaults to `noEmit`.
87
+ type LoadProgramOptions struct {
88
+ ForceEmit bool
89
+ ForceNoEmit bool
90
+ OutDir string
91
+ }
92
+
93
+ // Close releases the checker pool lease acquired by LoadProgram.
94
+ func (p *Program) Close() error {
95
+ if p.checkerRelease != nil {
96
+ p.checkerRelease()
97
+ p.checkerRelease = nil
98
+ }
99
+ return nil
100
+ }
101
+
102
+ // ParseTSConfig parses a tsconfig.json file via tsgo's native JSONC parser.
103
+ // Comments, trailing commas, and `extends` chains are handled automatically.
104
+ //
105
+ // The absolute path is resolved against cwd before any VFS lookups because
106
+ // tsgo's filesystem APIs require absolute paths — mirrors what tsc does when
107
+ // you pass a relative `--project` flag.
108
+ func ParseTSConfig(fs vfs.FS, cwd, tsconfigPath string, host shimcompiler.CompilerHost) (*tsoptions.ParsedCommandLine, []Diagnostic, error) {
109
+ resolved := tspath.ResolvePath(cwd, tsconfigPath)
110
+ if !fs.FileExists(resolved) {
111
+ return nil, nil, fmt.Errorf("tsconfig not found: %s", resolved)
112
+ }
113
+ parsed, diags := tsoptions.GetParsedCommandLineOfConfigFile(resolved, &core.CompilerOptions{}, nil, host, nil)
114
+ if len(diags) > 0 {
115
+ return nil, convertDiagnostics(diags), nil
116
+ }
117
+ if parsed == nil {
118
+ return nil, nil, errors.New("tsoptions: parsed command line was nil")
119
+ }
120
+ if len(parsed.Errors) > 0 {
121
+ return nil, convertDiagnostics(parsed.Errors), nil
122
+ }
123
+ return parsed, nil, nil
124
+ }
125
+
126
+ // CreateProgramFromConfig builds a tsgo Program from the parsed config.
127
+ func CreateProgramFromConfig(parsed *tsoptions.ParsedCommandLine, host shimcompiler.CompilerHost) (*shimcompiler.Program, []Diagnostic, error) {
128
+ opts := shimcompiler.ProgramOptions{
129
+ Config: parsed,
130
+ SingleThreaded: core.TSTrue,
131
+ Host: host,
132
+ UseSourceOfProjectReference: true,
133
+ }
134
+ p := shimcompiler.NewProgram(opts)
135
+ if p == nil {
136
+ return nil, nil, errors.New("shimcompiler.NewProgram returned nil")
137
+ }
138
+ return p, nil, nil
139
+ }
140
+
141
+ // LoadProgram is the one-shot convenience used by `ttsc`.
142
+ // It parses the tsconfig, creates a program and a type-checker, and returns
143
+ // the wrapped facade.
144
+ //
145
+ // cwd must be absolute; tsconfigPath may be relative to cwd.
146
+ func LoadProgram(cwd, tsconfigPath string, options LoadProgramOptions) (*Program, []Diagnostic, error) {
147
+ if !filepath.IsAbs(cwd) {
148
+ if abs, err := filepath.Abs(cwd); err == nil {
149
+ cwd = abs
150
+ }
151
+ }
152
+ cwd = tspath.ResolvePath(cwd)
153
+ fs := DefaultFS()
154
+ host := DefaultHost(cwd, fs)
155
+
156
+ parsed, diags, err := ParseTSConfig(fs, cwd, tsconfigPath, host)
157
+ if err != nil {
158
+ return nil, nil, err
159
+ }
160
+ if len(diags) > 0 {
161
+ return nil, diags, nil
162
+ }
163
+ if options.ForceNoEmit {
164
+ forceNoEmit(parsed)
165
+ }
166
+ if options.ForceEmit {
167
+ forceEmit(parsed)
168
+ }
169
+ if options.OutDir != "" {
170
+ overrideOutDir(cwd, parsed, options.OutDir)
171
+ }
172
+
173
+ tsProgram, progDiags, err := CreateProgramFromConfig(parsed, host)
174
+ if err != nil {
175
+ return nil, nil, err
176
+ }
177
+ if len(progDiags) > 0 {
178
+ return nil, progDiags, nil
179
+ }
180
+
181
+ checker, done := tsProgram.GetTypeChecker(context.Background())
182
+ return &Program{
183
+ TSProgram: tsProgram,
184
+ ParsedConfig: parsed,
185
+ Checker: checker,
186
+ checkerRelease: done,
187
+ Host: host,
188
+ FS: fs,
189
+ }, nil, nil
190
+ }
191
+
192
+ func forceEmit(parsed *tsoptions.ParsedCommandLine) {
193
+ if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
194
+ return
195
+ }
196
+ options := parsed.ParsedConfig.CompilerOptions
197
+ options.NoEmit = core.TSFalse
198
+ options.EmitDeclarationOnly = core.TSFalse
199
+ }
200
+
201
+ func forceNoEmit(parsed *tsoptions.ParsedCommandLine) {
202
+ if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
203
+ return
204
+ }
205
+ parsed.ParsedConfig.CompilerOptions.NoEmit = core.TSTrue
206
+ }
207
+
208
+ func overrideOutDir(cwd string, parsed *tsoptions.ParsedCommandLine, outDir string) {
209
+ if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
210
+ return
211
+ }
212
+ parsed.ParsedConfig.CompilerOptions.OutDir = tspath.ResolvePath(cwd, outDir)
213
+ }
214
+
215
+ // SourceFiles exposes the program's user-authored source files (declaration
216
+ // files filtered out).
217
+ func (p *Program) SourceFiles() []*ast.SourceFile {
218
+ out := make([]*ast.SourceFile, 0)
219
+ for _, f := range p.TSProgram.SourceFiles() {
220
+ if f.IsDeclarationFile {
221
+ continue
222
+ }
223
+ out = append(out, f)
224
+ }
225
+ return out
226
+ }
227
+
228
+ // Diagnostics returns project diagnostics that must block compilation or
229
+ // runtime execution before any JavaScript is emitted or evaluated.
230
+ func (p *Program) Diagnostics() []Diagnostic {
231
+ if p == nil || p.TSProgram == nil {
232
+ return []Diagnostic{{Message: "driver: nil program"}}
233
+ }
234
+ ctx := context.Background()
235
+ raw := shimcompiler.GetDiagnosticsOfAnyProgram(
236
+ ctx,
237
+ p.TSProgram,
238
+ nil,
239
+ false,
240
+ p.TSProgram.GetBindDiagnostics,
241
+ p.TSProgram.GetSemanticDiagnostics,
242
+ )
243
+ raw = filterDiagnostics(raw)
244
+ return convertDiagnostics(shimcompiler.SortAndDeduplicateDiagnostics(raw))
245
+ }
246
+
247
+ func filterDiagnostics(in []*ast.Diagnostic) []*ast.Diagnostic {
248
+ out := in[:0]
249
+ for _, d := range in {
250
+ if isUnusedOverloadSignatureTypeParameterDiagnostic(d) {
251
+ continue
252
+ }
253
+ out = append(out, d)
254
+ }
255
+ return out
256
+ }
257
+
258
+ func isUnusedOverloadSignatureTypeParameterDiagnostic(d *ast.Diagnostic) bool {
259
+ if d == nil || d.File() == nil {
260
+ return false
261
+ }
262
+ switch d.Code() {
263
+ case 6196, 6205: // unused declaration / all type parameters are unused
264
+ default:
265
+ return false
266
+ }
267
+ node := ast.GetNodeAtPosition(d.File(), d.Pos(), false)
268
+ for node != nil {
269
+ if node.Kind == ast.KindFunctionDeclaration {
270
+ return node.Body() == nil
271
+ }
272
+ node = node.Parent
273
+ }
274
+ return false
275
+ }
276
+
277
+ // convertDiagnostics translates shim-specific diagnostics into the plain
278
+ // Diagnostic struct with line/column populated via tsgo's ECMALineMap (the
279
+ // same helper tsc uses for its "file:line:col: message" banner).
280
+ func convertDiagnostics(in []*ast.Diagnostic) []Diagnostic {
281
+ out := make([]Diagnostic, 0, len(in))
282
+ for _, d := range in {
283
+ if d == nil {
284
+ continue
285
+ }
286
+ diag := Diagnostic{Message: d.String(), raw: d}
287
+ if file := d.File(); file != nil {
288
+ diag.File = file.FileName()
289
+ if pos := d.Pos(); pos >= 0 {
290
+ line, col := shimscanner.GetECMALineAndByteOffsetOfPosition(file, pos)
291
+ diag.Line = line + 1
292
+ diag.Column = col + 1
293
+ }
294
+ }
295
+ out = append(out, diag)
296
+ }
297
+ return out
298
+ }
@@ -0,0 +1,315 @@
1
+ // Package driver: post-emit rewriter.
2
+ //
3
+ // tsgo emits `.js` with plugin-owned call expressions preserved as-is because
4
+ // the compile-time transformer stage is now hosted outside the native
5
+ // compiler. This file implements the emit-time rewrite pattern pioneered by
6
+ // tsgonest: we hook tsgo's Emit() via its WriteFile callback, locate each
7
+ // previously-recognized plugin call in the emitted JS, and replace the call
8
+ // expression with the JS the native consumer produced.
9
+ //
10
+ // The rewriter operates on the output text only — it relies on the caller
11
+ // having already produced an ordered list of (file, call, emittedJS) triples.
12
+ // Today we match by textual pattern (`<alias>.<method>(...)`), which is safe
13
+ // because the compiler-stripped call site is distinctive.
14
+ package driver
15
+
16
+ import (
17
+ "context"
18
+ "errors"
19
+ "fmt"
20
+ "os"
21
+ "path/filepath"
22
+ "strings"
23
+ "unicode"
24
+
25
+ "github.com/microsoft/typescript-go/shim/ast"
26
+ shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
27
+ )
28
+
29
+ // Rewrite describes one emit-time patch. Produced by CollectCallSites after
30
+ // the engine has generated a replacement JS fragment for the call.
31
+ type Rewrite struct {
32
+ File *ast.SourceFile
33
+ RootName string
34
+ Namespaces []string
35
+ Method string
36
+ Replacement string
37
+ ConsumeParens bool
38
+ }
39
+
40
+ // RewriteSet groups rewrites by file, preserving source order.
41
+ type RewriteSet struct {
42
+ byPath map[string][]Rewrite
43
+ }
44
+
45
+ // NewRewriteSet returns an empty set.
46
+ func NewRewriteSet() *RewriteSet { return &RewriteSet{byPath: map[string][]Rewrite{}} }
47
+
48
+ // Add registers a rewrite under the absolute path of its source file.
49
+ func (rs *RewriteSet) Add(r Rewrite) {
50
+ if r.File == nil {
51
+ return
52
+ }
53
+ path := filepath.ToSlash(r.File.FileName())
54
+ rs.byPath[path] = append(rs.byPath[path], r)
55
+ }
56
+
57
+ // Len returns the total number of rewrites across every file.
58
+ func (rs *RewriteSet) Len() int {
59
+ n := 0
60
+ for _, rws := range rs.byPath {
61
+ n += len(rws)
62
+ }
63
+ return n
64
+ }
65
+
66
+ // RewriteSentinel is the marker inserted at the top of a patched file so
67
+ // re-running the emit on an already-rewritten file is a no-op.
68
+ const RewriteSentinel = "/* @ttsc-rewritten */"
69
+
70
+ // EmitAll runs tsgo's emitter, patching every registered plugin-owned call in
71
+ // the output. Returns the tsgo diagnostics and any patch-time error. When
72
+ // `writeFile` is nil, the patched JS is written to disk via the standard
73
+ // tsgo WriteFile.
74
+ func (p *Program) EmitAll(rs *RewriteSet, writeFile shimcompiler.WriteFile) (*shimcompiler.EmitResult, []Diagnostic, error) {
75
+ return p.emit(rs, nil, writeFile)
76
+ }
77
+
78
+ // EmitFile runs tsgo's emitter for one source file, applying the same rewrite
79
+ // pipeline as EmitAll.
80
+ func (p *Program) EmitFile(rs *RewriteSet, target *ast.SourceFile, writeFile shimcompiler.WriteFile) (*shimcompiler.EmitResult, []Diagnostic, error) {
81
+ return p.emit(rs, target, writeFile)
82
+ }
83
+
84
+ func (p *Program) emit(rs *RewriteSet, target *ast.SourceFile, writeFile shimcompiler.WriteFile) (*shimcompiler.EmitResult, []Diagnostic, error) {
85
+ if p == nil || p.TSProgram == nil {
86
+ return nil, nil, errors.New("driver: nil program")
87
+ }
88
+ if rs == nil {
89
+ rs = NewRewriteSet()
90
+ }
91
+ cursors := map[string]int{}
92
+ wf := func(fileName, text string, data *shimcompiler.WriteFileData) error {
93
+ if strings.Contains(text, RewriteSentinel) {
94
+ if writeFile != nil {
95
+ return writeFile(fileName, text, data)
96
+ }
97
+ return DefaultWriteFile(fileName, text)
98
+ }
99
+ patched, err := applyRewrites(fileName, text, rs, cursors)
100
+ if err != nil {
101
+ return err
102
+ }
103
+ if patched != text {
104
+ patched = insertSentinel(patched)
105
+ }
106
+ if writeFile != nil {
107
+ return writeFile(fileName, patched, data)
108
+ }
109
+ return DefaultWriteFile(fileName, patched)
110
+ }
111
+
112
+ result := p.TSProgram.Emit(context.Background(), shimcompiler.EmitOptions{
113
+ TargetSourceFile: target,
114
+ WriteFile: wf,
115
+ })
116
+ if result == nil {
117
+ return nil, nil, errors.New("driver: Emit returned nil")
118
+ }
119
+ return result, convertDiagnostics(result.Diagnostics), nil
120
+ }
121
+
122
+ // DefaultWriteFile is the default disk writer used when EmitAll's caller does not
123
+ // supply a custom WriteFile hook.
124
+ func DefaultWriteFile(fileName, text string) error {
125
+ if dir := filepath.Dir(fileName); dir != "" {
126
+ if err := os.MkdirAll(dir, 0o755); err != nil {
127
+ return err
128
+ }
129
+ }
130
+ return os.WriteFile(fileName, []byte(text), 0o644)
131
+ }
132
+
133
+ func insertSentinel(text string) string {
134
+ for _, prefix := range []string{"\"use strict\";\n", "'use strict';\n"} {
135
+ if strings.HasPrefix(text, prefix) {
136
+ return prefix + RewriteSentinel + "\n" + text[len(prefix):]
137
+ }
138
+ }
139
+ return RewriteSentinel + "\n" + text
140
+ }
141
+
142
+ func applyRewrites(outputName, text string, rs *RewriteSet, cursors map[string]int) (string, error) {
143
+ srcPath, ok := findSourceForOutput(outputName, rs)
144
+ if !ok || len(rs.byPath[srcPath]) == 0 {
145
+ return text, nil
146
+ }
147
+ rewrites := rs.byPath[srcPath]
148
+ pos := cursors[srcPath]
149
+ out := text
150
+ searchFrom := 0
151
+ for pos < len(rewrites) {
152
+ r := rewrites[pos]
153
+ replaced, nextSearchFrom, ok, err := spliceCall(out, r, searchFrom)
154
+ if err != nil {
155
+ return "", err
156
+ }
157
+ if !ok {
158
+ preview := out
159
+ if len(preview) > 400 {
160
+ preview = preview[:400] + "…"
161
+ }
162
+ return "", fmt.Errorf("driver: could not locate %s.%s(…) call in %s (tried roots %v; preview: %q)", joinRootAndNamespaces(r), r.Method, outputName, candidateRoots(r.RootName), preview)
163
+ }
164
+ out = replaced
165
+ searchFrom = nextSearchFrom
166
+ pos++
167
+ }
168
+ cursors[srcPath] = pos
169
+ return out, nil
170
+ }
171
+
172
+ func findSourceForOutput(outputName string, rs *RewriteSet) (string, bool) {
173
+ outSlash := strings.TrimSuffix(filepath.ToSlash(outputName), filepath.Ext(outputName))
174
+ var best string
175
+ bestScore := 0
176
+ for path := range rs.byPath {
177
+ srcStem := strings.TrimSuffix(filepath.ToSlash(path), filepath.Ext(path))
178
+ score := commonSuffixSegments(srcStem, outSlash)
179
+ if score > bestScore {
180
+ best = path
181
+ bestScore = score
182
+ }
183
+ }
184
+ return best, bestScore > 0
185
+ }
186
+
187
+ func commonSuffixSegments(a, b string) int {
188
+ as := strings.Split(a, "/")
189
+ bs := strings.Split(b, "/")
190
+ n := len(as)
191
+ if len(bs) < n {
192
+ n = len(bs)
193
+ }
194
+ shared := 0
195
+ for i := 1; i <= n; i++ {
196
+ if as[len(as)-i] != bs[len(bs)-i] {
197
+ break
198
+ }
199
+ shared++
200
+ }
201
+ return shared
202
+ }
203
+
204
+ func spliceCall(text string, r Rewrite, searchFrom int) (string, int, bool, error) {
205
+ aliases := candidateRoots(r.RootName)
206
+ tail := needleTail(r)
207
+ idx := -1
208
+ needleLen := 0
209
+ for _, cand := range aliases {
210
+ candNeedle := cand + tail
211
+ if i := indexAtCallStart(text, candNeedle, searchFrom); i >= 0 {
212
+ idx = i
213
+ needleLen = len(candNeedle) - 1
214
+ break
215
+ }
216
+ }
217
+ if idx < 0 {
218
+ return text, searchFrom, false, nil
219
+ }
220
+ parenPos := idx + needleLen
221
+ if parenPos >= len(text) || text[parenPos] != '(' {
222
+ return text, searchFrom, false, errors.New("driver: expected '(' after method name")
223
+ }
224
+ closePos, ok := matchParen(text, parenPos)
225
+ if !ok {
226
+ return text, searchFrom, false, errors.New("driver: unbalanced parens while locating plugin call")
227
+ }
228
+ if r.ConsumeParens {
229
+ replaced := text[:idx] + r.Replacement + text[closePos+1:]
230
+ return replaced, idx + len(r.Replacement), true, nil
231
+ }
232
+ replaced := text[:idx] + r.Replacement + text[idx+needleLen:]
233
+ return replaced, idx + len(r.Replacement), true, nil
234
+ }
235
+
236
+ func candidateRoots(root string) []string {
237
+ return []string{
238
+ root,
239
+ root + "_1.default",
240
+ root + "_2.default",
241
+ root + ".default",
242
+ root + "_1",
243
+ root + "_2",
244
+ }
245
+ }
246
+
247
+ func joinRootAndNamespaces(r Rewrite) string {
248
+ if len(r.Namespaces) == 0 {
249
+ return r.RootName
250
+ }
251
+ return r.RootName + "." + strings.Join(r.Namespaces, ".")
252
+ }
253
+
254
+ func needleTail(r Rewrite) string {
255
+ if len(r.Namespaces) == 0 {
256
+ return "." + r.Method + "("
257
+ }
258
+ return "." + strings.Join(r.Namespaces, ".") + "." + r.Method + "("
259
+ }
260
+
261
+ func indexAtCallStart(text, needle string, searchFrom int) int {
262
+ start := searchFrom
263
+ if start < 0 {
264
+ start = 0
265
+ }
266
+ for {
267
+ hit := strings.Index(text[start:], needle)
268
+ if hit < 0 {
269
+ return -1
270
+ }
271
+ pos := start + hit
272
+ if pos == 0 {
273
+ return pos
274
+ }
275
+ prev := rune(text[pos-1])
276
+ if isIdentifierPart(prev) {
277
+ start = pos + 1
278
+ continue
279
+ }
280
+ return pos
281
+ }
282
+ }
283
+
284
+ func isIdentifierPart(r rune) bool {
285
+ return r == '_' || r == '$' || unicode.IsLetter(r) || unicode.IsDigit(r)
286
+ }
287
+
288
+ func matchParen(text string, pos int) (int, bool) {
289
+ if pos >= len(text) || text[pos] != '(' {
290
+ return 0, false
291
+ }
292
+ depth := 1
293
+ for i := pos + 1; i < len(text); i++ {
294
+ switch text[i] {
295
+ case '(':
296
+ depth++
297
+ case ')':
298
+ depth--
299
+ if depth == 0 {
300
+ return i, true
301
+ }
302
+ case '"', '\'', '`':
303
+ q := text[i]
304
+ j := i + 1
305
+ for j < len(text) && text[j] != q {
306
+ if text[j] == '\\' {
307
+ j++
308
+ }
309
+ j++
310
+ }
311
+ i = j
312
+ }
313
+ }
314
+ return 0, false
315
+ }
package/go.mod ADDED
@@ -0,0 +1,48 @@
1
+ module github.com/samchon/ttsc/packages/ttsc
2
+
3
+ go 1.26
4
+
5
+ // Every shim sub-module is wired up via the sibling go.work file together
6
+ // with the local typescript-go checkout under ../../third_party/typescript-go.
7
+ // Tagged upstream versions can replace these local wires when the shim API
8
+ // stabilizes.
9
+ replace (
10
+ github.com/microsoft/typescript-go/shim/ast => ./shim/ast
11
+ github.com/microsoft/typescript-go/shim/bundled => ./shim/bundled
12
+ github.com/microsoft/typescript-go/shim/checker => ./shim/checker
13
+ github.com/microsoft/typescript-go/shim/compiler => ./shim/compiler
14
+ github.com/microsoft/typescript-go/shim/core => ./shim/core
15
+ github.com/microsoft/typescript-go/shim/diagnosticwriter => ./shim/diagnosticwriter
16
+ github.com/microsoft/typescript-go/shim/parser => ./shim/parser
17
+ github.com/microsoft/typescript-go/shim/scanner => ./shim/scanner
18
+ github.com/microsoft/typescript-go/shim/tsoptions => ./shim/tsoptions
19
+ github.com/microsoft/typescript-go/shim/tspath => ./shim/tspath
20
+ github.com/microsoft/typescript-go/shim/vfs => ./shim/vfs
21
+ github.com/microsoft/typescript-go/shim/vfs/cachedvfs => ./shim/vfs/cachedvfs
22
+ github.com/microsoft/typescript-go/shim/vfs/osvfs => ./shim/vfs/osvfs
23
+ )
24
+
25
+ require (
26
+ github.com/microsoft/typescript-go/shim/ast v0.0.0
27
+ github.com/microsoft/typescript-go/shim/bundled v0.0.0
28
+ github.com/microsoft/typescript-go/shim/checker v0.0.0
29
+ github.com/microsoft/typescript-go/shim/compiler v0.0.0
30
+ github.com/microsoft/typescript-go/shim/core v0.0.0
31
+ github.com/microsoft/typescript-go/shim/diagnosticwriter v0.0.0
32
+ github.com/microsoft/typescript-go/shim/scanner v0.0.0
33
+ github.com/microsoft/typescript-go/shim/tsoptions v0.0.0
34
+ github.com/microsoft/typescript-go/shim/tspath v0.0.0
35
+ github.com/microsoft/typescript-go/shim/vfs v0.0.0
36
+ github.com/microsoft/typescript-go/shim/vfs/cachedvfs v0.0.0
37
+ github.com/microsoft/typescript-go/shim/vfs/osvfs v0.0.0
38
+ )
39
+
40
+ require (
41
+ github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 // indirect
42
+ github.com/klauspost/cpuid/v2 v2.2.10 // indirect
43
+ github.com/microsoft/typescript-go v0.0.0-20260408193441-2a5e1cf9fe22 // indirect
44
+ github.com/zeebo/xxh3 v1.1.0 // indirect
45
+ golang.org/x/sync v0.20.0 // indirect
46
+ golang.org/x/sys v0.42.0 // indirect
47
+ golang.org/x/text v0.35.0 // indirect
48
+ )
package/go.sum ADDED
@@ -0,0 +1,22 @@
1
+ github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=
2
+ github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
3
+ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
4
+ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
5
+ github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
6
+ github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
7
+ github.com/microsoft/typescript-go v0.0.0-20260408193441-2a5e1cf9fe22 h1:iH0TuqCh+yQ95Ea0f9lVM5B1xkTqlmxQhzOEj8QWR6o=
8
+ github.com/microsoft/typescript-go v0.0.0-20260408193441-2a5e1cf9fe22/go.mod h1:eE2mO7PBRpeR8gWgxwsZq1LOW4tsgIF6zd0iQ5TbsJU=
9
+ github.com/peter-evans/patience v0.3.0 h1:rX0JdJeepqdQl1Sk9c9uvorjYYzL2TfgLX1adqYm9cA=
10
+ github.com/peter-evans/patience v0.3.0/go.mod h1:Kmxu5sY1NmBLFSStvXjX1wS9mIv7wMcP/ubucyMOAu0=
11
+ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
12
+ github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
13
+ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
14
+ github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
15
+ golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
16
+ golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
17
+ golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
18
+ golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
19
+ golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
20
+ golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
21
+ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
22
+ gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=