ttsc 0.12.2 → 0.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/cmd/platform/main.go +9 -0
  2. package/cmd/ttsc/api_compile.go +109 -101
  3. package/cmd/ttsc/api_compile_branches_test.go +55 -57
  4. package/cmd/ttsc/api_transform.go +54 -49
  5. package/cmd/ttsc/api_transform_branches_test.go +30 -32
  6. package/cmd/ttsc/build.go +111 -108
  7. package/cmd/ttsc/build_branches_test.go +95 -97
  8. package/cmd/ttsc/helpers_test.go +39 -28
  9. package/cmd/ttsc/main.go +111 -104
  10. package/cmd/ttsc/main_alias_branches_test.go +21 -23
  11. package/cmd/ttscserver/main.go +96 -90
  12. package/cmd/ttscserver/run_lsp_prefers_tsgo_flag_test.go +22 -22
  13. package/cmd/ttscserver/run_lsp_reports_server_error_test.go +22 -22
  14. package/cmd/ttscserver/run_lsp_uses_tsgo_environment_test.go +23 -23
  15. package/cmd/utility-host/main.go +30 -23
  16. package/driver/lsp.go +53 -7
  17. package/driver/plugins.go +88 -72
  18. package/driver/program.go +362 -339
  19. package/driver/rewrite.go +442 -380
  20. package/internal/lspserver/lsp_envelope.go +64 -64
  21. package/internal/lspserver/lsp_frame.go +60 -60
  22. package/internal/lspserver/lsp_plugin.go +57 -57
  23. package/internal/lspserver/lsp_proxy.go +265 -251
  24. package/internal/lspserver/lsp_server.go +155 -144
  25. package/lib/TtscCompiler.d.ts +7 -0
  26. package/lib/TtscCompiler.js +7 -0
  27. package/lib/TtscCompiler.js.map +1 -1
  28. package/lib/compiler/internal/buildNativeCompiler.d.ts +11 -1
  29. package/lib/compiler/internal/buildNativeCompiler.js +23 -3
  30. package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
  31. package/lib/compiler/internal/compileProjectInMemory.d.ts +9 -0
  32. package/lib/compiler/internal/compileProjectInMemory.js +36 -21
  33. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  34. package/lib/compiler/internal/paths.d.ts +27 -0
  35. package/lib/compiler/internal/paths.js +70 -0
  36. package/lib/compiler/internal/paths.js.map +1 -0
  37. package/lib/compiler/internal/project/readProjectConfig.d.ts +9 -1
  38. package/lib/compiler/internal/project/readProjectConfig.js +80 -1
  39. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  40. package/lib/compiler/internal/project/resolveProjectConfig.d.ts +13 -1
  41. package/lib/compiler/internal/project/resolveProjectConfig.js +24 -1
  42. package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
  43. package/lib/compiler/internal/resolveBinary.d.ts +9 -1
  44. package/lib/compiler/internal/resolveBinary.js +17 -1
  45. package/lib/compiler/internal/resolveBinary.js.map +1 -1
  46. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +16 -1
  47. package/lib/compiler/internal/resolveEmittedJavaScript.js +42 -10
  48. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  49. package/lib/compiler/internal/resolveTsgo.d.ts +21 -1
  50. package/lib/compiler/internal/resolveTsgo.js +23 -1
  51. package/lib/compiler/internal/resolveTsgo.js.map +1 -1
  52. package/lib/compiler/internal/runBuild.d.ts +19 -0
  53. package/lib/compiler/internal/runBuild.js +112 -46
  54. package/lib/compiler/internal/runBuild.js.map +1 -1
  55. package/lib/compiler/internal/runSingleFileEmit.d.ts +10 -1
  56. package/lib/compiler/internal/runSingleFileEmit.js +14 -1
  57. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  58. package/lib/compiler/internal/sharedHostHelpers.d.ts +5 -0
  59. package/lib/compiler/internal/sharedHostHelpers.js +6 -2
  60. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  61. package/lib/compiler/internal/spawnNative.d.ts +21 -0
  62. package/lib/compiler/internal/spawnNative.js +59 -0
  63. package/lib/compiler/internal/spawnNative.js.map +1 -0
  64. package/lib/compiler/internal/transformProjectInMemory.d.ts +15 -1
  65. package/lib/compiler/internal/transformProjectInMemory.js +57 -61
  66. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  67. package/lib/launcher/internal/getCompilerVersionText.js +7 -0
  68. package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
  69. package/lib/launcher/internal/prepareExecution.js +32 -0
  70. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  71. package/lib/launcher/internal/resolveCacheDir.d.ts +9 -0
  72. package/lib/launcher/internal/resolveCacheDir.js +22 -0
  73. package/lib/launcher/internal/resolveCacheDir.js.map +1 -0
  74. package/lib/launcher/internal/resolveTtscserverBinary.js +2 -0
  75. package/lib/launcher/internal/resolveTtscserverBinary.js.map +1 -1
  76. package/lib/launcher/internal/runTtsc.d.ts +10 -0
  77. package/lib/launcher/internal/runTtsc.js +101 -16
  78. package/lib/launcher/internal/runTtsc.js.map +1 -1
  79. package/lib/launcher/internal/runTtscserver.js +10 -35
  80. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  81. package/lib/launcher/internal/runTtsx.d.ts +8 -0
  82. package/lib/launcher/internal/runTtsx.js +33 -7
  83. package/lib/launcher/internal/runTtsx.js.map +1 -1
  84. package/lib/plugin/internal/buildSourcePlugin.d.ts +38 -0
  85. package/lib/plugin/internal/buildSourcePlugin.js +40 -17
  86. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  87. package/lib/plugin/internal/loadProjectPlugins.d.ts +26 -0
  88. package/lib/plugin/internal/loadProjectPlugins.js +32 -18
  89. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  90. package/lib/structures/ITtscCompilerTransformation.d.ts +15 -2
  91. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +0 -2
  92. package/package.json +9 -9
  93. package/shim/ast/go.mod +9 -0
  94. package/shim/ast/go.sum +20 -0
  95. package/shim/ast/safe_text.go +86 -0
  96. package/shim/ast/shim.go +24 -0
  97. package/shim/ast/test/node_text_joins_multi_hop_qualified_name_test.go +32 -0
  98. package/shim/ast/test/node_text_joins_one_hop_qualified_name_test.go +31 -0
  99. package/shim/ast/test/node_text_returns_empty_for_nil_test.go +23 -0
  100. package/shim/ast/test/node_text_returns_identifier_text_test.go +27 -0
  101. package/shim/ast/test/node_text_skips_empty_qualified_left_test.go +30 -0
  102. package/shim/checker/shim.go +35 -0
  103. package/shim/core/shim.go +17 -0
  104. package/shim/diagnosticwriter/shim.go +4 -0
  105. package/shim/lsp/shim.go +3 -3
  106. package/shim/printer/shim.go +20 -0
  107. package/src/TtscCompiler.ts +7 -0
  108. package/src/compiler/internal/buildNativeCompiler.ts +23 -3
  109. package/src/compiler/internal/compileProjectInMemory.ts +34 -25
  110. package/src/compiler/internal/paths.ts +68 -0
  111. package/src/compiler/internal/project/readProjectConfig.ts +86 -1
  112. package/src/compiler/internal/project/resolveProjectConfig.ts +24 -1
  113. package/src/compiler/internal/resolveBinary.ts +17 -1
  114. package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -12
  115. package/src/compiler/internal/resolveTsgo.ts +29 -1
  116. package/src/compiler/internal/runBuild.ts +110 -55
  117. package/src/compiler/internal/runSingleFileEmit.ts +14 -1
  118. package/src/compiler/internal/sharedHostHelpers.ts +7 -7
  119. package/src/compiler/internal/spawnNative.ts +64 -0
  120. package/src/compiler/internal/transformProjectInMemory.ts +47 -67
  121. package/src/launcher/internal/getCompilerVersionText.ts +7 -0
  122. package/src/launcher/internal/prepareExecution.ts +32 -0
  123. package/src/launcher/internal/resolveCacheDir.ts +19 -0
  124. package/src/launcher/internal/resolveTtscserverBinary.ts +2 -0
  125. package/src/launcher/internal/runTtsc.ts +120 -20
  126. package/src/launcher/internal/runTtscserver.ts +9 -1
  127. package/src/launcher/internal/runTtsx.ts +33 -8
  128. package/src/plugin/internal/buildSourcePlugin.ts +40 -14
  129. package/src/plugin/internal/loadProjectPlugins.ts +42 -18
  130. package/src/structures/ITtscCompilerTransformation.ts +15 -2
  131. package/src/structures/ITtscPlugin.ts +0 -1
  132. package/src/structures/internal/TtscSingleFileEmitOptions.ts +0 -2
  133. package/tools/gen_shims/main.go +43 -8
  134. package/utility/host.go +294 -245
