ttsc 0.3.1 → 0.4.0-dev.20260425
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +93 -362
- package/cmd/ttsc/build.go +128 -0
- package/cmd/ttsc/main.go +222 -0
- package/cmd/ttsc/main_test.go +288 -0
- package/cmd/ttsc/transform.go +166 -0
- package/driver/host.go +31 -0
- package/driver/program.go +298 -0
- package/driver/rewrite.go +315 -0
- package/go.mod +48 -0
- package/go.sum +22 -0
- package/lib/api.d.ts +91 -0
- package/lib/api.js +325 -0
- package/lib/api.js.map +1 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +481 -0
- package/lib/cli.js.map +1 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +35 -0
- package/lib/index.js.map +1 -0
- package/lib/native.d.ts +15 -0
- package/lib/native.js +34 -0
- package/lib/native.js.map +1 -0
- package/lib/platform.d.ts +66 -0
- package/lib/platform.js +170 -0
- package/lib/platform.js.map +1 -0
- package/lib/plugin.d.ts +47 -0
- package/lib/plugin.js +150 -0
- package/lib/plugin.js.map +1 -0
- package/lib/project.d.ts +32 -0
- package/lib/project.js +303 -0
- package/lib/project.js.map +1 -0
- package/lib/runner/cli.d.ts +1 -0
- package/lib/runner/cli.js +284 -0
- package/lib/runner/cli.js.map +1 -0
- package/lib/runner/register.d.ts +13 -0
- package/lib/runner/register.js +424 -0
- package/lib/runner/register.js.map +1 -0
- package/package.json +72 -63
- package/shim/ast/extra-shim.json +4 -0
- package/shim/ast/go.mod +5 -0
- package/shim/ast/shim.go +117 -0
- package/shim/bundled/extra-shim.json +1 -0
- package/shim/bundled/go.mod +5 -0
- package/shim/bundled/shim.go +19 -0
- package/shim/checker/extra-shim.json +31 -0
- package/shim/checker/go.mod +5 -0
- package/shim/checker/shim.go +146 -0
- package/shim/compiler/extra-shim.json +4 -0
- package/shim/compiler/go.mod +5 -0
- package/shim/compiler/shim.go +50 -0
- package/shim/core/extra-shim.json +4 -0
- package/shim/core/go.mod +5 -0
- package/shim/core/shim.go +11 -0
- package/shim/diagnosticwriter/go.mod +5 -0
- package/shim/diagnosticwriter/shim.go +29 -0
- package/shim/parser/extra-shim.json +4 -0
- package/shim/parser/go.mod +5 -0
- package/shim/parser/shim.go +55 -0
- package/shim/scanner/extra-shim.json +4 -0
- package/shim/scanner/go.mod +5 -0
- package/shim/scanner/shim.go +96 -0
- package/shim/tsoptions/extra-shim.json +4 -0
- package/shim/tsoptions/go.mod +5 -0
- package/shim/tsoptions/shim.go +123 -0
- package/shim/tspath/extra-shim.json +4 -0
- package/shim/tspath/go.mod +5 -0
- package/shim/tspath/shim.go +165 -0
- package/shim/vfs/cachedvfs/extra-shim.json +4 -0
- package/shim/vfs/cachedvfs/go.mod +5 -0
- package/shim/vfs/cachedvfs/shim.go +12 -0
- package/shim/vfs/extra-shim.json +4 -0
- package/shim/vfs/go.mod +5 -0
- package/shim/vfs/osvfs/extra-shim.json +4 -0
- package/shim/vfs/osvfs/go.mod +5 -0
- package/shim/vfs/osvfs/shim.go +13 -0
- package/shim/vfs/shim.go +19 -0
- package/src/api.ts +416 -0
- package/src/cli.ts +492 -0
- package/src/index.ts +19 -0
- package/src/launcher/ttsc-dev.js +42 -0
- package/src/launcher/ttsc.js +19 -0
- package/src/launcher/ttsx.js +19 -0
- package/src/native.ts +60 -0
- package/src/platform.ts +163 -0
- package/src/plugin.ts +241 -0
- package/src/project.ts +330 -0
- package/src/runner/cli.ts +286 -0
- package/src/runner/register.ts +487 -0
- package/test/native.test.ts +60 -0
- package/test/package.test.ts +131 -0
- package/test/platform.test.ts +58 -0
- package/test/project.test.ts +195 -0
- package/tools/gen_shims/go.mod +14 -0
- package/tools/gen_shims/go.sum +12 -0
- package/tools/gen_shims/main.go +446 -0
- package/CHANGELOG.md +0 -2
- package/bin/tsc +0 -2
- package/bin/tsserver +0 -2
- package/lib/PluginCreator.d.ts +0 -67
- package/lib/PluginCreator.js +0 -190
- package/lib/cancelationToken.d.ts +0 -0
- package/lib/cancelationToken.js +0 -2
- package/lib/loadTypescript.d.ts +0 -7
- package/lib/loadTypescript.js +0 -56
- package/lib/patchCreateProgram.d.ts +0 -21
- package/lib/patchCreateProgram.js +0 -190
- package/lib/tsc.d.ts +0 -1
- package/lib/tsc.js +0 -24
- package/lib/tsserver.d.ts +0 -0
- package/lib/tsserver.js +0 -2
- package/lib/tsserverlibrary.d.ts +0 -3
- package/lib/tsserverlibrary.js +0 -4
- package/lib/typescript.d.ts +0 -3
- package/lib/typescript.js +0 -4
- package/lib/typescriptServices.d.ts +0 -3
- package/lib/typescriptServices.js +0 -4
- package/lib/typingsInstaller.d.ts +0 -0
- package/lib/typingsInstaller.js +0 -2
- package/lib/watchGuard.d.ts +0 -0
- package/lib/watchGuard.js +0 -2
|
@@ -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=
|