ttsc 0.11.0 → 0.12.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/README.md +25 -6
- package/cmd/ttsc/api_compile.go +101 -106
- package/cmd/ttsc/api_compile_branches_test.go +81 -0
- package/cmd/ttsc/api_transform.go +49 -54
- package/cmd/ttsc/api_transform_branches_test.go +41 -0
- package/cmd/ttsc/build.go +108 -109
- package/cmd/ttsc/build_branches_test.go +130 -0
- package/cmd/ttsc/helpers_test.go +47 -0
- package/cmd/ttsc/main.go +104 -103
- package/cmd/ttsc/main_alias_branches_test.go +29 -0
- package/cmd/ttscserver/main.go +104 -96
- package/cmd/ttscserver/run_lsp_prefers_tsgo_flag_test.go +42 -0
- package/cmd/ttscserver/run_lsp_reports_server_error_test.go +23 -25
- package/cmd/ttscserver/run_lsp_uses_tsgo_environment_test.go +44 -0
- package/cmd/utility-host/main.go +42 -0
- package/driver/lsp.go +60 -0
- package/driver/plugins.go +122 -0
- package/driver/program.go +346 -356
- package/driver/rewrite.go +336 -339
- package/{driver → internal/lspserver}/lsp_envelope.go +70 -64
- package/{driver → internal/lspserver}/lsp_frame.go +62 -62
- package/{driver → internal/lspserver}/lsp_plugin.go +58 -58
- package/internal/lspserver/lsp_proxy.go +391 -0
- package/internal/lspserver/lsp_server.go +231 -0
- package/lib/TtscCompiler.d.ts +3 -3
- package/lib/TtscCompiler.js +20 -6
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/resolveTsgo.d.ts +1 -0
- package/lib/compiler/internal/resolveTsgo.js +15 -4
- package/lib/compiler/internal/resolveTsgo.js.map +1 -1
- package/lib/compiler/internal/runBuild.js +12 -4
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +12 -33
- package/lib/compiler/internal/sharedHostHelpers.js +27 -70
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +12 -5
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +9 -4
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.d.ts +1 -0
- package/lib/launcher/internal/runTtscserver.js +38 -1
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.d.ts +3 -0
- package/lib/plugin/internal/buildSourcePlugin.js +444 -41
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.js +153 -34
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerContext.d.ts +2 -1
- package/lib/structures/ITtscPlugin.d.ts +8 -13
- package/lib/structures/ITtscPluginFactoryContext.d.ts +4 -5
- package/lib/structures/ITtscProjectPluginConfig.d.ts +2 -2
- package/lib/structures/internal/ITtscLoadedNativePlugin.d.ts +4 -2
- package/package.json +8 -8
- package/shim/lsp/shim.go +11 -17
- package/src/TtscCompiler.ts +20 -7
- package/src/compiler/internal/resolveTsgo.ts +18 -5
- package/src/compiler/internal/runBuild.ts +17 -4
- package/src/compiler/internal/sharedHostHelpers.ts +34 -67
- package/src/compiler/internal/transformProjectInMemory.ts +18 -5
- package/src/index.ts +0 -1
- package/src/launcher/internal/runTtsc.ts +9 -4
- package/src/launcher/internal/runTtscserver.ts +43 -1
- package/src/plugin/internal/buildSourcePlugin.ts +475 -41
- package/src/plugin/internal/loadProjectPlugins.ts +199 -39
- package/src/structures/ITtscCompilerContext.ts +2 -1
- package/src/structures/ITtscPlugin.ts +9 -13
- package/src/structures/ITtscPluginFactoryContext.ts +4 -5
- package/src/structures/ITtscProjectPluginConfig.ts +2 -2
- package/src/structures/internal/ITtscLoadedNativePlugin.ts +4 -2
- package/utility/host.go +267 -1296
- package/driver/lsp_proxy.go +0 -394
- package/driver/lsp_server.go +0 -197
- package/lib/defineTtscPlugin.d.ts +0 -18
- package/lib/defineTtscPlugin.js +0 -7
- package/lib/defineTtscPlugin.js.map +0 -1
- package/src/defineTtscPlugin.ts +0 -26
package/utility/host.go
CHANGED
|
@@ -1,1337 +1,308 @@
|
|
|
1
1
|
package utility
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"sort"
|
|
11
|
-
"strings"
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"flag"
|
|
6
|
+
"fmt"
|
|
7
|
+
"os"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
"strings"
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
shimcore "github.com/microsoft/typescript-go/shim/core"
|
|
16
|
-
shimprinter "github.com/microsoft/typescript-go/shim/printer"
|
|
11
|
+
shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
|
|
12
|
+
shimprinter "github.com/microsoft/typescript-go/shim/printer"
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
19
15
|
)
|
|
20
16
|
|
|
21
|
-
type pluginEntry struct {
|
|
22
|
-
Config map[string]any `json:"config"`
|
|
23
|
-
Name string `json:"name"`
|
|
24
|
-
Stage string `json:"stage"`
|
|
25
|
-
}
|
|
26
|
-
|
|
27
17
|
type hostOptions struct {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
type transformState struct {
|
|
39
|
-
paths *pathsRewriter
|
|
40
|
-
strip *stripRewriter
|
|
18
|
+
cwd string
|
|
19
|
+
emit bool
|
|
20
|
+
noEmit bool
|
|
21
|
+
outDir string
|
|
22
|
+
pluginsJSON string
|
|
23
|
+
quiet bool
|
|
24
|
+
tsconfig string
|
|
25
|
+
verbose bool
|
|
41
26
|
}
|
|
42
27
|
|
|
43
28
|
type transformResult struct {
|
|
44
|
-
|
|
45
|
-
|
|
29
|
+
Diagnostics []any `json:"diagnostics,omitempty"`
|
|
30
|
+
TypeScript map[string]string `json:"typescript"`
|
|
46
31
|
}
|
|
47
32
|
|
|
48
|
-
// RunCheck validates the project and
|
|
49
|
-
//
|
|
33
|
+
// RunCheck validates the project and linked plugin configuration without
|
|
34
|
+
// emitting output.
|
|
50
35
|
func RunCheck(args []string) int {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
36
|
+
opts, ok := parseHostOptions("check", args)
|
|
37
|
+
if !ok {
|
|
38
|
+
return 2
|
|
39
|
+
}
|
|
40
|
+
opts.noEmit = true
|
|
41
|
+
prog, _, ok := loadUtilityProgram(opts)
|
|
42
|
+
if !ok {
|
|
43
|
+
return 2
|
|
44
|
+
}
|
|
45
|
+
defer prog.Close()
|
|
46
|
+
if err := prog.ApplyLinkedPlugins(); err != nil {
|
|
47
|
+
fmt.Fprintln(os.Stderr, err)
|
|
48
|
+
return 2
|
|
49
|
+
}
|
|
50
|
+
return 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// RunBuild hosts linked transform packages inside one compiler emit.
|
|
65
54
|
func RunBuild(args []string) int {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return 0
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// RunTransform returns the project TypeScript text after source mutations.
|
|
55
|
+
opts, ok := parseHostOptions("build", args)
|
|
56
|
+
if !ok {
|
|
57
|
+
return 2
|
|
58
|
+
}
|
|
59
|
+
prog, entries, ok := loadUtilityProgram(opts)
|
|
60
|
+
if !ok {
|
|
61
|
+
return 2
|
|
62
|
+
}
|
|
63
|
+
defer prog.Close()
|
|
64
|
+
if opts.noEmit {
|
|
65
|
+
return 0
|
|
66
|
+
}
|
|
67
|
+
if opts.verbose {
|
|
68
|
+
opts.quiet = false
|
|
69
|
+
}
|
|
70
|
+
if !opts.quiet {
|
|
71
|
+
fmt.Fprintf(os.Stdout, "// ttsc utility: plugins=%d emit=%v\n", len(entries), !opts.noEmit)
|
|
72
|
+
}
|
|
73
|
+
res, eDiags, err := prog.EmitAllRaw(makeSourcePreambleWriteFile(prog))
|
|
74
|
+
if err != nil {
|
|
75
|
+
fmt.Fprintf(os.Stderr, "ttsc utility: emit failed: %v\n", err)
|
|
76
|
+
return 3
|
|
77
|
+
}
|
|
78
|
+
for _, d := range eDiags {
|
|
79
|
+
fmt.Fprintln(os.Stderr, " -", d.String())
|
|
80
|
+
}
|
|
81
|
+
if driver.CountErrors(eDiags) > 0 {
|
|
82
|
+
return 2
|
|
83
|
+
}
|
|
84
|
+
if res != nil && !opts.quiet {
|
|
85
|
+
fmt.Fprintf(os.Stdout, "// ttsc utility: emitted=%d files\n", len(res.EmittedFiles))
|
|
86
|
+
}
|
|
87
|
+
return 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// RunTransform returns the project TypeScript text after linked source
|
|
91
|
+
// mutations.
|
|
107
92
|
func RunTransform(args []string) int {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
fmt.Fprintln(os.Stdout, string(data))
|
|
132
|
-
return 0
|
|
93
|
+
opts, ok := parseHostOptions("transform", args)
|
|
94
|
+
if !ok {
|
|
95
|
+
return 2
|
|
96
|
+
}
|
|
97
|
+
prog, _, ok := loadUtilityProgram(opts)
|
|
98
|
+
if !ok {
|
|
99
|
+
return 2
|
|
100
|
+
}
|
|
101
|
+
defer prog.Close()
|
|
102
|
+
if err := prog.ApplyLinkedPlugins(); err != nil {
|
|
103
|
+
fmt.Fprintln(os.Stderr, err)
|
|
104
|
+
return 2
|
|
105
|
+
}
|
|
106
|
+
printer := shimprinter.NewPrinter(shimprinter.PrinterOptions{}, shimprinter.PrintHandlers{}, nil)
|
|
107
|
+
out := transformResult{TypeScript: map[string]string{}}
|
|
108
|
+
for _, file := range prog.SourceFiles() {
|
|
109
|
+
text := shimprinter.EmitSourceFile(printer, file)
|
|
110
|
+
out.TypeScript[apiOutputKey(opts.cwd, file.FileName())] = text
|
|
111
|
+
}
|
|
112
|
+
data, _ := json.Marshal(out)
|
|
113
|
+
fmt.Fprintln(os.Stdout, string(data))
|
|
114
|
+
return 0
|
|
133
115
|
}
|
|
134
116
|
|
|
135
117
|
func parseHostOptions(command string, args []string) (hostOptions, bool) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
118
|
+
fs := flag.NewFlagSet(command, flag.ContinueOnError)
|
|
119
|
+
fs.SetOutput(os.Stderr)
|
|
120
|
+
cwd := fs.String("cwd", "", "project directory")
|
|
121
|
+
emit := fs.Bool("emit", false, "force emit")
|
|
122
|
+
noEmit := fs.Bool("noEmit", false, "force no emit")
|
|
123
|
+
outDir := fs.String("outDir", "", "emit directory override")
|
|
124
|
+
pluginsJSON := fs.String("plugins-json", "", "ttsc plugin manifest JSON")
|
|
125
|
+
quiet := fs.Bool("quiet", true, "suppress summary")
|
|
126
|
+
tsconfig := fs.String("tsconfig", "tsconfig.json", "project tsconfig")
|
|
127
|
+
verbose := fs.Bool("verbose", false, "print summary")
|
|
128
|
+
if err := fs.Parse(filterHostArgs(args)); err != nil {
|
|
129
|
+
return hostOptions{}, false
|
|
130
|
+
}
|
|
131
|
+
if *emit && *noEmit {
|
|
132
|
+
fmt.Fprintln(os.Stderr, "ttsc utility: --emit and --noEmit are mutually exclusive")
|
|
133
|
+
return hostOptions{}, false
|
|
134
|
+
}
|
|
135
|
+
resolvedCwd := *cwd
|
|
136
|
+
if resolvedCwd == "" {
|
|
137
|
+
var err error
|
|
138
|
+
resolvedCwd, err = os.Getwd()
|
|
139
|
+
if err != nil {
|
|
140
|
+
fmt.Fprintf(os.Stderr, "ttsc utility: cwd: %v\n", err)
|
|
141
|
+
return hostOptions{}, false
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if !filepath.IsAbs(resolvedCwd) {
|
|
145
|
+
abs, err := filepath.Abs(resolvedCwd)
|
|
146
|
+
if err != nil {
|
|
147
|
+
fmt.Fprintf(os.Stderr, "ttsc utility: cwd: %v\n", err)
|
|
148
|
+
return hostOptions{}, false
|
|
149
|
+
}
|
|
150
|
+
resolvedCwd = abs
|
|
151
|
+
}
|
|
152
|
+
return hostOptions{
|
|
153
|
+
cwd: filepath.Clean(resolvedCwd),
|
|
154
|
+
emit: *emit,
|
|
155
|
+
noEmit: *noEmit,
|
|
156
|
+
outDir: *outDir,
|
|
157
|
+
pluginsJSON: *pluginsJSON,
|
|
158
|
+
quiet: *quiet,
|
|
159
|
+
tsconfig: *tsconfig,
|
|
160
|
+
verbose: *verbose,
|
|
161
|
+
}, true
|
|
180
162
|
}
|
|
181
163
|
|
|
182
164
|
func filterHostArgs(args []string) []string {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
165
|
+
known := map[string]bool{
|
|
166
|
+
"cwd": true,
|
|
167
|
+
"emit": false,
|
|
168
|
+
"noEmit": false,
|
|
169
|
+
"outDir": true,
|
|
170
|
+
"plugins-json": true,
|
|
171
|
+
"quiet": false,
|
|
172
|
+
"tsconfig": true,
|
|
173
|
+
"verbose": false,
|
|
174
|
+
}
|
|
175
|
+
filtered := make([]string, 0, len(args))
|
|
176
|
+
for i := 0; i < len(args); i++ {
|
|
177
|
+
current := args[i]
|
|
178
|
+
if current == "--" {
|
|
179
|
+
break
|
|
180
|
+
}
|
|
181
|
+
if !strings.HasPrefix(current, "--") {
|
|
182
|
+
filtered = append(filtered, current)
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
185
|
+
name, hasInlineValue := flagName(current)
|
|
186
|
+
takesValue, ok := known[name]
|
|
187
|
+
if ok {
|
|
188
|
+
filtered = append(filtered, current)
|
|
189
|
+
if takesValue && !hasInlineValue && i+1 < len(args) {
|
|
190
|
+
i++
|
|
191
|
+
filtered = append(filtered, args[i])
|
|
192
|
+
}
|
|
193
|
+
continue
|
|
194
|
+
}
|
|
195
|
+
if !hasInlineValue && i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
|
|
196
|
+
i++
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return filtered
|
|
218
200
|
}
|
|
219
201
|
|
|
220
202
|
func flagName(arg string) (string, bool) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
func loadUtilityProgram(opts hostOptions) (*driver.Program, []
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return state, err
|
|
294
|
-
}
|
|
295
|
-
state.strip = strip
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return state, nil
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
func prepareSourcePreamble(entries []pluginEntry, cwd, tsconfigPath string) (string, error) {
|
|
302
|
-
var preamble strings.Builder
|
|
303
|
-
for _, entry := range entries {
|
|
304
|
-
if entry.Name != "@ttsc/banner" {
|
|
305
|
-
continue
|
|
306
|
-
}
|
|
307
|
-
banner, err := parseBanner(entry.Config, cwd, tsconfigPath)
|
|
308
|
-
if err != nil {
|
|
309
|
-
return "", err
|
|
310
|
-
}
|
|
311
|
-
preamble.WriteString(banner)
|
|
312
|
-
}
|
|
313
|
-
return preamble.String(), nil
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
func makeSourcePreambleWriteFile(prog *driver.Program, sourcePreamble string) shimcompiler.WriteFile {
|
|
317
|
-
if sourcePreamble == "" || shouldRemoveComments(prog) {
|
|
318
|
-
return nil
|
|
319
|
-
}
|
|
320
|
-
return func(fileName, text string, _ *shimcompiler.WriteFileData) error {
|
|
321
|
-
if shouldEnsureSourcePreamble(fileName, text, sourcePreamble) {
|
|
322
|
-
text = driver.ApplySourcePreamble(text, sourcePreamble)
|
|
323
|
-
}
|
|
324
|
-
return driver.DefaultWriteFile(fileName, text)
|
|
325
|
-
}
|
|
203
|
+
name := strings.TrimPrefix(arg, "--")
|
|
204
|
+
before, _, found := strings.Cut(name, "=")
|
|
205
|
+
return before, found
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
func loadUtilityProgram(opts hostOptions) (*driver.Program, []driver.PluginEntry, bool) {
|
|
209
|
+
entries, err := parsePluginEntries(opts.pluginsJSON)
|
|
210
|
+
if err != nil {
|
|
211
|
+
fmt.Fprintln(os.Stderr, err)
|
|
212
|
+
return nil, nil, false
|
|
213
|
+
}
|
|
214
|
+
restoreEnv := setLinkedPluginManifest(opts.pluginsJSON)
|
|
215
|
+
defer restoreEnv()
|
|
216
|
+
|
|
217
|
+
prog, diags, err := driver.LoadProgram(opts.cwd, opts.tsconfig, driver.LoadProgramOptions{
|
|
218
|
+
ForceEmit: opts.emit,
|
|
219
|
+
ForceNoEmit: opts.noEmit,
|
|
220
|
+
OutDir: opts.outDir,
|
|
221
|
+
})
|
|
222
|
+
if err != nil {
|
|
223
|
+
fmt.Fprintf(os.Stderr, "ttsc utility: %v\n", err)
|
|
224
|
+
return nil, nil, false
|
|
225
|
+
}
|
|
226
|
+
if len(diags) > 0 {
|
|
227
|
+
driver.WritePrettyDiagnostics(os.Stderr, diags, opts.cwd)
|
|
228
|
+
return nil, nil, false
|
|
229
|
+
}
|
|
230
|
+
if diags := prog.Diagnostics(); len(diags) > 0 {
|
|
231
|
+
driver.WritePrettyDiagnostics(os.Stderr, diags, opts.cwd)
|
|
232
|
+
_ = prog.Close()
|
|
233
|
+
return nil, nil, false
|
|
234
|
+
}
|
|
235
|
+
return prog, entries, true
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
func parsePluginEntries(input string) ([]driver.PluginEntry, error) {
|
|
239
|
+
if strings.TrimSpace(input) == "" {
|
|
240
|
+
return nil, nil
|
|
241
|
+
}
|
|
242
|
+
var entries []driver.PluginEntry
|
|
243
|
+
if err := json.Unmarshal([]byte(input), &entries); err != nil {
|
|
244
|
+
return nil, fmt.Errorf("ttsc utility: invalid --plugins-json: %w", err)
|
|
245
|
+
}
|
|
246
|
+
return entries, nil
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
func setLinkedPluginManifest(input string) func() {
|
|
250
|
+
previous, existed := os.LookupEnv(driver.LinkedPluginsEnv)
|
|
251
|
+
if strings.TrimSpace(input) == "" {
|
|
252
|
+
_ = os.Unsetenv(driver.LinkedPluginsEnv)
|
|
253
|
+
} else {
|
|
254
|
+
_ = os.Setenv(driver.LinkedPluginsEnv, input)
|
|
255
|
+
}
|
|
256
|
+
return func() {
|
|
257
|
+
if existed {
|
|
258
|
+
_ = os.Setenv(driver.LinkedPluginsEnv, previous)
|
|
259
|
+
} else {
|
|
260
|
+
_ = os.Unsetenv(driver.LinkedPluginsEnv)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
func makeSourcePreambleWriteFile(prog *driver.Program) shimcompiler.WriteFile {
|
|
266
|
+
if prog == nil || prog.SourcePreamble == "" || shouldRemoveComments(prog) {
|
|
267
|
+
return nil
|
|
268
|
+
}
|
|
269
|
+
return func(fileName, text string, _ *shimcompiler.WriteFileData) error {
|
|
270
|
+
if shouldEnsureSourcePreamble(fileName, text, prog.SourcePreamble) {
|
|
271
|
+
text = driver.ApplySourcePreamble(text, prog.SourcePreamble)
|
|
272
|
+
}
|
|
273
|
+
return driver.DefaultWriteFile(fileName, text)
|
|
274
|
+
}
|
|
326
275
|
}
|
|
327
276
|
|
|
328
277
|
func shouldRemoveComments(prog *driver.Program) bool {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
278
|
+
if prog == nil || prog.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig.CompilerOptions == nil {
|
|
279
|
+
return false
|
|
280
|
+
}
|
|
281
|
+
return prog.ParsedConfig.ParsedConfig.CompilerOptions.RemoveComments.IsTrue()
|
|
333
282
|
}
|
|
334
283
|
|
|
335
284
|
func shouldEnsureSourcePreamble(fileName, text, sourcePreamble string) bool {
|
|
336
|
-
|
|
285
|
+
return isSourcePreambleOutputTarget(fileName) && !strings.Contains(text, sourcePreamble)
|
|
337
286
|
}
|
|
338
287
|
|
|
339
288
|
func isSourcePreambleOutputTarget(fileName string) bool {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
func applySourceTransforms(prog *driver.Program, state transformState) error {
|
|
350
|
-
for _, file := range prog.SourceFiles() {
|
|
351
|
-
if state.paths != nil {
|
|
352
|
-
state.paths.apply(file)
|
|
353
|
-
}
|
|
354
|
-
if state.strip != nil {
|
|
355
|
-
state.strip.apply(file)
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return nil
|
|
289
|
+
lower := strings.ToLower(filepath.ToSlash(fileName))
|
|
290
|
+
for _, suffix := range []string{".d.ts", ".d.mts", ".d.cts", ".js", ".jsx", ".mjs", ".cjs"} {
|
|
291
|
+
if strings.HasSuffix(lower, suffix) {
|
|
292
|
+
return true
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return false
|
|
359
296
|
}
|
|
360
297
|
|
|
361
298
|
func apiOutputKey(cwd, fileName string) string {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
299
|
+
rel, err := filepath.Rel(cwd, fileName)
|
|
300
|
+
if err != nil || isOutsideRelativePath(rel) {
|
|
301
|
+
return filepath.ToSlash(fileName)
|
|
302
|
+
}
|
|
303
|
+
return filepath.ToSlash(rel)
|
|
367
304
|
}
|
|
368
305
|
|
|
369
306
|
func isOutsideRelativePath(rel string) bool {
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
func parseBanner(config map[string]any, cwd, tsconfigPath string) (string, error) {
|
|
374
|
-
text, err := resolveBannerText(config, cwd, tsconfigPath)
|
|
375
|
-
if err != nil {
|
|
376
|
-
return "", err
|
|
377
|
-
}
|
|
378
|
-
lines := strings.Split(strings.ReplaceAll(text, "\r\n", "\n"), "\n")
|
|
379
|
-
for len(lines) > 0 && strings.TrimSpace(lines[len(lines)-1]) == "" {
|
|
380
|
-
lines = lines[:len(lines)-1]
|
|
381
|
-
}
|
|
382
|
-
var b strings.Builder
|
|
383
|
-
sep := strings.Repeat("-", 64)
|
|
384
|
-
b.WriteString("/**\n")
|
|
385
|
-
b.WriteString(" * ")
|
|
386
|
-
b.WriteString(sep)
|
|
387
|
-
b.WriteByte('\n')
|
|
388
|
-
for _, line := range lines {
|
|
389
|
-
b.WriteString(" * ")
|
|
390
|
-
b.WriteString(sanitizeJSDocLine(line))
|
|
391
|
-
b.WriteByte('\n')
|
|
392
|
-
}
|
|
393
|
-
b.WriteString(" *\n")
|
|
394
|
-
b.WriteString(" * @packageDocumentation\n ")
|
|
395
|
-
b.WriteString("*/\n")
|
|
396
|
-
return b.String(), nil
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
func resolveBannerText(config map[string]any, cwd, tsconfigPath string) (string, error) {
|
|
400
|
-
if text, ok, err := bannerTextFromConfigValue(config["text"], `"text"`); ok || err != nil {
|
|
401
|
-
return text, err
|
|
402
|
-
}
|
|
403
|
-
if rawConfigPath, ok := config["config"]; ok {
|
|
404
|
-
configPath, ok := rawConfigPath.(string)
|
|
405
|
-
if !ok || strings.TrimSpace(configPath) == "" {
|
|
406
|
-
return "", fmt.Errorf("@ttsc/banner: \"config\" must be a non-empty string path")
|
|
407
|
-
}
|
|
408
|
-
location := resolveBannerConfigPath(configPath, cwd, tsconfigPath)
|
|
409
|
-
raw, err := loadBannerConfigFile(location)
|
|
410
|
-
if err != nil {
|
|
411
|
-
return "", err
|
|
412
|
-
}
|
|
413
|
-
text, ok, err := bannerTextFromConfigValue(raw, filepath.Base(location))
|
|
414
|
-
if err != nil {
|
|
415
|
-
return "", err
|
|
416
|
-
}
|
|
417
|
-
if !ok {
|
|
418
|
-
return "", fmt.Errorf("@ttsc/banner: %s must export a non-empty string or an object with a non-empty \"text\" string", location)
|
|
419
|
-
}
|
|
420
|
-
return text, nil
|
|
421
|
-
}
|
|
422
|
-
location, err := findBannerConfigFile(cwd, tsconfigPath)
|
|
423
|
-
if err != nil {
|
|
424
|
-
return "", err
|
|
425
|
-
}
|
|
426
|
-
if location == "" {
|
|
427
|
-
return "", fmt.Errorf("@ttsc/banner: \"text\" must be a non-empty string or a banner.config.{js,cjs,mjs,ts,mts,cts} file must exist")
|
|
428
|
-
}
|
|
429
|
-
raw, err := loadBannerConfigFile(location)
|
|
430
|
-
if err != nil {
|
|
431
|
-
return "", err
|
|
432
|
-
}
|
|
433
|
-
text, ok, err := bannerTextFromConfigValue(raw, filepath.Base(location))
|
|
434
|
-
if err != nil {
|
|
435
|
-
return "", err
|
|
436
|
-
}
|
|
437
|
-
if !ok {
|
|
438
|
-
return "", fmt.Errorf("@ttsc/banner: %s must export a non-empty string or an object with a non-empty \"text\" string", location)
|
|
439
|
-
}
|
|
440
|
-
return text, nil
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
func bannerTextFromConfigValue(raw any, label string) (string, bool, error) {
|
|
444
|
-
if raw == nil {
|
|
445
|
-
return "", false, nil
|
|
446
|
-
}
|
|
447
|
-
text, ok := raw.(string)
|
|
448
|
-
if ok {
|
|
449
|
-
if strings.TrimSpace(text) == "" {
|
|
450
|
-
return "", true, fmt.Errorf("@ttsc/banner: %s must be a non-empty string", label)
|
|
451
|
-
}
|
|
452
|
-
return text, true, nil
|
|
453
|
-
}
|
|
454
|
-
object, ok := raw.(map[string]any)
|
|
455
|
-
if !ok {
|
|
456
|
-
return "", true, fmt.Errorf("@ttsc/banner: %s must be a string or an object with a non-empty \"text\" string", label)
|
|
457
|
-
}
|
|
458
|
-
rawText, ok := object["text"]
|
|
459
|
-
if !ok {
|
|
460
|
-
return "", false, nil
|
|
461
|
-
}
|
|
462
|
-
text, ok = rawText.(string)
|
|
463
|
-
if !ok || strings.TrimSpace(text) == "" {
|
|
464
|
-
return "", true, fmt.Errorf("@ttsc/banner: %s.text must be a non-empty string", label)
|
|
465
|
-
}
|
|
466
|
-
return text, true, nil
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
func findBannerConfigFile(cwd, tsconfigPath string) (string, error) {
|
|
470
|
-
dir := discoveryConfigBaseDir(cwd, tsconfigPath)
|
|
471
|
-
for {
|
|
472
|
-
matches := make([]string, 0, 1)
|
|
473
|
-
for _, name := range []string{
|
|
474
|
-
"banner.config.js",
|
|
475
|
-
"banner.config.cjs",
|
|
476
|
-
"banner.config.mjs",
|
|
477
|
-
"banner.config.ts",
|
|
478
|
-
"banner.config.cts",
|
|
479
|
-
"banner.config.mts",
|
|
480
|
-
} {
|
|
481
|
-
candidate := filepath.Join(dir, name)
|
|
482
|
-
if stat, err := os.Stat(candidate); err == nil && !stat.IsDir() {
|
|
483
|
-
matches = append(matches, candidate)
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
if len(matches) > 1 {
|
|
487
|
-
return "", fmt.Errorf("@ttsc/banner: multiple banner.config.* files found in %s", dir)
|
|
488
|
-
}
|
|
489
|
-
if len(matches) == 1 {
|
|
490
|
-
return matches[0], nil
|
|
491
|
-
}
|
|
492
|
-
parent := filepath.Dir(dir)
|
|
493
|
-
if parent == dir {
|
|
494
|
-
return "", nil
|
|
495
|
-
}
|
|
496
|
-
dir = parent
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
func resolveBannerConfigPath(configPath, cwd, tsconfigPath string) string {
|
|
501
|
-
if filepath.IsAbs(configPath) {
|
|
502
|
-
return configPath
|
|
503
|
-
}
|
|
504
|
-
return filepath.Join(tsconfigBaseDir(cwd, tsconfigPath), configPath)
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
func tsconfigBaseDir(cwd, tsconfigPath string) string {
|
|
508
|
-
if tsconfigPath == "" {
|
|
509
|
-
return cwd
|
|
510
|
-
}
|
|
511
|
-
resolvedTsconfig := tsconfigPath
|
|
512
|
-
if !filepath.IsAbs(resolvedTsconfig) {
|
|
513
|
-
resolvedTsconfig = filepath.Join(cwd, resolvedTsconfig)
|
|
514
|
-
}
|
|
515
|
-
return filepath.Dir(resolvedTsconfig)
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
func discoveryConfigBaseDir(cwd, tsconfigPath string) string {
|
|
519
|
-
if tsconfigPath != "" {
|
|
520
|
-
resolvedTsconfig := tsconfigPath
|
|
521
|
-
if !filepath.IsAbs(resolvedTsconfig) {
|
|
522
|
-
resolvedTsconfig = filepath.Join(cwd, resolvedTsconfig)
|
|
523
|
-
}
|
|
524
|
-
return filepath.Dir(resolvedTsconfig)
|
|
525
|
-
}
|
|
526
|
-
return cwd
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
func loadBannerConfigFile(location string) (any, error) {
|
|
530
|
-
if !isBannerConfigFileName(filepath.Base(location)) {
|
|
531
|
-
return nil, fmt.Errorf("@ttsc/banner: config file must be named banner.config.{js,cjs,mjs,ts,mts,cts}: %s", location)
|
|
532
|
-
}
|
|
533
|
-
ext := strings.ToLower(filepath.Ext(location))
|
|
534
|
-
switch ext {
|
|
535
|
-
case ".js", ".cjs", ".mjs":
|
|
536
|
-
return loadBannerScriptConfigFile(location)
|
|
537
|
-
case ".ts", ".cts", ".mts":
|
|
538
|
-
return loadBannerTypeScriptConfigFile(location)
|
|
539
|
-
default:
|
|
540
|
-
return nil, fmt.Errorf("@ttsc/banner: unsupported config file extension %q for %s", ext, location)
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
func isBannerConfigFileName(name string) bool {
|
|
545
|
-
switch name {
|
|
546
|
-
case "banner.config.js",
|
|
547
|
-
"banner.config.cjs",
|
|
548
|
-
"banner.config.mjs",
|
|
549
|
-
"banner.config.ts",
|
|
550
|
-
"banner.config.cts",
|
|
551
|
-
"banner.config.mts":
|
|
552
|
-
return true
|
|
553
|
-
default:
|
|
554
|
-
return false
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
func loadBannerScriptConfigFile(location string) (any, error) {
|
|
559
|
-
const script = `
|
|
560
|
-
const { pathToFileURL } = require("node:url");
|
|
561
|
-
|
|
562
|
-
(async () => {
|
|
563
|
-
const mod = await import(pathToFileURL(process.argv[1]).href);
|
|
564
|
-
const candidate = Object.prototype.hasOwnProperty.call(mod, "default") ? mod.default : mod;
|
|
565
|
-
const value = typeof candidate === "function" ? await candidate() : candidate;
|
|
566
|
-
process.stdout.write(JSON.stringify(toSerializableBanner(value)));
|
|
567
|
-
})().catch((error) => {
|
|
568
|
-
process.stderr.write(error && error.stack ? error.stack : String(error));
|
|
569
|
-
process.exit(1);
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
function toSerializableBanner(value) {
|
|
573
|
-
if (typeof value === "string") {
|
|
574
|
-
return value;
|
|
575
|
-
}
|
|
576
|
-
if (value !== null && typeof value === "object" && typeof value.text === "string") {
|
|
577
|
-
return { text: value.text };
|
|
578
|
-
}
|
|
579
|
-
throw new Error("config file must export a string or an object with a text string");
|
|
580
|
-
}
|
|
581
|
-
`
|
|
582
|
-
node := os.Getenv("TTSC_NODE_BINARY")
|
|
583
|
-
if node == "" {
|
|
584
|
-
node = "node"
|
|
585
|
-
}
|
|
586
|
-
cmd := exec.Command(node, "-e", script, location)
|
|
587
|
-
cmd.Env = nodeConfigLoaderEnv(location)
|
|
588
|
-
output, err := cmd.Output()
|
|
589
|
-
if err != nil {
|
|
590
|
-
stderr := ""
|
|
591
|
-
if exit, ok := err.(*exec.ExitError); ok {
|
|
592
|
-
stderr = strings.TrimSpace(string(exit.Stderr))
|
|
593
|
-
}
|
|
594
|
-
if stderr != "" {
|
|
595
|
-
return nil, fmt.Errorf("@ttsc/banner: load config file %s: %s", location, stderr)
|
|
596
|
-
}
|
|
597
|
-
return nil, fmt.Errorf("@ttsc/banner: load config file %s: %w", location, err)
|
|
598
|
-
}
|
|
599
|
-
var out any
|
|
600
|
-
if err := json.Unmarshal(output, &out); err != nil {
|
|
601
|
-
return nil, fmt.Errorf("@ttsc/banner: parse config file %s output: %w", location, err)
|
|
602
|
-
}
|
|
603
|
-
return out, nil
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
func loadBannerTypeScriptConfigFile(location string) (any, error) {
|
|
607
|
-
tempDir, err := os.MkdirTemp("", "ttsc-banner-config-")
|
|
608
|
-
if err != nil {
|
|
609
|
-
return nil, fmt.Errorf("@ttsc/banner: create config loader tempdir: %w", err)
|
|
610
|
-
}
|
|
611
|
-
defer os.RemoveAll(tempDir)
|
|
612
|
-
|
|
613
|
-
if err := linkNearestNodeModules(tempDir, filepath.Dir(location)); err != nil {
|
|
614
|
-
return nil, err
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
loader := filepath.Join(tempDir, "loader.mts")
|
|
618
|
-
tsconfig := filepath.Join(tempDir, "tsconfig.json")
|
|
619
|
-
importSpecifier, err := relativeImportSpecifier(tempDir, location)
|
|
620
|
-
if err != nil {
|
|
621
|
-
return nil, err
|
|
622
|
-
}
|
|
623
|
-
importLiteral, err := json.Marshal(importSpecifier)
|
|
624
|
-
if err != nil {
|
|
625
|
-
return nil, fmt.Errorf("@ttsc/banner: encode config import %s: %w", location, err)
|
|
626
|
-
}
|
|
627
|
-
if err := os.WriteFile(loader, []byte(bannerTypeScriptConfigLoaderSource(string(importLiteral))), 0o644); err != nil {
|
|
628
|
-
return nil, fmt.Errorf("@ttsc/banner: write config loader: %w", err)
|
|
629
|
-
}
|
|
630
|
-
if err := os.WriteFile(tsconfig, []byte(typeScriptConfigLoaderTsconfig(loader, location, tempDir)), 0o644); err != nil {
|
|
631
|
-
return nil, fmt.Errorf("@ttsc/banner: write config loader tsconfig: %w", err)
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
args := []string{
|
|
635
|
-
"--project", tsconfig,
|
|
636
|
-
"--cwd", tempDir,
|
|
637
|
-
"--cache-dir", filepath.Join(tempDir, "cache"),
|
|
638
|
-
}
|
|
639
|
-
if tsgo := os.Getenv("TTSC_TSGO_BINARY"); tsgo != "" {
|
|
640
|
-
args = append(args, "--binary", tsgo)
|
|
641
|
-
}
|
|
642
|
-
args = append(args, loader)
|
|
643
|
-
|
|
644
|
-
cmd := ttsxCommand(args...)
|
|
645
|
-
cmd.Env = nodeConfigLoaderEnv(location)
|
|
646
|
-
output, err := cmd.Output()
|
|
647
|
-
if err != nil {
|
|
648
|
-
stderr := ""
|
|
649
|
-
if exit, ok := err.(*exec.ExitError); ok {
|
|
650
|
-
stderr = strings.TrimSpace(string(exit.Stderr))
|
|
651
|
-
}
|
|
652
|
-
if stderr != "" {
|
|
653
|
-
return nil, fmt.Errorf("@ttsc/banner: load TypeScript config file %s: %s", location, stderr)
|
|
654
|
-
}
|
|
655
|
-
return nil, fmt.Errorf("@ttsc/banner: load TypeScript config file %s: %w", location, err)
|
|
656
|
-
}
|
|
657
|
-
var out any
|
|
658
|
-
if err := json.Unmarshal(output, &out); err != nil {
|
|
659
|
-
return nil, fmt.Errorf("@ttsc/banner: parse TypeScript config file %s output: %w", location, err)
|
|
660
|
-
}
|
|
661
|
-
return out, nil
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
func relativeImportSpecifier(fromDir, location string) (string, error) {
|
|
665
|
-
relative, err := filepath.Rel(fromDir, location)
|
|
666
|
-
if err != nil {
|
|
667
|
-
return "", fmt.Errorf("@ttsc/banner: resolve relative config import %s: %w", location, err)
|
|
668
|
-
}
|
|
669
|
-
relative = filepath.ToSlash(relative)
|
|
670
|
-
if strings.HasPrefix(relative, "../") || strings.HasPrefix(relative, "./") {
|
|
671
|
-
return relative, nil
|
|
672
|
-
}
|
|
673
|
-
return "./" + relative, nil
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
func bannerTypeScriptConfigLoaderSource(importLiteral string) string {
|
|
677
|
-
return fmt.Sprintf(`import * as importedConfig from %s;
|
|
678
|
-
|
|
679
|
-
declare const process: {
|
|
680
|
-
stdout: { write(value: string): void };
|
|
681
|
-
stderr: { write(value: string): void };
|
|
682
|
-
exit(code?: number): never;
|
|
683
|
-
};
|
|
684
|
-
|
|
685
|
-
try {
|
|
686
|
-
const value = await resolveConfig(importedConfig);
|
|
687
|
-
process.stdout.write(JSON.stringify(toSerializableBanner(value)));
|
|
688
|
-
} catch (error) {
|
|
689
|
-
process.stderr.write(error instanceof Error && error.stack ? error.stack : String(error));
|
|
690
|
-
process.exit(1);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
async function resolveConfig(value: unknown): Promise<unknown> {
|
|
694
|
-
let current = value;
|
|
695
|
-
for (let i = 0; i < 8; i++) {
|
|
696
|
-
if (isObject(current) && hasOwn(current, "default")) {
|
|
697
|
-
current = current.default;
|
|
698
|
-
continue;
|
|
699
|
-
}
|
|
700
|
-
break;
|
|
701
|
-
}
|
|
702
|
-
if (typeof current === "function") {
|
|
703
|
-
return await (current as () => unknown | Promise<unknown>)();
|
|
704
|
-
}
|
|
705
|
-
return current;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
function isObject(value: unknown): value is Record<string, unknown> {
|
|
709
|
-
return value !== null && typeof value === "object";
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
function hasOwn(value: Record<string, unknown>, key: string): boolean {
|
|
713
|
-
return Object.prototype.hasOwnProperty.call(value, key);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
function toSerializableBanner(value: unknown): unknown {
|
|
717
|
-
if (typeof value === "string") {
|
|
718
|
-
return value;
|
|
719
|
-
}
|
|
720
|
-
if (isObject(value) && typeof value.text === "string") {
|
|
721
|
-
return { text: value.text };
|
|
722
|
-
}
|
|
723
|
-
throw new Error("config file must export a string or an object with a text string");
|
|
724
|
-
}
|
|
725
|
-
`, importLiteral)
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
func typeScriptConfigLoaderTsconfig(loader, location, outDir string) string {
|
|
729
|
-
content := map[string]any{
|
|
730
|
-
"compilerOptions": map[string]any{
|
|
731
|
-
"allowImportingTsExtensions": true,
|
|
732
|
-
"module": "ESNext",
|
|
733
|
-
"moduleResolution": "bundler",
|
|
734
|
-
"outDir": filepath.ToSlash(filepath.Join(outDir, "out")),
|
|
735
|
-
"rewriteRelativeImportExtensions": true,
|
|
736
|
-
"rootDir": "/",
|
|
737
|
-
"skipLibCheck": true,
|
|
738
|
-
"strict": true,
|
|
739
|
-
"target": "ES2022",
|
|
740
|
-
},
|
|
741
|
-
"files": []string{
|
|
742
|
-
filepath.ToSlash(loader),
|
|
743
|
-
filepath.ToSlash(location),
|
|
744
|
-
},
|
|
745
|
-
}
|
|
746
|
-
body, err := json.MarshalIndent(content, "", " ")
|
|
747
|
-
if err != nil {
|
|
748
|
-
panic(err)
|
|
749
|
-
}
|
|
750
|
-
return string(body)
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
func ttsxCommand(args ...string) *exec.Cmd {
|
|
754
|
-
ttsx := os.Getenv("TTSC_TTSX_BINARY")
|
|
755
|
-
if ttsx == "" {
|
|
756
|
-
ttsx = "ttsx"
|
|
757
|
-
}
|
|
758
|
-
if shouldRunTtsxThroughNode(ttsx) {
|
|
759
|
-
node := os.Getenv("TTSC_NODE_BINARY")
|
|
760
|
-
if node == "" {
|
|
761
|
-
node = "node"
|
|
762
|
-
}
|
|
763
|
-
return exec.Command(node, append([]string{ttsx}, args...)...)
|
|
764
|
-
}
|
|
765
|
-
return exec.Command(ttsx, args...)
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
func shouldRunTtsxThroughNode(binary string) bool {
|
|
769
|
-
switch strings.ToLower(filepath.Ext(binary)) {
|
|
770
|
-
case ".js", ".cjs", ".mjs", ".ts", ".cts", ".mts":
|
|
771
|
-
return true
|
|
772
|
-
default:
|
|
773
|
-
return false
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
func nodeConfigLoaderEnv(location string) []string {
|
|
778
|
-
env := os.Environ()
|
|
779
|
-
parts := make([]string, 0, 2)
|
|
780
|
-
if nodeModules := findNearestNodeModules(filepath.Dir(location)); nodeModules != "" {
|
|
781
|
-
parts = append(parts, nodeModules)
|
|
782
|
-
}
|
|
783
|
-
if existing := os.Getenv("NODE_PATH"); existing != "" {
|
|
784
|
-
parts = append(parts, existing)
|
|
785
|
-
}
|
|
786
|
-
if len(parts) == 0 {
|
|
787
|
-
return env
|
|
788
|
-
}
|
|
789
|
-
return setEnv(env, "NODE_PATH", strings.Join(parts, string(os.PathListSeparator)))
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
func linkNearestNodeModules(tempDir, sourceDir string) error {
|
|
793
|
-
nodeModules := findNearestNodeModules(sourceDir)
|
|
794
|
-
if nodeModules == "" {
|
|
795
|
-
return nil
|
|
796
|
-
}
|
|
797
|
-
link := filepath.Join(tempDir, "node_modules")
|
|
798
|
-
if err := os.Symlink(nodeModules, link); err != nil {
|
|
799
|
-
return fmt.Errorf("@ttsc/banner: link config node_modules %s: %w", nodeModules, err)
|
|
800
|
-
}
|
|
801
|
-
return nil
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
func findNearestNodeModules(start string) string {
|
|
805
|
-
dir := filepath.Clean(start)
|
|
806
|
-
for {
|
|
807
|
-
candidate := filepath.Join(dir, "node_modules")
|
|
808
|
-
if stat, err := os.Stat(candidate); err == nil && stat.IsDir() {
|
|
809
|
-
return candidate
|
|
810
|
-
}
|
|
811
|
-
parent := filepath.Dir(dir)
|
|
812
|
-
if parent == dir {
|
|
813
|
-
return ""
|
|
814
|
-
}
|
|
815
|
-
dir = parent
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
func setEnv(env []string, key, value string) []string {
|
|
820
|
-
prefix := key + "="
|
|
821
|
-
for i, entry := range env {
|
|
822
|
-
if strings.HasPrefix(entry, prefix) {
|
|
823
|
-
env[i] = prefix + value
|
|
824
|
-
return env
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
return append(env, prefix+value)
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
func sanitizeJSDocLine(line string) string {
|
|
831
|
-
return strings.ReplaceAll(line, "*/", "* /")
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
type pathsRewriter struct {
|
|
835
|
-
basePath string
|
|
836
|
-
outDir string
|
|
837
|
-
patterns []pathsPattern
|
|
838
|
-
rootDir string
|
|
839
|
-
sourceFiles map[string]string
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
type pathsPattern struct {
|
|
843
|
-
pattern string
|
|
844
|
-
targets []string
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
func newPathsRewriter(prog *driver.Program) *pathsRewriter {
|
|
848
|
-
out := &pathsRewriter{sourceFiles: map[string]string{}}
|
|
849
|
-
if prog == nil || prog.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig.CompilerOptions == nil {
|
|
850
|
-
return out
|
|
851
|
-
}
|
|
852
|
-
options := prog.ParsedConfig.ParsedConfig.CompilerOptions
|
|
853
|
-
out.basePath = filepath.Clean(options.GetPathsBasePath(prog.Host.GetCurrentDirectory()))
|
|
854
|
-
out.outDir = optionalPath(options.OutDir, prog.Host.GetCurrentDirectory())
|
|
855
|
-
out.rootDir = optionalPath(options.RootDir, prog.Host.GetCurrentDirectory())
|
|
856
|
-
files := prog.SourceFiles()
|
|
857
|
-
if out.rootDir == "" {
|
|
858
|
-
out.rootDir = commonSourceDir(files)
|
|
859
|
-
}
|
|
860
|
-
for _, file := range files {
|
|
861
|
-
name := normalizePath(file.FileName())
|
|
862
|
-
out.sourceFiles[name] = name
|
|
863
|
-
out.sourceFiles[stripKnownSourceExtension(name)] = name
|
|
864
|
-
}
|
|
865
|
-
if options.Paths != nil {
|
|
866
|
-
for pattern, targets := range options.Paths.Entries() {
|
|
867
|
-
out.patterns = append(out.patterns, pathsPattern{
|
|
868
|
-
pattern: pattern,
|
|
869
|
-
targets: append([]string(nil), targets...),
|
|
870
|
-
})
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
sort.SliceStable(out.patterns, func(i, j int) bool {
|
|
874
|
-
return patternRank(out.patterns[i].pattern) > patternRank(out.patterns[j].pattern)
|
|
875
|
-
})
|
|
876
|
-
return out
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
func (r *pathsRewriter) apply(file *shimast.SourceFile) {
|
|
880
|
-
if r == nil || file == nil || len(r.patterns) == 0 {
|
|
881
|
-
return
|
|
882
|
-
}
|
|
883
|
-
visitModuleSpecifiers(file.AsNode(), func(lit *shimast.Node) {
|
|
884
|
-
if lit == nil || lit.Kind != shimast.KindStringLiteral {
|
|
885
|
-
return
|
|
886
|
-
}
|
|
887
|
-
spec := lit.Text()
|
|
888
|
-
rewritten, ok := r.rewrite(file.FileName(), spec)
|
|
889
|
-
if ok && rewritten != spec {
|
|
890
|
-
lit.AsStringLiteral().Text = rewritten
|
|
891
|
-
lit.Flags |= shimast.NodeFlagsSynthesized
|
|
892
|
-
lit.Loc = shimcore.UndefinedTextRange()
|
|
893
|
-
}
|
|
894
|
-
})
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
func visitModuleSpecifiers(node *shimast.Node, visit func(*shimast.Node)) {
|
|
898
|
-
if node == nil {
|
|
899
|
-
return
|
|
900
|
-
}
|
|
901
|
-
switch node.Kind {
|
|
902
|
-
case shimast.KindImportDeclaration:
|
|
903
|
-
visit(node.AsImportDeclaration().ModuleSpecifier)
|
|
904
|
-
case shimast.KindExportDeclaration:
|
|
905
|
-
visit(node.AsExportDeclaration().ModuleSpecifier)
|
|
906
|
-
case shimast.KindImportEqualsDeclaration:
|
|
907
|
-
ref := node.AsImportEqualsDeclaration().ModuleReference
|
|
908
|
-
if ref != nil && ref.Kind == shimast.KindExternalModuleReference {
|
|
909
|
-
visit(ref.AsExternalModuleReference().Expression)
|
|
910
|
-
}
|
|
911
|
-
case shimast.KindImportType:
|
|
912
|
-
arg := node.AsImportTypeNode().Argument
|
|
913
|
-
if arg != nil && arg.Kind == shimast.KindLiteralType {
|
|
914
|
-
visit(arg.AsLiteralTypeNode().Literal)
|
|
915
|
-
}
|
|
916
|
-
case shimast.KindModuleDeclaration:
|
|
917
|
-
decl := node.AsModuleDeclaration()
|
|
918
|
-
if decl != nil {
|
|
919
|
-
visit(decl.Name())
|
|
920
|
-
}
|
|
921
|
-
case shimast.KindCallExpression:
|
|
922
|
-
call := node.AsCallExpression()
|
|
923
|
-
if isModuleSpecifierCall(call) && call.Arguments != nil && len(call.Arguments.Nodes) > 0 {
|
|
924
|
-
visit(call.Arguments.Nodes[0])
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
node.ForEachChild(func(child *shimast.Node) bool {
|
|
928
|
-
visitModuleSpecifiers(child, visit)
|
|
929
|
-
return false
|
|
930
|
-
})
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
func isModuleSpecifierCall(call *shimast.CallExpression) bool {
|
|
934
|
-
if call == nil || call.Expression == nil {
|
|
935
|
-
return false
|
|
936
|
-
}
|
|
937
|
-
switch call.Expression.Kind {
|
|
938
|
-
case shimast.KindImportKeyword:
|
|
939
|
-
return true
|
|
940
|
-
case shimast.KindIdentifier:
|
|
941
|
-
return call.Expression.Text() == "require"
|
|
942
|
-
default:
|
|
943
|
-
return false
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
func (r *pathsRewriter) rewrite(fromSource string, specifier string) (string, bool) {
|
|
948
|
-
if specifier == "" || strings.HasPrefix(specifier, ".") || strings.HasPrefix(specifier, "/") {
|
|
949
|
-
return specifier, false
|
|
950
|
-
}
|
|
951
|
-
targetSource, ok := r.resolveSource(specifier)
|
|
952
|
-
if !ok {
|
|
953
|
-
return specifier, false
|
|
954
|
-
}
|
|
955
|
-
fromOut := r.outputPathForSource(fromSource)
|
|
956
|
-
targetOut := r.outputPathForSource(targetSource)
|
|
957
|
-
if fromOut == "" || targetOut == "" {
|
|
958
|
-
return specifier, false
|
|
959
|
-
}
|
|
960
|
-
rel, err := filepath.Rel(filepath.Dir(fromOut), targetOut)
|
|
961
|
-
if err != nil {
|
|
962
|
-
return specifier, false
|
|
963
|
-
}
|
|
964
|
-
rel = filepath.ToSlash(rel)
|
|
965
|
-
if !strings.HasPrefix(rel, ".") {
|
|
966
|
-
rel = "./" + rel
|
|
967
|
-
}
|
|
968
|
-
return rel, true
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
func (r *pathsRewriter) resolveSource(specifier string) (string, bool) {
|
|
972
|
-
for _, pattern := range r.patterns {
|
|
973
|
-
star, ok := matchPattern(pattern.pattern, specifier)
|
|
974
|
-
if !ok {
|
|
975
|
-
continue
|
|
976
|
-
}
|
|
977
|
-
for _, target := range pattern.targets {
|
|
978
|
-
candidate := strings.Replace(target, "*", star, 1)
|
|
979
|
-
resolved := normalizePath(filepath.Join(r.basePath, candidate))
|
|
980
|
-
if source, ok := r.lookupSource(resolved); ok {
|
|
981
|
-
return source, true
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
return "", false
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
func (r *pathsRewriter) lookupSource(candidate string) (string, bool) {
|
|
989
|
-
if source, ok := r.sourceFiles[normalizePath(candidate)]; ok {
|
|
990
|
-
return source, true
|
|
991
|
-
}
|
|
992
|
-
stem := stripKnownSourceExtension(normalizePath(candidate))
|
|
993
|
-
if source, ok := r.sourceFiles[stem]; ok {
|
|
994
|
-
return source, true
|
|
995
|
-
}
|
|
996
|
-
for _, ext := range []string{".ts", ".tsx", ".mts", ".cts"} {
|
|
997
|
-
if source, ok := r.sourceFiles[stem+ext]; ok {
|
|
998
|
-
return source, true
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
for _, ext := range []string{".ts", ".tsx", ".mts", ".cts"} {
|
|
1002
|
-
if source, ok := r.sourceFiles[normalizePath(filepath.Join(stem, "index"+ext))]; ok {
|
|
1003
|
-
return source, true
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
return "", false
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
func (r *pathsRewriter) outputPathForSource(source string) string {
|
|
1010
|
-
if r.outDir == "" || r.rootDir == "" {
|
|
1011
|
-
return ""
|
|
1012
|
-
}
|
|
1013
|
-
rel, err := filepath.Rel(r.rootDir, source)
|
|
1014
|
-
if err != nil || isOutsideRelativePath(rel) {
|
|
1015
|
-
return ""
|
|
1016
|
-
}
|
|
1017
|
-
return normalizePath(filepath.Join(r.outDir, replaceSourceExtension(rel, emittedJavaScriptExtension(rel))))
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
func emittedJavaScriptExtension(source string) string {
|
|
1021
|
-
switch strings.ToLower(filepath.Ext(source)) {
|
|
1022
|
-
case ".mts":
|
|
1023
|
-
return ".mjs"
|
|
1024
|
-
case ".cts":
|
|
1025
|
-
return ".cjs"
|
|
1026
|
-
default:
|
|
1027
|
-
return ".js"
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
func matchPattern(pattern string, specifier string) (string, bool) {
|
|
1032
|
-
if !strings.Contains(pattern, "*") {
|
|
1033
|
-
return "", pattern == specifier
|
|
1034
|
-
}
|
|
1035
|
-
parts := strings.SplitN(pattern, "*", 2)
|
|
1036
|
-
if !strings.HasPrefix(specifier, parts[0]) || !strings.HasSuffix(specifier, parts[1]) {
|
|
1037
|
-
return "", false
|
|
1038
|
-
}
|
|
1039
|
-
return specifier[len(parts[0]) : len(specifier)-len(parts[1])], true
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
func patternRank(pattern string) int {
|
|
1043
|
-
return len(strings.ReplaceAll(pattern, "*", ""))
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
func optionalPath(value string, cwd string) string {
|
|
1047
|
-
if value == "" {
|
|
1048
|
-
return ""
|
|
1049
|
-
}
|
|
1050
|
-
if filepath.IsAbs(value) {
|
|
1051
|
-
return normalizePath(value)
|
|
1052
|
-
}
|
|
1053
|
-
return normalizePath(filepath.Join(cwd, value))
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
func commonSourceDir(files []*shimast.SourceFile) string {
|
|
1057
|
-
if len(files) == 0 {
|
|
1058
|
-
return ""
|
|
1059
|
-
}
|
|
1060
|
-
common := normalizePath(filepath.Dir(files[0].FileName()))
|
|
1061
|
-
for _, file := range files[1:] {
|
|
1062
|
-
dir := normalizePath(filepath.Dir(file.FileName()))
|
|
1063
|
-
for common != "" && !strings.HasPrefix(dir+"/", common+"/") {
|
|
1064
|
-
next := filepath.Dir(common)
|
|
1065
|
-
if next == common {
|
|
1066
|
-
return common
|
|
1067
|
-
}
|
|
1068
|
-
common = normalizePath(next)
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
return common
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
func normalizePath(value string) string {
|
|
1075
|
-
if value == "" {
|
|
1076
|
-
return ""
|
|
1077
|
-
}
|
|
1078
|
-
return filepath.ToSlash(filepath.Clean(value))
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
func stripKnownSourceExtension(value string) string {
|
|
1082
|
-
lower := strings.ToLower(value)
|
|
1083
|
-
for _, ext := range []string{".d.ts", ".d.mts", ".d.cts", ".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"} {
|
|
1084
|
-
if strings.HasSuffix(lower, ext) {
|
|
1085
|
-
return value[:len(value)-len(ext)]
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
return strings.TrimSuffix(value, filepath.Ext(value))
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
func replaceSourceExtension(value string, ext string) string {
|
|
1092
|
-
return stripKnownSourceExtension(filepath.ToSlash(value)) + ext
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
type stripRewriter struct {
|
|
1096
|
-
calls []callPattern
|
|
1097
|
-
stripDebugger bool
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
type callPattern struct {
|
|
1101
|
-
parts []string
|
|
1102
|
-
wildcard bool
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
func parseStrip(config map[string]any) (*stripRewriter, error) {
|
|
1106
|
-
_, hasCalls := config["calls"]
|
|
1107
|
-
_, hasStatements := config["statements"]
|
|
1108
|
-
if !hasCalls && !hasStatements {
|
|
1109
|
-
config = map[string]any{
|
|
1110
|
-
"calls": []any{"console.log", "console.debug", "assert.*"},
|
|
1111
|
-
"statements": []any{"debugger"},
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
calls, err := stringArrayConfig(config, "calls")
|
|
1115
|
-
if err != nil {
|
|
1116
|
-
return nil, fmt.Errorf("@ttsc/strip: %w", err)
|
|
1117
|
-
}
|
|
1118
|
-
statements, err := stringArrayConfig(config, "statements")
|
|
1119
|
-
if err != nil {
|
|
1120
|
-
return nil, fmt.Errorf("@ttsc/strip: %w", err)
|
|
1121
|
-
}
|
|
1122
|
-
out := &stripRewriter{}
|
|
1123
|
-
for _, call := range calls {
|
|
1124
|
-
pattern, err := parseCallPattern(call)
|
|
1125
|
-
if err != nil {
|
|
1126
|
-
return nil, fmt.Errorf("@ttsc/strip: %w", err)
|
|
1127
|
-
}
|
|
1128
|
-
out.calls = append(out.calls, pattern)
|
|
1129
|
-
}
|
|
1130
|
-
for _, statement := range statements {
|
|
1131
|
-
switch statement {
|
|
1132
|
-
case "debugger":
|
|
1133
|
-
out.stripDebugger = true
|
|
1134
|
-
default:
|
|
1135
|
-
return nil, fmt.Errorf("@ttsc/strip: unsupported statement pattern %q", statement)
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
return out, nil
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
func (s *stripRewriter) apply(file *shimast.SourceFile) {
|
|
1142
|
-
if s == nil || file == nil || (len(s.calls) == 0 && !s.stripDebugger) {
|
|
1143
|
-
return
|
|
1144
|
-
}
|
|
1145
|
-
filterStatements(file.Statements, s)
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
func filterStatements(list *shimast.NodeList, strip *stripRewriter) {
|
|
1149
|
-
if list == nil || len(list.Nodes) == 0 {
|
|
1150
|
-
return
|
|
1151
|
-
}
|
|
1152
|
-
out := make([]*shimast.Node, 0, len(list.Nodes))
|
|
1153
|
-
for _, stmt := range list.Nodes {
|
|
1154
|
-
if shouldStripStatement(stmt, strip) {
|
|
1155
|
-
continue
|
|
1156
|
-
}
|
|
1157
|
-
filterChildStatements(stmt, strip)
|
|
1158
|
-
out = append(out, stmt)
|
|
1159
|
-
}
|
|
1160
|
-
list.Nodes = out
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
func filterChildStatements(node *shimast.Node, strip *stripRewriter) {
|
|
1164
|
-
if node == nil {
|
|
1165
|
-
return
|
|
1166
|
-
}
|
|
1167
|
-
filterEmbeddedStatements(node, strip)
|
|
1168
|
-
if node.CanHaveStatements() {
|
|
1169
|
-
filterStatements(node.StatementList(), strip)
|
|
1170
|
-
}
|
|
1171
|
-
node.ForEachChild(func(child *shimast.Node) bool {
|
|
1172
|
-
filterChildStatements(child, strip)
|
|
1173
|
-
return false
|
|
1174
|
-
})
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
func filterEmbeddedStatements(node *shimast.Node, strip *stripRewriter) {
|
|
1178
|
-
switch node.Kind {
|
|
1179
|
-
case shimast.KindIfStatement:
|
|
1180
|
-
stmt := node.AsIfStatement()
|
|
1181
|
-
stmt.ThenStatement = filterEmbeddedStatement(stmt.ThenStatement, strip)
|
|
1182
|
-
stmt.ElseStatement = filterEmbeddedStatement(stmt.ElseStatement, strip)
|
|
1183
|
-
case shimast.KindDoStatement:
|
|
1184
|
-
stmt := node.AsDoStatement()
|
|
1185
|
-
stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
|
|
1186
|
-
case shimast.KindWhileStatement:
|
|
1187
|
-
stmt := node.AsWhileStatement()
|
|
1188
|
-
stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
|
|
1189
|
-
case shimast.KindForStatement:
|
|
1190
|
-
stmt := node.AsForStatement()
|
|
1191
|
-
stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
|
|
1192
|
-
case shimast.KindForInStatement, shimast.KindForOfStatement:
|
|
1193
|
-
stmt := node.AsForInOrOfStatement()
|
|
1194
|
-
stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
|
|
1195
|
-
case shimast.KindWithStatement:
|
|
1196
|
-
stmt := node.AsWithStatement()
|
|
1197
|
-
stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
|
|
1198
|
-
case shimast.KindLabeledStatement:
|
|
1199
|
-
stmt := node.AsLabeledStatement()
|
|
1200
|
-
stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
func filterEmbeddedStatement(stmt *shimast.Statement, strip *stripRewriter) *shimast.Statement {
|
|
1205
|
-
if stmt == nil {
|
|
1206
|
-
return nil
|
|
1207
|
-
}
|
|
1208
|
-
if shouldStripStatement(stmt, strip) {
|
|
1209
|
-
return emptyStatement(stmt)
|
|
1210
|
-
}
|
|
1211
|
-
filterChildStatements(stmt, strip)
|
|
1212
|
-
return stmt
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
func emptyStatement(original *shimast.Node) *shimast.Statement {
|
|
1216
|
-
empty := shimast.NewNodeFactory(shimast.NodeFactoryHooks{}).NewEmptyStatement()
|
|
1217
|
-
empty.Flags |= shimast.NodeFlagsSynthesized
|
|
1218
|
-
if original != nil {
|
|
1219
|
-
empty.Loc = original.Loc
|
|
1220
|
-
}
|
|
1221
|
-
return empty
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
func shouldStripStatement(node *shimast.Node, strip *stripRewriter) bool {
|
|
1225
|
-
if node == nil {
|
|
1226
|
-
return false
|
|
1227
|
-
}
|
|
1228
|
-
switch node.Kind {
|
|
1229
|
-
case shimast.KindDebuggerStatement:
|
|
1230
|
-
return strip.stripDebugger
|
|
1231
|
-
case shimast.KindExpressionStatement:
|
|
1232
|
-
expr := node.AsExpressionStatement().Expression
|
|
1233
|
-
name, ok := callExpressionName(expr)
|
|
1234
|
-
return ok && strip.matchesCall(name)
|
|
1235
|
-
default:
|
|
1236
|
-
return false
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
func (s *stripRewriter) matchesCall(name string) bool {
|
|
1241
|
-
for _, pattern := range s.calls {
|
|
1242
|
-
if pattern.matches(name) {
|
|
1243
|
-
return true
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
return false
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
func parseCallPattern(text string) (callPattern, error) {
|
|
1250
|
-
parts := strings.Split(text, ".")
|
|
1251
|
-
if len(parts) == 0 {
|
|
1252
|
-
return callPattern{}, fmt.Errorf("empty call pattern")
|
|
1253
|
-
}
|
|
1254
|
-
for i, part := range parts {
|
|
1255
|
-
if part == "" {
|
|
1256
|
-
return callPattern{}, fmt.Errorf("invalid call pattern %q", text)
|
|
1257
|
-
}
|
|
1258
|
-
if part == "*" && i != len(parts)-1 {
|
|
1259
|
-
return callPattern{}, fmt.Errorf("wildcard is only supported at the end of call pattern %q", text)
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
wildcard := parts[len(parts)-1] == "*"
|
|
1263
|
-
if wildcard {
|
|
1264
|
-
parts = parts[:len(parts)-1]
|
|
1265
|
-
}
|
|
1266
|
-
return callPattern{parts: parts, wildcard: wildcard}, nil
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
func (p callPattern) matches(name string) bool {
|
|
1270
|
-
parts := strings.Split(name, ".")
|
|
1271
|
-
if p.wildcard {
|
|
1272
|
-
if len(parts) <= len(p.parts) {
|
|
1273
|
-
return false
|
|
1274
|
-
}
|
|
1275
|
-
return equalStringSlices(parts[:len(p.parts)], p.parts)
|
|
1276
|
-
}
|
|
1277
|
-
return equalStringSlices(parts, p.parts)
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
func callExpressionName(expr *shimast.Node) (string, bool) {
|
|
1281
|
-
if expr == nil || expr.Kind != shimast.KindCallExpression {
|
|
1282
|
-
return "", false
|
|
1283
|
-
}
|
|
1284
|
-
call := expr.AsCallExpression()
|
|
1285
|
-
return dottedName(call.Expression)
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
func dottedName(expr *shimast.Node) (string, bool) {
|
|
1289
|
-
if expr == nil {
|
|
1290
|
-
return "", false
|
|
1291
|
-
}
|
|
1292
|
-
switch expr.Kind {
|
|
1293
|
-
case shimast.KindIdentifier:
|
|
1294
|
-
return expr.Text(), true
|
|
1295
|
-
case shimast.KindPropertyAccessExpression:
|
|
1296
|
-
prop := expr.AsPropertyAccessExpression()
|
|
1297
|
-
left, ok := dottedName(prop.Expression)
|
|
1298
|
-
if !ok || prop.Name() == nil {
|
|
1299
|
-
return "", false
|
|
1300
|
-
}
|
|
1301
|
-
return left + "." + prop.Name().Text(), true
|
|
1302
|
-
default:
|
|
1303
|
-
return "", false
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
func stringArrayConfig(config map[string]any, key string) ([]string, error) {
|
|
1308
|
-
raw, ok := config[key]
|
|
1309
|
-
if !ok || raw == nil {
|
|
1310
|
-
return nil, nil
|
|
1311
|
-
}
|
|
1312
|
-
values, ok := raw.([]any)
|
|
1313
|
-
if !ok {
|
|
1314
|
-
return nil, fmt.Errorf("%q must be an array of strings", key)
|
|
1315
|
-
}
|
|
1316
|
-
out := make([]string, 0, len(values))
|
|
1317
|
-
for i, value := range values {
|
|
1318
|
-
text, ok := value.(string)
|
|
1319
|
-
if !ok || strings.TrimSpace(text) == "" {
|
|
1320
|
-
return nil, fmt.Errorf("%q[%d] must be a non-empty string", key, i)
|
|
1321
|
-
}
|
|
1322
|
-
out = append(out, text)
|
|
1323
|
-
}
|
|
1324
|
-
return out, nil
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
func equalStringSlices(left, right []string) bool {
|
|
1328
|
-
if len(left) != len(right) {
|
|
1329
|
-
return false
|
|
1330
|
-
}
|
|
1331
|
-
for i := range left {
|
|
1332
|
-
if left[i] != right[i] {
|
|
1333
|
-
return false
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
return true
|
|
307
|
+
return rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator))
|
|
1337
308
|
}
|