package/utility/host.go CHANGED
@@ -1,308 +1,357 @@
1
1
  package utility
2
2
 
3
3
  import (
4
- "encoding/json"
5
- "flag"
6
- "fmt"
7
- "os"
8
- "path/filepath"
9
- "strings"
4
+ "encoding/json"
5
+ "flag"
6
+ "fmt"
7
+ "os"
8
+ "path/filepath"
9
+ "strings"
10
10
 
11
- shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
12
- 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"
13
13
 
14
- "github.com/samchon/ttsc/packages/ttsc/driver"
14
+ "github.com/samchon/ttsc/packages/ttsc/driver"
15
15
  )
16
16
 
17
+ // hostOptions is the parsed form of the flags accepted by all three
18
+ // subcommands (check, build, transform).
17
19
  type hostOptions struct {
18
- cwd string
19
- emit bool
20
- noEmit bool
21
- outDir string
22
- pluginsJSON string
23
- quiet bool
24
- tsconfig string
25
- verbose bool
20
+ cwd string
21
+ emit bool
22
+ noEmit bool
23
+ outDir string
24
+ pluginsJSON string
25
+ quiet bool
26
+ tsconfig string
27
+ verbose bool
26
28
  }
27
29
 
30
+ // transformResult is the JSON envelope written to stdout by RunTransform.
31
+ // TypeScript maps relative output key → printer output; Diagnostics is
32
+ // reserved for future plugin diagnostics (currently always empty/omitted).
28
33
  type transformResult struct {
29
- Diagnostics []any `json:"diagnostics,omitempty"`
30
- TypeScript map[string]string `json:"typescript"`
34
+ Diagnostics []any `json:"diagnostics,omitempty"`
35
+ TypeScript map[string]string `json:"typescript"`
31
36
  }
