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