ttsc 0.12.3 → 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 (126) 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 +22 -9
  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/shim.go +24 -0
  94. package/shim/checker/shim.go +35 -0
  95. package/shim/core/shim.go +17 -0
  96. package/shim/diagnosticwriter/shim.go +4 -0
  97. package/shim/lsp/shim.go +3 -3
  98. package/shim/printer/shim.go +20 -0
  99. package/src/TtscCompiler.ts +7 -0
  100. package/src/compiler/internal/buildNativeCompiler.ts +23 -3
  101. package/src/compiler/internal/compileProjectInMemory.ts +34 -25
  102. package/src/compiler/internal/paths.ts +68 -0
  103. package/src/compiler/internal/project/readProjectConfig.ts +86 -1
  104. package/src/compiler/internal/project/resolveProjectConfig.ts +24 -1
  105. package/src/compiler/internal/resolveBinary.ts +17 -1
  106. package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -12
  107. package/src/compiler/internal/resolveTsgo.ts +29 -1
  108. package/src/compiler/internal/runBuild.ts +110 -55
  109. package/src/compiler/internal/runSingleFileEmit.ts +14 -1
  110. package/src/compiler/internal/sharedHostHelpers.ts +7 -7
  111. package/src/compiler/internal/spawnNative.ts +64 -0
  112. package/src/compiler/internal/transformProjectInMemory.ts +47 -67
  113. package/src/launcher/internal/getCompilerVersionText.ts +7 -0
  114. package/src/launcher/internal/prepareExecution.ts +32 -0
  115. package/src/launcher/internal/resolveCacheDir.ts +19 -0
  116. package/src/launcher/internal/resolveTtscserverBinary.ts +2 -0
  117. package/src/launcher/internal/runTtsc.ts +22 -10
  118. package/src/launcher/internal/runTtscserver.ts +9 -1
  119. package/src/launcher/internal/runTtsx.ts +33 -8
  120. package/src/plugin/internal/buildSourcePlugin.ts +40 -14
  121. package/src/plugin/internal/loadProjectPlugins.ts +42 -18
  122. package/src/structures/ITtscCompilerTransformation.ts +15 -2
  123. package/src/structures/ITtscPlugin.ts +0 -1
  124. package/src/structures/internal/TtscSingleFileEmitOptions.ts +0 -2
  125. package/tools/gen_shims/main.go +43 -8
  126. package/utility/host.go +294 -245
package/driver/program.go CHANGED
@@ -1,21 +1,21 @@
1
1
  package driver
2
2
 
3
3
  import (
4
- "context"
5
- "fmt"
6
- "io"
7
- "path/filepath"
8
- "strings"
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"
4
+ "context"
5
+ "fmt"
6
+ "io"
7
+ "path/filepath"
8
+ "strings"
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
19
  )
20
20
 
21
21
  // Diagnostic is the compilation diagnostic shape ttsc passes around. Kept