32
37
 
33
38
  // RunCheck validates the project and linked plugin configuration without
34
39
  // emitting output.
35
40
  func RunCheck(args []string) int {
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
41
+ opts, ok := parseHostOptions("check", args)
42
+ if !ok {
43
+ return 2
44
+ }
45
+ opts.noEmit = true
46
+ prog, _, ok := loadUtilityProgram(opts)
47
+ if !ok {
48
+ return 2
49
+ }
50
+ defer prog.Close()
51
+ if err := prog.ApplyLinkedPlugins(); err != nil {
52
+ fmt.Fprintln(os.Stderr, err)
53
+ return 2
54
+ }
55
+ return 0
51
56
  }
52
57
 
53
58
  // RunBuild hosts linked transform packages inside one compiler emit.
54
59
  func RunBuild(args []string) int {
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
60
+ opts, ok := parseHostOptions("build", args)
61
+ if !ok {
62
+ return 2
63
+ }
64
+ prog, entries, ok := loadUtilityProgram(opts)
65
+ if !ok {
66
+ return 2
67
+ }
68
+ defer prog.Close()
69
+ if opts.noEmit {
70
+ return 0
71
+ }
72
+ if opts.verbose {
73
+ opts.quiet = false // --verbose overrides the default --quiet=true
74
+ }
75
+ if !opts.quiet {
76
+ fmt.Fprintf(os.Stdout, "// ttsc utility: plugins=%d emit=%v\n", len(entries), !opts.noEmit)
77
+ }
78
+ res, eDiags, err := prog.EmitAllRaw(makeSourcePreambleWriteFile(prog))
79
+ if err != nil {
80
+ fmt.Fprintf(os.Stderr, "ttsc utility: emit failed: %v\n", err)
81
+ return 3
82
+ }
83
+ for _, d := range eDiags {
84
+ fmt.Fprintln(os.Stderr, " -", d.String())
85
+ }
86
+ if driver.CountErrors(eDiags) > 0 {
87
+ return 2
88
+ }
89
+ if res != nil && !opts.quiet {
90
+ fmt.Fprintf(os.Stdout, "// ttsc utility: emitted=%d files\n", len(res.EmittedFiles))
91
+ }
92
+ return 0
88
93
  }
89
94
 
90
95
  // RunTransform returns the project TypeScript text after linked source
91
96
  // mutations.
92
97
  func RunTransform(args []string) int {
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
98
+ opts, ok := parseHostOptions("transform", args)
99
+ if !ok {
100
+ return 2
101
+ }
102
+ prog, _, ok := loadUtilityProgram(opts)
103
+ if !ok {
104
+ return 2
105
+ }
106
+ defer prog.Close()
107
+ if err := prog.ApplyLinkedPlugins(); err != nil {
108
+ fmt.Fprintln(os.Stderr, err)
109
+ return 2
110
+ }
111
+ printer := shimprinter.NewPrinter(shimprinter.PrinterOptions{}, shimprinter.PrintHandlers{}, nil)
112
+ out := transformResult{TypeScript: map[string]string{}}
113
+ for _, file := range prog.SourceFiles() {
114
+ text := shimprinter.EmitSourceFile(printer, file)
115
+ out.TypeScript[apiOutputKey(opts.cwd, file.FileName())] = text
116
+ }
117
+ data, _ := json.Marshal(out)
118
+ fmt.Fprintln(os.Stdout, string(data))
119
+ return 0
115
120
  }
116
121
 
122
+ // parseHostOptions parses the standard flag set for the given subcommand name.
123
+ // Unknown flags forwarded from the JS launcher are stripped by filterHostArgs
124
+ // before flag.FlagSet sees them, so spurious "flag provided but not defined"
125
+ // errors are avoided. Returns (zero, false) on any parse or validation error.
117
126
  func parseHostOptions(command string, args []string) (hostOptions, bool) {
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
127
+ fs := flag.NewFlagSet(command, flag.ContinueOnError)
128
+ fs.SetOutput(os.Stderr)
129
+ cwd := fs.String("cwd", "", "project directory")
130
+ emit := fs.Bool("emit", false, "force emit")
131
+ noEmit := fs.Bool("noEmit", false, "force no emit")
132
+ outDir := fs.String("outDir", "", "emit directory override")
133
+ pluginsJSON := fs.String("plugins-json", "", "ttsc plugin manifest JSON")
134
+ quiet := fs.Bool("quiet", true, "suppress summary")
135
+ tsconfig := fs.String("tsconfig", "tsconfig.json", "project tsconfig")
136
+ verbose := fs.Bool("verbose", false, "print summary")
137
+ if err := fs.Parse(filterHostArgs(args)); err != nil {
138
+ return hostOptions{}, false
139
+ }
140
+ if *emit && *noEmit {
141
+ fmt.Fprintln(os.Stderr, "ttsc utility: --emit and --noEmit are mutually exclusive")
142
+ return hostOptions{}, false
143
+ }
144
+ resolvedCwd := *cwd
145
+ if resolvedCwd == "" {
146
+ var err error
147
+ resolvedCwd, err = os.Getwd()
148
+ if err != nil {
149
+ fmt.Fprintf(os.Stderr, "ttsc utility: cwd: %v\n", err)
150
+ return hostOptions{}, false
151
+ }
152
+ }
153
+ if !filepath.IsAbs(resolvedCwd) {
154
+ abs, err := filepath.Abs(resolvedCwd)
155
+ if err != nil {
156
+ fmt.Fprintf(os.Stderr, "ttsc utility: cwd: %v\n", err)
157
+ return hostOptions{}, false
158
+ }
159
+ resolvedCwd = abs
160
+ }
161
+ return hostOptions{
162
+ cwd: filepath.Clean(resolvedCwd),
163
+ emit: *emit,
164
+ noEmit: *noEmit,
165
+ outDir: *outDir,
166
+ pluginsJSON: *pluginsJSON,
167
+ quiet: *quiet,
168
+ tsconfig: *tsconfig,
169
+ verbose: *verbose,
170
+ }, true
162
171
  }
163
172
 