@@ -27,16 +27,16 @@ import (
27
27
  // `@ttsc/lint`). At most one of `raw` / `lint` is non-nil; both nil falls
28
28
  // back to the legacy single-line form.
29
29
  type Diagnostic struct {
30
- File string
31
- Line int
32
- Column int
33
- Code int32
34
- Start *int
35
- Length *int
36
- Message string
37
- Severity Severity
38
- raw *ast.Diagnostic
39
- lint *shimdiagnosticwriter.LintDiagnostic
30
+ File string
31
+ Line int
32
+ Column int
33
+ Code int32
34
+ Start *int
35
+ Length *int
36
+ Message string
37
+ Severity Severity
38
+ raw *ast.Diagnostic
39
+ lint *shimdiagnosticwriter.LintDiagnostic
40
40
  }
41
41
 
42
42
  // Severity classifies a diagnostic's blast radius. ttsc treats Error as a
@@ -44,12 +44,12 @@ type Diagnostic struct {
44
44
  type Severity int
45
45
 
46
46
  const (
47
- // SeverityError is the default for tsgo typecheck output and any
48
- // plugin-emitted finding that should fail the build.
49
- SeverityError Severity = iota
50
- // SeverityWarning prints with warning coloring but keeps the build
51
- // status at zero.
52
- SeverityWarning
47
+ // SeverityError is the default for tsgo typecheck output and any
48
+ // plugin-emitted finding that should fail the build.
49
+ SeverityError Severity = iota
50
+ // SeverityWarning prints with warning coloring but keeps the build
51
+ // status at zero.
52
+ SeverityWarning
53
53
  )
54
54
 
55
55
  // IsError reports whether the diagnostic counts toward the build's error
@@ -63,60 +63,60 @@ func (d Diagnostic) IsError() bool { return d.Severity == SeverityError }
63
63
  // rule's enum index). Severity controls both the rendered banner color and
64
64
  // the exit-code outcome.
65
65
  func NewLintDiagnostic(
66
- file *ast.SourceFile,
67
- pos, end int,
68
- code int32,
69
- severity Severity,
70
- message string,
66
+ file *ast.SourceFile,
67
+ pos, end int,
68
+ code int32,
69
+ severity Severity,
70
+ message string,
71
71
  ) Diagnostic {
72
- cat := shimdiagnosticwriter.LintCategoryError
73
- if severity == SeverityWarning {
74
- cat = shimdiagnosticwriter.LintCategoryWarning
75
- }
76
- lint := shimdiagnosticwriter.NewLintDiagnostic(file, pos, end, code, cat, message)
77
- d := Diagnostic{
78
- Code: code,
79
- Message: message,
80
- Severity: severity,
81
- lint: lint,
82
- }
83
- if file != nil {
84
- d.File = file.FileName()
85
- if pos >= 0 {
86
- length := lint.Len()
87
- d.Start = &pos
88
- d.Length = &length
89
- line, col := shimscanner.GetECMALineAndByteOffsetOfPosition(file, pos)
90
- d.Line = line + 1
91
- d.Column = col + 1
92
- }
93
- }
94
- return d
72
+ cat := shimdiagnosticwriter.LintCategoryError
73
+ if severity == SeverityWarning {
74
+ cat = shimdiagnosticwriter.LintCategoryWarning
75
+ }
76
+ lint := shimdiagnosticwriter.NewLintDiagnostic(file, pos, end, code, cat, message)
77
+ d := Diagnostic{
78
+ Code: code,
79
+ Message: message,
80
+ Severity: severity,
81
+ lint: lint,
82
+ }
83
+ if file != nil {
84
+ d.File = file.FileName()
85
+ if pos >= 0 {
86
+ length := lint.Len()
87
+ d.Start = &pos
88
+ d.Length = &length
89
+ line, col := shimscanner.GetECMALineAndByteOffsetOfPosition(file, pos)
90
+ d.Line = line + 1
91
+ d.Column = col + 1
92
+ }
93
+ }
94
+ return d
95
95
  }
96
96
 
97
97
  // SourceFile returns the program source file matching filename.
98
98
  func (p *Program) SourceFile(filename string) *ast.SourceFile {
99
- if p == nil || p.TSProgram == nil {
100
- return nil
101
- }
102
- normalized := filepath.ToSlash(filename)
103
- for _, file := range p.TSProgram.SourceFiles() {
104
- if filepath.ToSlash(file.FileName()) == normalized {
105
- return file
106
- }
107
- }
108
- return nil
99
+ if p == nil || p.TSProgram == nil {
100
+ return nil
101
+ }
102
+ normalized := filepath.ToSlash(filename)
103
+ for _, file := range p.TSProgram.SourceFiles() {
104
+ if filepath.ToSlash(file.FileName()) == normalized {
105
+ return file
106
+ }
107
+ }
108
+ return nil
109
109
  }
110
110
 
111
111
  // String returns a `path:line:col: message` formatted string.
112
112
  func (d Diagnostic) String() string {
113
- if d.File == "" {
114
- return d.Message
115
- }
116
- if d.Line > 0 {
117
- return fmt.Sprintf("%s:%d:%d: %s", d.File, d.Line, d.Column, d.Message)
118
- }
119
- return fmt.Sprintf("%s: %s", d.File, d.Message)
113
+ if d.File == "" {
114
+ return d.Message
115
+ }
116
+ if d.Line > 0 {
117
+ return fmt.Sprintf("%s:%d:%d: %s", d.File, d.Line, d.Column, d.Message)
118
+ }
119
+ return fmt.Sprintf("%s: %s", d.File, d.Message)
120
120
  }
121
121
 
122
122
  // WritePrettyDiagnostics renders diagnostics with TypeScript-style colors,
@@ -125,94 +125,94 @@ func (d Diagnostic) String() string {
125
125
  // are rendered through the same color/context pipeline; entries without
126
126
  // either anchor fall back to the legacy `path:line:col: message` form.
127
127
  func WritePrettyDiagnostics(w io.Writer, diagnostics []Diagnostic, cwd string) {
128
- if len(diagnostics) == 0 {
129
- return
130
- }
131
- rich := make([]Diagnostic, 0, len(diagnostics))
132
- plain := make([]Diagnostic, 0)
133
- for _, d := range diagnostics {
134
- if d.raw != nil || d.lint != nil {
135
- rich = append(rich, d)
136
- } else {
137
- plain = append(plain, d)
138
- }
139
- }
140
- if len(rich) > 0 {
141
- astDiags := make([]*ast.Diagnostic, 0, len(rich))
142
- lintDiags := make([]*shimdiagnosticwriter.LintDiagnostic, 0, len(rich))
143
- for _, d := range rich {
144
- if d.raw != nil {
145
- astDiags = append(astDiags, d.raw)
146
- }
147
- if d.lint != nil {
148
- lintDiags = append(lintDiags, d.lint)
149
- }
150
- }
151
- shimdiagnosticwriter.FormatMixedDiagnostics(w, astDiags, lintDiags, cwd)
152
- }
153
- for _, d := range plain {
154
- fmt.Fprintln(w, " -", d.String())
155
- }
128
+ if len(diagnostics) == 0 {
129
+ return
130
+ }
131
+ rich := make([]Diagnostic, 0, len(diagnostics))
132
+ plain := make([]Diagnostic, 0)
133
+ for _, d := range diagnostics {
134
+ if d.raw != nil || d.lint != nil {
135
+ rich = append(rich, d)
136
+ } else {
137
+ plain = append(plain, d)
138
+ }
139
+ }
140
+ if len(rich) > 0 {
141
+ astDiags := make([]*ast.Diagnostic, 0, len(rich))
142
+ lintDiags := make([]*shimdiagnosticwriter.LintDiagnostic, 0, len(rich))
143
+ for _, d := range rich {
144
+ if d.raw != nil {
145
+ astDiags = append(astDiags, d.raw)
146
+ }
147
+ if d.lint != nil {
148
+ lintDiags = append(lintDiags, d.lint)
149
+ }
150
+ }
151
+ shimdiagnosticwriter.FormatMixedDiagnostics(w, astDiags, lintDiags, cwd)
152
+ }
153
+ for _, d := range plain {
154
+ fmt.Fprintln(w, " -", d.String())
155
+ }
156
156
  }
157
157
 
158
158
  // CountErrors returns the number of diagnostics that should fail the build.
159
159
  // tsgo diagnostics carry their own `Error` category; lint diagnostics carry a
160
160
  // caller-set Severity. Anything that isn't an explicit warning counts.
161
161
  func CountErrors(diagnostics []Diagnostic) int {
162
- n := 0
163
- for _, d := range diagnostics {
164
- if d.lint != nil {
165
- if d.lint.IsError() {
166
- n++
167
- }
168
- continue
169
- }
170
- if d.raw != nil {
171
- // tsgo diagnostics use the diagnostics package category. The
172
- // renderer shim already mirrors the same Error/Warning split, so
173
- // re-categorize via the public IsError shortcut.
174
- if d.Severity != SeverityWarning {
175
- n++
176
- }
177
- continue
178
- }
179
- // Plain text diagnostics (manually assembled): treat as errors so
180
- // "ttsc: tsconfig not found"-style failures still flip the exit code.
181
- n++
182
- }
183
- return n
162
+ n := 0
163
+ for _, d := range diagnostics {
164
+ if d.lint != nil {
165
+ if d.lint.IsError() {
166
+ n++
167
+ }
168
+ continue
169
+ }
170
+ if d.raw != nil {
171
+ // tsgo diagnostics use the diagnostics package category. The
172
+ // renderer shim already mirrors the same Error/Warning split, so
173
+ // re-categorize via the public IsError shortcut.
174
+ if d.Severity != SeverityWarning {
175
+ n++
176
+ }
177
+ continue
178
+ }
179
+ // Plain text diagnostics (manually assembled): treat as errors so
180
+ // "ttsc: tsconfig not found"-style failures still flip the exit code.
181
+ n++
182
+ }
183
+ return n
184
184
  }
185
185
 
186
186
  // Program is the shim-agnostic facade the rest of the engine sees.
187
187
  type Program struct {
188
- TSProgram *shimcompiler.Program
189
- ParsedConfig *tsoptions.ParsedCommandLine
190
- Checker *shimchecker.Checker
191
- checkerRelease func()
192
- Host shimcompiler.CompilerHost
193
- FS vfs.FS
194
- SourcePreamble string
195
- plugins linkedPluginState
196
- pluginsApplied bool
188
+ TSProgram *shimcompiler.Program
189
+ ParsedConfig *tsoptions.ParsedCommandLine
190
+ Checker *shimchecker.Checker
191
+ checkerRelease func()
192
+ Host shimcompiler.CompilerHost
193
+ FS vfs.FS
194
+ SourcePreamble string
195
+ plugins linkedPluginState
196
+ pluginsApplied bool
197
197
  }
198
198
 
199
199
  // LoadProgramOptions controls tsconfig overrides applied before tsgo creates
200
200
  // the program. `ForceEmit` is used by `ttsc --emit` and runtime compilation
201
201
  // so execution still works when the project defaults to `noEmit`.
202
202
  type LoadProgramOptions struct {
203
- ForceEmit bool
204
- ForceNoEmit bool
205
- OutDir string
206
- SourcePreamble string
203
+ ForceEmit bool
204
+ ForceNoEmit bool
205
+ OutDir string
206
+ SourcePreamble string
207
207
  }
208
208
 
209
209
  // Close releases the checker pool lease acquired by LoadProgram.
210
210
  func (p *Program) Close() error {
211
- if p.checkerRelease != nil {
212
- p.checkerRelease()
213
- p.checkerRelease = nil
214
- }
215
- return nil
211
+ if p.checkerRelease != nil {
212
+ p.checkerRelease()
213
+ p.checkerRelease = nil
214
+ }
215
+ return nil
216
216
  }
217
217
 
218
218
  // ParseTSConfig parses a tsconfig.json file via tsgo's native JSONC parser.
@@ -222,31 +222,31 @@ func (p *Program) Close() error {
222
222
  // tsgo's filesystem APIs require absolute paths — mirrors what tsc does when
223
223
  // you pass a relative `--project` flag.
224
224
  func ParseTSConfig(fs vfs.FS, cwd, tsconfigPath string, host shimcompiler.CompilerHost) (*tsoptions.ParsedCommandLine, []Diagnostic, error) {
225
- resolved := tspath.ResolvePath(cwd, tsconfigPath)
226
- if !fs.FileExists(resolved) {
227
- return nil, nil, fmt.Errorf("tsconfig not found: %s", resolved)
228
- }
229
- parsed, diags := tsoptions.GetParsedCommandLineOfConfigFile(resolved, &core.CompilerOptions{}, nil, host, nil)
230
- allDiags := append(diags, parsed.Errors...)
231
- if len(allDiags) > 0 {
232
- return nil, convertDiagnostics(allDiags), nil
233
- }
234
- return parsed, nil, nil
225
+ resolved := tspath.ResolvePath(cwd, tsconfigPath)
226
+ if !fs.FileExists(resolved) {
227
+ return nil, nil, fmt.Errorf("tsconfig not found: %s", resolved)
228
+ }
229
+ parsed, diags := tsoptions.GetParsedCommandLineOfConfigFile(resolved, &core.CompilerOptions{}, nil, host, nil)
230
+ allDiags := append(diags, parsed.Errors...)
231
+ if len(allDiags) > 0 {
232
+ return nil, convertDiagnostics(allDiags), nil
233
+ }
234
+ return parsed, nil, nil
235
235
  }
236
236
 
237
237
  // CreateProgramFromConfig builds a tsgo Program from the parsed config.
238
238
  func CreateProgramFromConfig(parsed *tsoptions.ParsedCommandLine, host shimcompiler.CompilerHost) (*shimcompiler.Program, []Diagnostic, error) {
239
- if parsed == nil {
240
- return nil, nil, fmt.Errorf("driver: nil parsed command line")
241
- }
242
- opts := shimcompiler.ProgramOptions{
243
- Config: parsed,
244
- SingleThreaded: core.TSTrue,
245
- Host: host,
246
- UseSourceOfProjectReference: true,
247
- }
248
- p := shimcompiler.NewProgram(opts)
249
- return p, nil, nil
239
+ if parsed == nil {
240
+ return nil, nil, fmt.Errorf("driver: nil parsed command line")
241
+ }
242
+ opts := shimcompiler.ProgramOptions{
243
+ Config: parsed,
244
+ SingleThreaded: core.TSTrue,
245
+ Host: host,
246
+ UseSourceOfProjectReference: true,
247
+ }
248
+ p := shimcompiler.NewProgram(opts)
249
+ return p, nil, nil
250
250
  }
251
251
 
252
252
  // LoadProgram is the one-shot convenience used by `ttsc`.
@@ -255,230 +255,253 @@ func CreateProgramFromConfig(parsed *tsoptions.ParsedCommandLine, host shimcompi
255
255
  //
256
256
  // cwd must be absolute; tsconfigPath may be relative to cwd.
257
257
  func LoadProgram(cwd, tsconfigPath string, options LoadProgramOptions) (*Program, []Diagnostic, error) {
258
- if !filepath.IsAbs(cwd) {
259
- if abs, err := filepath.Abs(cwd); err == nil {
260
- cwd = abs
261
- }
262
- }
263
- cwd = tspath.ResolvePath(cwd)
264
- pluginState, err := loadLinkedPluginState(cwd, tsconfigPath)
265
- if err != nil {
266
- return nil, nil, err
267
- }
268
- preamble, err := pluginState.sourcePreamble()
269
- if err != nil {
270
- return nil, nil, err
271
- }
272
- if preamble != "" {
273
- options.SourcePreamble += preamble
274
- }
275
- fs := DefaultFS()
276
- if options.SourcePreamble != "" {
277
- fs = sourcePreambleFS{
278
- FS: fs,
279
- preamble: options.SourcePreamble,
280
- }
281
- }
282
- host := DefaultHost(cwd, fs)
283
-
284
- parsed, diags, err := ParseTSConfig(fs, cwd, tsconfigPath, host)
285
- if err != nil {
286
- return nil, nil, err
287
- }
288
- if len(diags) > 0 {
289
- return nil, diags, nil
290
- }
291
- if options.ForceNoEmit {
292
- forceNoEmit(parsed)
293
- }
294
- if options.ForceEmit {
295
- forceEmit(parsed)
296
- }
297
- if options.OutDir != "" {
298
- overrideOutDir(cwd, parsed, options.OutDir)
299
- }
300
-
301
- tsProgram, _, _ := CreateProgramFromConfig(parsed, host)
302
-
303
- checker, done := tsProgram.GetTypeChecker(context.Background())
304
- prog := &Program{
305
- TSProgram: tsProgram,
306
- ParsedConfig: parsed,
307
- Checker: checker,
308
- checkerRelease: done,
309
- Host: host,
310
- FS: fs,
311
- SourcePreamble: options.SourcePreamble,
312
- }
313
- prog.plugins = pluginState
314
- return prog, nil, nil
258
+ if !filepath.IsAbs(cwd) {
259
+ if abs, err := filepath.Abs(cwd); err == nil {
260
+ cwd = abs
261
+ }
262
+ }
263
+ cwd = tspath.ResolvePath(cwd)
264
+ pluginState, err := loadLinkedPluginState(cwd, tsconfigPath)
265
+ if err != nil {
266
+ return nil, nil, err
267
+ }
268
+ preamble, err := pluginState.sourcePreamble()
269
+ if err != nil {
270
+ return nil, nil, err
271
+ }
272
+ if preamble != "" {
273
+ options.SourcePreamble += preamble
274
+ }
275
+ fs := DefaultFS()
276
+ if options.SourcePreamble != "" {
277
+ fs = sourcePreambleFS{
278
+ FS: fs,
279
+ preamble: options.SourcePreamble,
280
+ }
281
+ }
282
+ host := DefaultHost(cwd, fs)
283
+
284
+ parsed, diags, err := ParseTSConfig(fs, cwd, tsconfigPath, host)
285
+ if err != nil {
286
+ return nil, nil, err
287
+ }
288
+ if len(diags) > 0 {
289
+ return nil, diags, nil
290
+ }
291
+ if options.ForceNoEmit {
292
+ forceNoEmit(parsed)
293
+ }
294
+ if options.ForceEmit {
295
+ forceEmit(parsed)
296
+ }
297
+ if options.OutDir != "" {
298
+ overrideOutDir(cwd, parsed, options.OutDir)
299
+ }
300
+
301
+ tsProgram, _, _ := CreateProgramFromConfig(parsed, host)
302
+
303
+ checker, done := tsProgram.GetTypeChecker(context.Background())
304
+ prog := &Program{
305
+ TSProgram: tsProgram,
306
+ ParsedConfig: parsed,
307
+ Checker: checker,
308
+ checkerRelease: done,
309
+ Host: host,
310
+ FS: fs,
311
+ SourcePreamble: options.SourcePreamble,
312
+ }
313
+ prog.plugins = pluginState
314
+ return prog, nil, nil
315
315
  }
316
316
 
317
+ // forceEmit clears noEmit and emitDeclarationOnly so the program always
318
+ // produces JavaScript output regardless of the tsconfig settings.
317
319
  func forceEmit(parsed *tsoptions.ParsedCommandLine) {
318
- options := parsed.ParsedConfig.CompilerOptions
319
- options.NoEmit = core.TSFalse
320
- options.EmitDeclarationOnly = core.TSFalse
320
+ options := parsed.ParsedConfig.CompilerOptions
321
+ options.NoEmit = core.TSFalse
322
+ options.EmitDeclarationOnly = core.TSFalse
321
323
  }
322
324
 
325
+ // forceNoEmit sets noEmit so the program type-checks without writing files.
323
326
  func forceNoEmit(parsed *tsoptions.ParsedCommandLine) {
324
- parsed.ParsedConfig.CompilerOptions.NoEmit = core.TSTrue
327
+ parsed.ParsedConfig.CompilerOptions.NoEmit = core.TSTrue
325
328
  }
326
329
 
330
+ // overrideOutDir resolves outDir against cwd and applies it to the parsed
331
+ // config, replacing any outDir already set in tsconfig.json.
327
332
  func overrideOutDir(cwd string, parsed *tsoptions.ParsedCommandLine, outDir string) {
328
- parsed.ParsedConfig.CompilerOptions.OutDir = tspath.ResolvePath(cwd, outDir)
333
+ parsed.ParsedConfig.CompilerOptions.OutDir = tspath.ResolvePath(cwd, outDir)
329
334
  }
330
335
 
336
+ // sourcePreambleFS wraps a vfs.FS and prepends the preamble string to every
337
+ // source file read by tsgo's parser. Declaration files (.d.ts etc.) are
338
+ // excluded so injected code never appears in type definitions.
331
339
  type sourcePreambleFS struct {
332
- vfs.FS
333
- preamble string
340
+ vfs.FS
341
+ preamble string
334
342
  }
335
343
 
336
344
  func (fs sourcePreambleFS) ReadFile(filePath string) (string, bool) {
337
- contents, ok := fs.FS.ReadFile(filePath)
338
- if !ok || !isSourcePreambleTarget(filePath) {
339
- return contents, ok
340
- }
341
- return ApplySourcePreamble(contents, fs.preamble), true
345
+ contents, ok := fs.FS.ReadFile(filePath)
346
+ if !ok || !isSourcePreambleTarget(filePath) {
347
+ return contents, ok
348
+ }
349
+ return ApplySourcePreamble(contents, fs.preamble), true
342
350
  }
343
351
 
352
+ // isSourcePreambleTarget reports whether the preamble should be injected into
353
+ // the file at filePath. Declaration files are excluded; all other TypeScript
354
+ // and JavaScript source extensions qualify.
344
355
  func isSourcePreambleTarget(filePath string) bool {
345
- lower := strings.ToLower(filepath.ToSlash(filePath))
346
- for _, suffix := range []string{".d.ts", ".d.mts", ".d.cts"} {
347
- if strings.HasSuffix(lower, suffix) {
348
- return false
349
- }
350
- }
351
- for _, suffix := range []string{".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"} {
352
- if strings.HasSuffix(lower, suffix) {
353
- return true
354
- }
355
- }
356
- return false
356
+ lower := strings.ToLower(filepath.ToSlash(filePath))
357
+ for _, suffix := range []string{".d.ts", ".d.mts", ".d.cts"} {
358
+ if strings.HasSuffix(lower, suffix) {
359
+ return false
360
+ }
361
+ }
362
+ for _, suffix := range []string{".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"} {
363
+ if strings.HasSuffix(lower, suffix) {
364
+ return true
365
+ }
366
+ }
367
+ return false
357
368
  }
358
369
 
359
370
  // ApplySourcePreamble inserts a generated source preamble without moving the
360
371
  // file's BOM or hashbang away from the first bytes of the physical output.
361
372
  func ApplySourcePreamble(text string, preamble string) string {
362
- if preamble == "" {
363
- return text
364
- }
365
- bom := ""
366
- rest := text
367
- if strings.HasPrefix(rest, "\ufeff") {
368
- bom = "\ufeff"
369
- rest = strings.TrimPrefix(rest, "\ufeff")
370
- }
371
- if strings.HasPrefix(rest, "#!") {
372
- end := strings.IndexByte(rest, '\n')
373
- if end < 0 {
374
- return bom + rest + "\n" + preamble
375
- }
376
- return bom + rest[:end+1] + preamble + rest[end+1:]
377
- }
378
- return bom + preamble + rest
373
+ if preamble == "" {
374
+ return text
375
+ }
376
+ bom := ""
377
+ rest := text
378
+ if strings.HasPrefix(rest, "\ufeff") {
379
+ bom = "\ufeff"
380
+ rest = strings.TrimPrefix(rest, "\ufeff")
381
+ }
382
+ if strings.HasPrefix(rest, "#!") {
383
+ end := strings.IndexByte(rest, '\n')
384
+ if end < 0 {
385
+ return bom + rest + "\n" + preamble
386
+ }
387
+ return bom + rest[:end+1] + preamble + rest[end+1:]
388
+ }
389
+ return bom + preamble + rest
379
390
  }
380
391
 
381
392
  // SourceFiles exposes the program's user-authored source files (declaration
382
393
  // files filtered out).
383
394
  func (p *Program) SourceFiles() []*ast.SourceFile {
384
- _ = p.ApplyLinkedPlugins()
385
- return p.sourceFilesRaw()
395
+ _ = p.ApplyLinkedPlugins()
396
+ return p.sourceFilesRaw()
386
397
  }
387
398
 
399
+ // sourceFilesRaw returns the program's non-declaration source files without
400
+ // running ApplyLinkedPlugins. Used internally to avoid a re-entrant apply.
388
401
  func (p *Program) sourceFilesRaw() []*ast.SourceFile {
389
- out := make([]*ast.SourceFile, 0)
390
- if p == nil || p.TSProgram == nil {
391
- return out
392
- }
393
- for _, f := range p.TSProgram.SourceFiles() {
394
- if f.IsDeclarationFile {
395
- continue
396
- }
397
- out = append(out, f)
398
- }
399
- return out
402
+ out := make([]*ast.SourceFile, 0)
403
+ if p == nil || p.TSProgram == nil {
404
+ return out
405
+ }
406
+ for _, f := range p.TSProgram.SourceFiles() {
407
+ if f.IsDeclarationFile {
408
+ continue
409
+ }
410
+ out = append(out, f)
411
+ }
412
+ return out
400
413
  }
401
414
 
402
415
  // ApplyLinkedPlugins runs registered linked ProgramPlugin hooks exactly once.
403
416
  func (p *Program) ApplyLinkedPlugins() error {
404
- if p == nil || p.pluginsApplied {
405
- return nil
406
- }
407
- p.pluginsApplied = true
408
- return p.plugins.apply(p)
417
+ if p == nil || p.pluginsApplied {
418
+ return nil
419
+ }
420
+ p.pluginsApplied = true
421
+ return p.plugins.apply(p)
409
422
  }
410
423
 
411
424
  // Diagnostics returns project diagnostics that must block compilation or
412
425
  // runtime execution before any JavaScript is emitted or evaluated.
413
426
  func (p *Program) Diagnostics() []Diagnostic {
414
- if p == nil || p.TSProgram == nil {
415
- return []Diagnostic{{Message: "driver: nil program"}}
416
- }
417
- ctx := context.Background()
418
- raw := shimcompiler.GetDiagnosticsOfAnyProgram(
419
- ctx,
420
- p.TSProgram,
421
- nil,
422
- false,
423
- p.TSProgram.GetBindDiagnostics,
424
- p.TSProgram.GetSemanticDiagnostics,
425
- )
426
- raw = filterDiagnostics(raw)
427
- return convertDiagnostics(shimcompiler.SortAndDeduplicateDiagnostics(raw))
427
+ if p == nil || p.TSProgram == nil {
428
+ return []Diagnostic{{Message: "driver: nil program"}}
429
+ }
430
+ ctx := context.Background()
431
+ raw := shimcompiler.GetDiagnosticsOfAnyProgram(
432
+ ctx,
433
+ p.TSProgram,
434
+ nil,
435
+ false,
436
+ p.TSProgram.GetBindDiagnostics,
437
+ p.TSProgram.GetSemanticDiagnostics,
438
+ )
439
+ raw = filterDiagnostics(raw)
440
+ return convertDiagnostics(shimcompiler.SortAndDeduplicateDiagnostics(raw))
428
441
  }
429
442
 
443
+ // filterDiagnostics removes diagnostics that are false positives in ttsc's
444
+ // compilation model. Currently it suppresses unused type-parameter warnings
445
+ // on overload signatures that have no body (see isUnusedOverloadSignatureTypeParameterDiagnostic).
430
446
  func filterDiagnostics(in []*ast.Diagnostic) []*ast.Diagnostic {
431
- out := in[:0]
432
- for _, d := range in {
433
- if isUnusedOverloadSignatureTypeParameterDiagnostic(d) {
434
- continue
435
- }
436
- out = append(out, d)
437
- }
438
- return out
447
+ out := in[:0]
448
+ for _, d := range in {
449
+ if isUnusedOverloadSignatureTypeParameterDiagnostic(d) {
450
+ continue
451
+ }
452
+ out = append(out, d)
453
+ }
454
+ return out
439
455
  }
440
456
 
457
+ // isUnusedOverloadSignatureTypeParameterDiagnostic reports true when the
458
+ // diagnostic is TS6196 ("unused declaration") or TS6205 ("all type parameters
459
+ // are unused") on a function declaration that has no body — i.e., an overload
460
+ // signature. tsgo fires these on overloads whose type parameters are used only
461
+ // in the implementation signature, which is a false positive: the overload
462
+ // signatures are required for narrowing and their type parameters are
463
+ // effectively forwarded to the implementation.
441
464
  func isUnusedOverloadSignatureTypeParameterDiagnostic(d *ast.Diagnostic) bool {
442
- if d == nil || d.File() == nil {
443
- return false
444
- }
445
- switch d.Code() {
446
- case 6196, 6205: // unused declaration / all type parameters are unused
447
- default:
448
- return false
449
- }
450
- node := ast.GetNodeAtPosition(d.File(), d.Pos(), false)
451
- for node != nil {
452
- if node.Kind == ast.KindFunctionDeclaration {
453
- return node.Body() == nil
454
- }
455
- node = node.Parent
456
- }
457
- return false
465
+ if d == nil || d.File() == nil {
466
+ return false
467
+ }
468
+ switch d.Code() {
469
+ case 6196, 6205: // unused declaration / all type parameters are unused
470
+ default:
471
+ return false
472
+ }
473
+ node := ast.GetNodeAtPosition(d.File(), d.Pos(), false)
474
+ for node != nil {
475
+ if node.Kind == ast.KindFunctionDeclaration {
476
+ return node.Body() == nil
477
+ }
478
+ node = node.Parent
479
+ }
480
+ return false
458
481
  }
459
482
 
460
483
  // convertDiagnostics translates shim-specific diagnostics into the plain
461
484
  // Diagnostic struct with line/column populated via tsgo's ECMALineMap (the
462
485
  // same helper tsc uses for its "file:line:col: message" banner).
463
486
  func convertDiagnostics(in []*ast.Diagnostic) []Diagnostic {
464
- out := make([]Diagnostic, 0, len(in))
465
- for _, d := range in {
466
- if d == nil {
467
- continue
468
- }
469
- diag := Diagnostic{Code: d.Code(), Message: d.String(), raw: d}
470
- if file := d.File(); file != nil {
471
- diag.File = file.FileName()
472
- if pos := d.Pos(); pos >= 0 {
473
- length := d.Len()
474
- diag.Start = &pos
475
- diag.Length = &length
476
- line, col := shimscanner.GetECMALineAndByteOffsetOfPosition(file, pos)
477
- diag.Line = line + 1
478
- diag.Column = col + 1
479
- }
480
- }
481
- out = append(out, diag)
482
- }
483
- return out
487
+ out := make([]Diagnostic, 0, len(in))
488
+ for _, d := range in {
489
+ if d == nil {
490
+ continue
491
+ }
492
+ diag := Diagnostic{Code: d.Code(), Message: d.String(), raw: d}
493
+ if file := d.File(); file != nil {
494
+ diag.File = file.FileName()
495
+ if pos := d.Pos(); pos >= 0 {
496
+ length := d.Len()
497
+ diag.Start = &pos
498
+ diag.Length = &length
499
+ line, col := shimscanner.GetECMALineAndByteOffsetOfPosition(file, pos)
500
+ diag.Line = line + 1
501
+ diag.Column = col + 1
502
+ }
503
+ }
504
+ out = append(out, diag)
505
+ }
506
+ return out
484
507
  }