173
+ // filterHostArgs strips flags that the Go flag set does not declare so that
174
+ // flags forwarded from the JS launcher (e.g. tsgo-specific options) do not
175
+ // cause flag.FlagSet to error. Flags not in the known set are consumed together
176
+ // with their value argument when they clearly take one (no inline "=" and the
177
+ // next token does not start with "-").
164
178
  func filterHostArgs(args []string) []string {
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
179
+ // known maps flag name → whether it takes a separate value token.
180
+ // true = the flag accepts a value (--flag value or --flag=value).
181
+ // false = the flag is boolean (no following value token).
182
+ known := map[string]bool{
183
+ "cwd": true,
184
+ "emit": false,
185
+ "noEmit": false,
186
+ "outDir": true,
187
+ "plugins-json": true,
188
+ "quiet": false,
189
+ "tsconfig": true,
190
+ "verbose": false,
191
+ }
192
+ filtered := make([]string, 0, len(args))
193
+ for i := 0; i < len(args); i++ {
194
+ current := args[i]
195
+ if current == "--" {
196
+ break
197
+ }
198
+ if !strings.HasPrefix(current, "--") {
199
+ filtered = append(filtered, current)
200
+ continue
201
+ }
202
+ name, hasInlineValue := flagName(current)
203
+ takesValue, ok := known[name]
204
+ if ok {
205
+ filtered = append(filtered, current)
206
+ if takesValue && !hasInlineValue && i+1 < len(args) {
207
+ i++
208
+ filtered = append(filtered, args[i])
209
+ }
210
+ continue
211
+ }
212
+ if !hasInlineValue && i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
213
+ i++
214
+ }
215
+ }
216
+ return filtered
200
217
  }
201
218
 
219
+ // flagName strips the leading "--" from a flag argument and reports whether
220
+ // the flag carries an inline value (i.e. the argument contains "=").
202
221
  func flagName(arg string) (string, bool) {
203
- name := strings.TrimPrefix(arg, "--")
204
- before, _, found := strings.Cut(name, "=")
205
- return before, found
222
+ name := strings.TrimPrefix(arg, "--")
223
+ before, _, found := strings.Cut(name, "=")
224
+ return before, found
206
225
  }
207
226
 
227
+ // loadUtilityProgram parses plugins JSON, sets the linked-plugin environment
228
+ // variable for the duration of LoadProgram, and returns a fully initialized
229
+ // Program along with the decoded plugin entries. Returns (nil, nil, false) and
230
+ // prints diagnostics to stderr on any error.
208
231
  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()
232
+ entries, err := parsePluginEntries(opts.pluginsJSON)
233
+ if err != nil {
234
+ fmt.Fprintln(os.Stderr, err)
235
+ return nil, nil, false
236
+ }
237
+ restoreEnv := setLinkedPluginManifest(opts.pluginsJSON)
238
+ defer restoreEnv()
216
239
 
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
240
+ prog, diags, err := driver.LoadProgram(opts.cwd, opts.tsconfig, driver.LoadProgramOptions{
241
+ ForceEmit: opts.emit,
242
+ ForceNoEmit: opts.noEmit,
243
+ OutDir: opts.outDir,
244
+ })
245
+ if err != nil {
246
+ fmt.Fprintf(os.Stderr, "ttsc utility: %v\n", err)
247
+ return nil, nil, false
248
+ }
249
+ if len(diags) > 0 {
250
+ driver.WritePrettyDiagnostics(os.Stderr, diags, opts.cwd)
251
+ return nil, nil, false
252
+ }
253
+ if diags := prog.Diagnostics(); len(diags) > 0 {
254
+ driver.WritePrettyDiagnostics(os.Stderr, diags, opts.cwd)
255
+ _ = prog.Close()
256
+ return nil, nil, false
257
+ }
258
+ return prog, entries, true
236
259
  }
237
260
 
261
+ // parsePluginEntries decodes the --plugins-json flag value into a slice of
262
+ // PluginEntry. An empty or whitespace-only string is treated as "no plugins"
263
+ // (returns nil, nil) rather than a JSON error.
238
264
  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
265
+ if strings.TrimSpace(input) == "" {
266
+ return nil, nil
267
+ }
268
+ var entries []driver.PluginEntry
269
+ if err := json.Unmarshal([]byte(input), &entries); err != nil {
270
+ return nil, fmt.Errorf("ttsc utility: invalid --plugins-json: %w", err)
271
+ }
272
+ return entries, nil
247
273
  }
248
274
 
275
+ // setLinkedPluginManifest writes input into the LinkedPluginsEnv environment
276
+ // variable (or clears it when input is blank) and returns a restore function
277
+ // that puts the variable back to its previous state. The restore function is
278
+ // intended to be called via defer immediately after setLinkedPluginManifest.
249
279
  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
- }
280
+ previous, existed := os.LookupEnv(driver.LinkedPluginsEnv)
281
+ if strings.TrimSpace(input) == "" {
282
+ _ = os.Unsetenv(driver.LinkedPluginsEnv)
283
+ } else {
284
+ _ = os.Setenv(driver.LinkedPluginsEnv, input)
285
+ }
286
+ return func() {
287
+ if existed {
288
+ _ = os.Setenv(driver.LinkedPluginsEnv, previous)
289
+ } else {
290
+ _ = os.Unsetenv(driver.LinkedPluginsEnv)
291
+ }
292
+ }
263
293
  }
264
294
 
295
+ // makeSourcePreambleWriteFile returns a WriteFile callback that injects
296
+ // prog.SourcePreamble at the top of every emitted output file that supports
297
+ // it. Returns nil when no preamble is needed (empty preamble, nil program, or
298
+ // RemoveComments enabled), which tells the caller to use the default writer.
265
299
  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
- }
300
+ if prog == nil || prog.SourcePreamble == "" || shouldRemoveComments(prog) {
301
+ return nil
302
+ }
303
+ return func(fileName, text string, _ *shimcompiler.WriteFileData) error {
304
+ if shouldEnsureSourcePreamble(fileName, text, prog.SourcePreamble) {
305
+ text = driver.ApplySourcePreamble(text, prog.SourcePreamble)
306
+ }
307
+ return driver.DefaultWriteFile(fileName, text)
308
+ }
275
309
  }
276
310
 
311
+ // shouldRemoveComments reports whether the compiler options ask tsgo to strip
312
+ // comments. When true the source preamble (which is itself a comment block) must
313
+ // not be injected because tsgo would then strip it, resulting in a no-op.
277
314
  func shouldRemoveComments(prog *driver.Program) bool {
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()
315
+ if prog == nil || prog.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig.CompilerOptions == nil {
316
+ return false
317
+ }
318
+ return prog.ParsedConfig.ParsedConfig.CompilerOptions.RemoveComments.IsTrue()
282
319
  }
283
320
 
321
+ // shouldEnsureSourcePreamble reports whether the preamble still needs to be
322
+ // injected into the output file. The idempotency check (strings.Contains)
323
+ // prevents double-injection on watch-mode rebuilds.
284
324
  func shouldEnsureSourcePreamble(fileName, text, sourcePreamble string) bool {
285
- return isSourcePreambleOutputTarget(fileName) && !strings.Contains(text, sourcePreamble)
325
+ return isSourcePreambleOutputTarget(fileName) && !strings.Contains(text, sourcePreamble)
286
326
  }
287
327
 
328
+ // isSourcePreambleOutputTarget reports whether fileName is a JS or declaration
329
+ // output file that should receive the source preamble. Declaration files
330
+ // (.d.ts/.d.mts/.d.cts) are included because plugins like @ttsc/banner inject
331
+ // a copyright header that must appear there as well.
288
332
  func isSourcePreambleOutputTarget(fileName string) bool {
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
333
+ lower := strings.ToLower(filepath.ToSlash(fileName))
334
+ for _, suffix := range []string{".d.ts", ".d.mts", ".d.cts", ".js", ".jsx", ".mjs", ".cjs"} {
335
+ if strings.HasSuffix(lower, suffix) {
336
+ return true
337
+ }
338
+ }
339
+ return false
296
340
  }
297
341
 
342
+ // apiOutputKey converts an absolute fileName to a path relative to cwd for use
343
+ // as the JSON key in RunTransform output. Falls back to the slash-normalized
344
+ // absolute path when the file lives outside the project root.
298
345
  func apiOutputKey(cwd, fileName string) string {
299
- rel, err := filepath.Rel(cwd, fileName)
300
- if err != nil || isOutsideRelativePath(rel) {
301
- return filepath.ToSlash(fileName)
302
- }
303
- return filepath.ToSlash(rel)
346
+ rel, err := filepath.Rel(cwd, fileName)
347
+ if err != nil || isOutsideRelativePath(rel) {
348
+ return filepath.ToSlash(fileName)
349
+ }
350
+ return filepath.ToSlash(rel)
304
351
  }
305
352
 
353
+ // isOutsideRelativePath reports whether rel escapes the project root (starts
354
+ // with ".." or is exactly "..").
306
355
  func isOutsideRelativePath(rel string) bool {
307
- return rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator))
356
+ return rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator))
308
357
  }