ttsc 0.12.3 → 0.13.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 (151) hide show
  1. package/cmd/platform/main.go +9 -0
  2. package/cmd/ttsc/api_compile.go +122 -101
  3. package/cmd/ttsc/api_compile_branches_test.go +55 -57
  4. package/cmd/ttsc/api_transform.go +67 -49
  5. package/cmd/ttsc/api_transform_branches_test.go +30 -32
  6. package/cmd/ttsc/build.go +141 -108
  7. package/cmd/ttsc/build_branches_test.go +95 -97
  8. package/cmd/ttsc/filter.go +70 -0
  9. package/cmd/ttsc/filter_strips_unknown_forwarded_flag_test.go +61 -0
  10. package/cmd/ttsc/flags_gen.go +30 -0
  11. package/cmd/ttsc/helpers_test.go +39 -28
  12. package/cmd/ttsc/main.go +111 -104
  13. package/cmd/ttsc/main_alias_branches_test.go +21 -23
  14. package/cmd/ttscserver/main.go +96 -90
  15. package/cmd/ttscserver/run_lsp_prefers_tsgo_flag_test.go +22 -22
  16. package/cmd/ttscserver/run_lsp_reports_server_error_test.go +22 -22
  17. package/cmd/ttscserver/run_lsp_uses_tsgo_environment_test.go +23 -23
  18. package/cmd/utility-host/main.go +30 -23
  19. package/driver/lsp.go +53 -7
  20. package/driver/plugins.go +88 -72
  21. package/driver/program.go +464 -340
  22. package/driver/rewrite.go +477 -380
  23. package/internal/lspserver/lsp_envelope.go +64 -64
  24. package/internal/lspserver/lsp_frame.go +60 -60
  25. package/internal/lspserver/lsp_plugin.go +57 -57
  26. package/internal/lspserver/lsp_proxy.go +265 -251
  27. package/internal/lspserver/lsp_server.go +155 -144
  28. package/lib/TtscCompiler.d.ts +7 -0
  29. package/lib/TtscCompiler.js +7 -0
  30. package/lib/TtscCompiler.js.map +1 -1
  31. package/lib/compiler/internal/buildNativeCompiler.d.ts +11 -1
  32. package/lib/compiler/internal/buildNativeCompiler.js +23 -3
  33. package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
  34. package/lib/compiler/internal/compileProjectInMemory.d.ts +9 -0
  35. package/lib/compiler/internal/compileProjectInMemory.js +36 -21
  36. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  37. package/lib/compiler/internal/paths.d.ts +27 -0
  38. package/lib/compiler/internal/paths.js +70 -0
  39. package/lib/compiler/internal/paths.js.map +1 -0
  40. package/lib/compiler/internal/project/readProjectConfig.d.ts +9 -1
  41. package/lib/compiler/internal/project/readProjectConfig.js +80 -1
  42. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  43. package/lib/compiler/internal/project/resolveProjectConfig.d.ts +13 -1
  44. package/lib/compiler/internal/project/resolveProjectConfig.js +42 -1
  45. package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
  46. package/lib/compiler/internal/resolveBinary.d.ts +9 -1
  47. package/lib/compiler/internal/resolveBinary.js +17 -1
  48. package/lib/compiler/internal/resolveBinary.js.map +1 -1
  49. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +16 -1
  50. package/lib/compiler/internal/resolveEmittedJavaScript.js +42 -10
  51. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  52. package/lib/compiler/internal/resolveTsgo.d.ts +21 -1
  53. package/lib/compiler/internal/resolveTsgo.js +23 -1
  54. package/lib/compiler/internal/resolveTsgo.js.map +1 -1
  55. package/lib/compiler/internal/runBuild.d.ts +24 -4
  56. package/lib/compiler/internal/runBuild.js +355 -93
  57. package/lib/compiler/internal/runBuild.js.map +1 -1
  58. package/lib/compiler/internal/runSingleFileEmit.d.ts +10 -1
  59. package/lib/compiler/internal/runSingleFileEmit.js +14 -1
  60. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  61. package/lib/compiler/internal/sharedHostHelpers.d.ts +5 -0
  62. package/lib/compiler/internal/sharedHostHelpers.js +6 -2
  63. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  64. package/lib/compiler/internal/spawnNative.d.ts +21 -0
  65. package/lib/compiler/internal/spawnNative.js +59 -0
  66. package/lib/compiler/internal/spawnNative.js.map +1 -0
  67. package/lib/compiler/internal/transformProjectInMemory.d.ts +15 -1
  68. package/lib/compiler/internal/transformProjectInMemory.js +57 -61
  69. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  70. package/lib/flags/parser.d.ts +63 -0
  71. package/lib/flags/parser.js +254 -0
  72. package/lib/flags/parser.js.map +1 -0
  73. package/lib/flags/schema.d.ts +147 -0
  74. package/lib/flags/schema.js +405 -0
  75. package/lib/flags/schema.js.map +1 -0
  76. package/lib/launcher/internal/getCompilerVersionText.js +7 -0
  77. package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
  78. package/lib/launcher/internal/prepareExecution.js +35 -0
  79. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  80. package/lib/launcher/internal/resolveCacheDir.d.ts +9 -0
  81. package/lib/launcher/internal/resolveCacheDir.js +22 -0
  82. package/lib/launcher/internal/resolveCacheDir.js.map +1 -0
  83. package/lib/launcher/internal/resolveTtscserverBinary.js +2 -0
  84. package/lib/launcher/internal/resolveTtscserverBinary.js.map +1 -1
  85. package/lib/launcher/internal/runTtsc.d.ts +10 -0
  86. package/lib/launcher/internal/runTtsc.js +90 -153
  87. package/lib/launcher/internal/runTtsc.js.map +1 -1
  88. package/lib/launcher/internal/runTtscserver.js +10 -35
  89. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  90. package/lib/launcher/internal/runTtsx.d.ts +8 -0
  91. package/lib/launcher/internal/runTtsx.js +137 -79
  92. package/lib/launcher/internal/runTtsx.js.map +1 -1
  93. package/lib/plugin/internal/buildSourcePlugin.d.ts +38 -0
  94. package/lib/plugin/internal/buildSourcePlugin.js +40 -17
  95. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  96. package/lib/plugin/internal/loadProjectPlugins.d.ts +26 -0
  97. package/lib/plugin/internal/loadProjectPlugins.js +90 -41
  98. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  99. package/lib/structures/ITtscCompilerTransformation.d.ts +15 -2
  100. package/lib/structures/ITtscPlugin.d.ts +24 -0
  101. package/lib/structures/ITtscPluginCapabilities.d.ts +29 -0
  102. package/lib/structures/ITtscPluginCapabilities.js +3 -0
  103. package/lib/structures/ITtscPluginCapabilities.js.map +1 -0
  104. package/lib/structures/index.d.ts +1 -0
  105. package/lib/structures/index.js +1 -0
  106. package/lib/structures/index.js.map +1 -1
  107. package/lib/structures/internal/ITtscLoadedNativePlugin.d.ts +5 -0
  108. package/lib/structures/internal/TtscCommonOptions.d.ts +19 -0
  109. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +0 -2
  110. package/package.json +9 -9
  111. package/shim/ast/shim.go +24 -0
  112. package/shim/checker/shim.go +35 -0
  113. package/shim/core/shim.go +17 -0
  114. package/shim/diagnosticwriter/shim.go +4 -0
  115. package/shim/lsp/shim.go +3 -3
  116. package/shim/printer/shim.go +20 -0
  117. package/src/TtscCompiler.ts +7 -0
  118. package/src/compiler/internal/buildNativeCompiler.ts +23 -3
  119. package/src/compiler/internal/compileProjectInMemory.ts +34 -25
  120. package/src/compiler/internal/paths.ts +68 -0
  121. package/src/compiler/internal/project/readProjectConfig.ts +86 -1
  122. package/src/compiler/internal/project/resolveProjectConfig.ts +44 -1
  123. package/src/compiler/internal/resolveBinary.ts +17 -1
  124. package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -12
  125. package/src/compiler/internal/resolveTsgo.ts +29 -1
  126. package/src/compiler/internal/runBuild.ts +398 -112
  127. package/src/compiler/internal/runSingleFileEmit.ts +14 -1
  128. package/src/compiler/internal/sharedHostHelpers.ts +7 -7
  129. package/src/compiler/internal/spawnNative.ts +64 -0
  130. package/src/compiler/internal/transformProjectInMemory.ts +47 -67
  131. package/src/flags/parser.ts +367 -0
  132. package/src/flags/schema.ts +570 -0
  133. package/src/launcher/internal/getCompilerVersionText.ts +7 -0
  134. package/src/launcher/internal/prepareExecution.ts +35 -0
  135. package/src/launcher/internal/resolveCacheDir.ts +19 -0
  136. package/src/launcher/internal/resolveTtscserverBinary.ts +2 -0
  137. package/src/launcher/internal/runTtsc.ts +97 -143
  138. package/src/launcher/internal/runTtscserver.ts +9 -1
  139. package/src/launcher/internal/runTtsx.ts +149 -82
  140. package/src/plugin/internal/buildSourcePlugin.ts +40 -14
  141. package/src/plugin/internal/loadProjectPlugins.ts +116 -45
  142. package/src/structures/ITtscCompilerTransformation.ts +15 -2
  143. package/src/structures/ITtscPlugin.ts +26 -1
  144. package/src/structures/ITtscPluginCapabilities.ts +29 -0
  145. package/src/structures/index.ts +1 -0
  146. package/src/structures/internal/ITtscLoadedNativePlugin.ts +5 -0
  147. package/src/structures/internal/TtscCommonOptions.ts +19 -0
  148. package/src/structures/internal/TtscSingleFileEmitOptions.ts +0 -2
  149. package/tools/gen_shims/main.go +43 -8
  150. package/utility/flags_gen.go +30 -0
  151. package/utility/host.go +304 -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,105 @@ 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
+ // SingleThreaded forces TypeScript-Go's single-threaded mode (one checker,
208
+ // serial parse/check/emit), mirroring `tsgo --singleThreaded`.
209
+ SingleThreaded bool
210
+ // Checkers overrides the type-checker pool size, mirroring `tsgo --checkers`.
211
+ // Zero leaves TypeScript-Go's default; ignored when SingleThreaded is set.
212
+ Checkers int
213
+ // TsgoArgs carries tsgo CLI flags the `ttsc` launcher did not recognize as
214
+ // its own (`--strict`, `--target es2020`, …). They are parsed through
215
+ // TypeScript-Go's own command-line parser into a CompilerOptions overlay
216
+ // that wins over the tsconfig, exactly as `tsgo`'s CLI merges them.
217
+ TsgoArgs []string
207
218
  }
208
219
 
209
220
  // Close releases the checker pool lease acquired by LoadProgram.
210
221
  func (p *Program) Close() error {
211
- if p.checkerRelease != nil {
212
- p.checkerRelease()
213
- p.checkerRelease = nil
214
- }
215
- return nil
222
+ if p.checkerRelease != nil {
223
+ p.checkerRelease()
224
+ p.checkerRelease = nil
225
+ }
226
+ return nil
216
227
  }
217
228
 
218
229
  // ParseTSConfig parses a tsconfig.json file via tsgo's native JSONC parser.
@@ -221,32 +232,91 @@ func (p *Program) Close() error {
221
232
  // The absolute path is resolved against cwd before any VFS lookups because
222
233
  // tsgo's filesystem APIs require absolute paths — mirrors what tsc does when
223
234
  // you pass a relative `--project` flag.
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
235
+ //
236
+ // cliOptions is a CompilerOptions overlay (from forwarded `tsgo` CLI flags);
237
+ // TypeScript-Go merges its non-zero fields over the tsconfig so the CLI wins,
238
+ // the same precedence tsgo's own command line uses. Pass nil for none.
239
+ func ParseTSConfig(fs vfs.FS, cwd, tsconfigPath string, host shimcompiler.CompilerHost, cliOptions *core.CompilerOptions) (*tsoptions.ParsedCommandLine, []Diagnostic, error) {
240
+ resolved := tspath.ResolvePath(cwd, tsconfigPath)
241
+ if !fs.FileExists(resolved) {
242
+ return nil, nil, fmt.Errorf("tsconfig not found: %s", resolved)
243
+ }
244
+ if cliOptions == nil {
245
+ cliOptions = &core.CompilerOptions{}
246
+ }
247
+ parsed, diags := tsoptions.GetParsedCommandLineOfConfigFile(resolved, cliOptions, nil, host, nil)
248
+ allDiags := append(diags, parsed.Errors...)
249
+ if len(allDiags) > 0 {
250
+ return nil, convertDiagnostics(allDiags), nil
251
+ }
252
+ return parsed, nil, nil
253
+ }
254
+
255
+ // parseTsgoArgs runs forwarded tsgo CLI flags through TypeScript-Go's own
256
+ // command-line parser, yielding a CompilerOptions overlay ParseTSConfig merges
257
+ // over the tsconfig. This is how a plugin build — which constructs its Program
258
+ // in-process rather than shelling out to `tsgo` — still honors flags like
259
+ // `ttsc --strict`. Returns (nil, nil, nil) when there are no forwarded flags.
260
+ func parseTsgoArgs(args []string, host shimcompiler.CompilerHost) (*core.CompilerOptions, []Diagnostic, error) {
261
+ if len(args) == 0 {
262
+ return nil, nil, nil
263
+ }
264
+ cli := tsoptions.ParseCommandLine(args, host)
265
+ if cli == nil {
266
+ return nil, nil, fmt.Errorf("driver: tsgo argument parser returned nil")
267
+ }
268
+ if len(cli.Errors) > 0 {
269
+ return nil, convertDiagnostics(cli.Errors), nil
270
+ }
271
+ return cli.CompilerOptions(), nil, nil
235
272
  }
236
273
 
237
274
  // CreateProgramFromConfig builds a tsgo Program from the parsed config.
275
+ //
276
+ // SingleThreaded is intentionally left unset so the program keeps
277
+ // TypeScript-Go's parallel source parsing and parallel emit. The checker
278
+ // pool, however, is pinned to a single checker (see forceSingleChecker):
279
+ // every phase ttsc layers on top — plugin transforms and the output
280
+ // rewriter — walks the program serially against the one checker returned by
281
+ // Program.GetTypeChecker, and then asks that checker to resolve types in
282
+ // nodes drawn from *every* source file. TypeScript-Go's multi-checker pool
283
+ // affinitizes each file to a different checker and forbids mixing types
284
+ // across them; a circular type whose declarations span files on different
285
+ // checkers resolves to `any` on the borrowed checker. Pinning the pool to
286
+ // one checker keeps prog.Checker consistent with how every file was checked
287
+ // while leaving parse and emit parallel. Both EmitAll and EmitAllRaw
288
+ // serialize the WriteFile callback under a mutex so the emit-stage rewriter
289
+ // never observes the parallel emit either.
238
290
  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
291
+ if parsed == nil {
292
+ return nil, nil, fmt.Errorf("driver: nil parsed command line")
293
+ }
294
+ forceSingleChecker(parsed)
295
+ opts := shimcompiler.ProgramOptions{
296
+ Config: parsed,
297
+ Host: host,
298
+ UseSourceOfProjectReference: true,
299
+ }
300
+ p := shimcompiler.NewProgram(opts)
301
+ return p, nil, nil
302
+ }
303
+
304
+ // forceSingleChecker pins the TypeScript-Go checker pool to a single checker.
305
+ //
306
+ // ttsc's transform and rewrite phases run serially and obtain types through
307
+ // the single checker that Program.GetTypeChecker hands back. Those phases
308
+ // query types on nodes from arbitrary source files, so the checker must be
309
+ // the same one that checked every file. A pool of size > 1 affinitizes files
310
+ // to distinct checkers; resolving a type whose declarations cross that
311
+ // boundary (e.g. a circular indexed-access alias) yields `any`. Parallel
312
+ // parsing and emit are unaffected — they do not consult the checker count.
313
+ func forceSingleChecker(parsed *tsoptions.ParsedCommandLine) {
314
+ options := parsed.ParsedConfig.CompilerOptions
315
+ if options.SingleThreaded == core.TSTrue {
316
+ return
317
+ }
318
+ one := 1
319
+ options.Checkers = &one
250
320
  }
251
321
 
252
322
  // LoadProgram is the one-shot convenience used by `ttsc`.
@@ -255,230 +325,284 @@ func CreateProgramFromConfig(parsed *tsoptions.ParsedCommandLine, host shimcompi
255
325
  //
256
326
  // cwd must be absolute; tsconfigPath may be relative to cwd.
257
327
  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
328
+ if !filepath.IsAbs(cwd) {
329
+ if abs, err := filepath.Abs(cwd); err == nil {
330
+ cwd = abs
331
+ }
332
+ }
333
+ cwd = tspath.ResolvePath(cwd)
334
+ pluginState, err := loadLinkedPluginState(cwd, tsconfigPath)
335
+ if err != nil {
336
+ return nil, nil, err
337
+ }
338
+ preamble, err := pluginState.sourcePreamble()
339
+ if err != nil {
340
+ return nil, nil, err
341
+ }
342
+ if preamble != "" {
343
+ options.SourcePreamble += preamble
344
+ }
345
+ fs := DefaultFS()
346
+ if options.SourcePreamble != "" {
347
+ fs = sourcePreambleFS{
348
+ FS: fs,
349
+ preamble: options.SourcePreamble,
350
+ }
351
+ }
352
+ host := DefaultHost(cwd, fs)
353
+
354
+ cliOptions, cliDiags, err := parseTsgoArgs(options.TsgoArgs, host)
355
+ if err != nil {
356
+ return nil, nil, err
357
+ }
358
+ if len(cliDiags) > 0 {
359
+ return nil, cliDiags, nil
360
+ }
361
+
362
+ parsed, diags, err := ParseTSConfig(fs, cwd, tsconfigPath, host, cliOptions)
363
+ if err != nil {
364
+ return nil, nil, err
365
+ }
366
+ if len(diags) > 0 {
367
+ return nil, diags, nil
368
+ }
369
+ if options.ForceNoEmit {
370
+ forceNoEmit(parsed)
371
+ }
372
+ if options.ForceEmit {
373
+ forceEmit(parsed)
374
+ }
375
+ if options.OutDir != "" {
376
+ overrideOutDir(cwd, parsed, options.OutDir)
377
+ }
378
+ applyThreadingOptions(parsed, options.SingleThreaded, options.Checkers)
379
+
380
+ tsProgram, _, _ := CreateProgramFromConfig(parsed, host)
381
+
382
+ checker, done := tsProgram.GetTypeChecker(context.Background())
383
+ prog := &Program{
384
+ TSProgram: tsProgram,
385
+ ParsedConfig: parsed,
386
+ Checker: checker,
387
+ checkerRelease: done,
388
+ Host: host,
389
+ FS: fs,
390
+ SourcePreamble: options.SourcePreamble,
391
+ }
392
+ prog.plugins = pluginState
393
+ return prog, nil, nil
315
394
  }
316
395
 
396
+ // forceEmit clears noEmit and emitDeclarationOnly so the program always
397
+ // produces JavaScript output regardless of the tsconfig settings.
317
398
  func forceEmit(parsed *tsoptions.ParsedCommandLine) {
318
- options := parsed.ParsedConfig.CompilerOptions
319
- options.NoEmit = core.TSFalse
320
- options.EmitDeclarationOnly = core.TSFalse
399
+ options := parsed.ParsedConfig.CompilerOptions
400
+ options.NoEmit = core.TSFalse
401
+ options.EmitDeclarationOnly = core.TSFalse
321
402
  }
322
403
 
404
+ // forceNoEmit sets noEmit so the program type-checks without writing files.
323
405
  func forceNoEmit(parsed *tsoptions.ParsedCommandLine) {
324
- parsed.ParsedConfig.CompilerOptions.NoEmit = core.TSTrue
406
+ parsed.ParsedConfig.CompilerOptions.NoEmit = core.TSTrue
325
407
  }
326
408
 
409
+ // overrideOutDir resolves outDir against cwd and applies it to the parsed
410
+ // config, replacing any outDir already set in tsconfig.json.
327
411
  func overrideOutDir(cwd string, parsed *tsoptions.ParsedCommandLine, outDir string) {
328
- parsed.ParsedConfig.CompilerOptions.OutDir = tspath.ResolvePath(cwd, outDir)
412
+ parsed.ParsedConfig.CompilerOptions.OutDir = tspath.ResolvePath(cwd, outDir)
413
+ }
414
+
415
+ // applyThreadingOptions forwards the CLI threading knobs onto the parsed
416
+ // compiler options. ttsc mirrors tsgo here: `--singleThreaded` / `--checkers`
417
+ // land in CompilerOptions, and both Program.SingleThreaded() and the checker
418
+ // pool read them from there — ProgramOptions is left untouched, exactly as
419
+ // tsgo's own CLI does. SingleThreaded wins over Checkers, matching the pool's
420
+ // own precedence.
421
+ //
422
+ // Note that CreateProgramFromConfig calls forceSingleChecker afterwards, so a
423
+ // `--checkers N` greater than 1 is recorded here but then clamped back to a
424
+ // single checker: ttsc's serial transform/rewrite phases require one checker
425
+ // (see forceSingleChecker). `--singleThreaded` still takes full effect.
426
+ func applyThreadingOptions(parsed *tsoptions.ParsedCommandLine, singleThreaded bool, checkers int) {
427
+ options := parsed.ParsedConfig.CompilerOptions
428
+ if singleThreaded {
429
+ options.SingleThreaded = core.TSTrue
430
+ }
431
+ if checkers > 0 {
432
+ n := checkers
433
+ options.Checkers = &n
434
+ }
329
435
  }
330
436
 
437
+ // sourcePreambleFS wraps a vfs.FS and prepends the preamble string to every
438
+ // source file read by tsgo's parser. Declaration files (.d.ts etc.) are
439
+ // excluded so injected code never appears in type definitions.
331
440
  type sourcePreambleFS struct {
332
- vfs.FS
333
- preamble string
441
+ vfs.FS
442
+ preamble string
334
443
  }
335
444
 
336
445
  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
446
+ contents, ok := fs.FS.ReadFile(filePath)
447
+ if !ok || !isSourcePreambleTarget(filePath) {
448
+ return contents, ok
449
+ }
450
+ return ApplySourcePreamble(contents, fs.preamble), true
342
451
  }
343
452
 
453
+ // isSourcePreambleTarget reports whether the preamble should be injected into
454
+ // the file at filePath. Declaration files are excluded; all other TypeScript
455
+ // and JavaScript source extensions qualify.
344
456
  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
457
+ lower := strings.ToLower(filepath.ToSlash(filePath))
458
+ for _, suffix := range []string{".d.ts", ".d.mts", ".d.cts"} {
459
+ if strings.HasSuffix(lower, suffix) {
460
+ return false
461
+ }
462
+ }
463
+ for _, suffix := range []string{".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"} {
464
+ if strings.HasSuffix(lower, suffix) {
465
+ return true
466
+ }
467
+ }
468
+ return false
357
469
  }
358
470
 
359
471
  // ApplySourcePreamble inserts a generated source preamble without moving the
360
472
  // file's BOM or hashbang away from the first bytes of the physical output.
361
473
  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
474
+ if preamble == "" {
475
+ return text
476
+ }
477
+ bom := ""
478
+ rest := text
479
+ if strings.HasPrefix(rest, "\ufeff") {
480
+ bom = "\ufeff"
481
+ rest = strings.TrimPrefix(rest, "\ufeff")
482
+ }
483
+ if strings.HasPrefix(rest, "#!") {
484
+ end := strings.IndexByte(rest, '\n')
485
+ if end < 0 {
486
+ return bom + rest + "\n" + preamble
487
+ }
488
+ return bom + rest[:end+1] + preamble + rest[end+1:]
489
+ }
490
+ return bom + preamble + rest
379
491
  }
380
492
 
381
493
  // SourceFiles exposes the program's user-authored source files (declaration
382
494
  // files filtered out).
383
495
  func (p *Program) SourceFiles() []*ast.SourceFile {
384
- _ = p.ApplyLinkedPlugins()
385
- return p.sourceFilesRaw()
496
+ _ = p.ApplyLinkedPlugins()
497
+ return p.sourceFilesRaw()
386
498
  }
387
499
 
500
+ // sourceFilesRaw returns the program's non-declaration source files without
501
+ // running ApplyLinkedPlugins. Used internally to avoid a re-entrant apply.
388
502
  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
503
+ out := make([]*ast.SourceFile, 0)
504
+ if p == nil || p.TSProgram == nil {
505
+ return out
506
+ }
507
+ for _, f := range p.TSProgram.SourceFiles() {
508
+ if f.IsDeclarationFile {
509
+ continue
510
+ }
511
+ out = append(out, f)
512
+ }
513
+ return out
400
514
  }
401
515
 
402
516
  // ApplyLinkedPlugins runs registered linked ProgramPlugin hooks exactly once.
403
517
  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)
518
+ if p == nil || p.pluginsApplied {
519
+ return nil
520
+ }
521
+ p.pluginsApplied = true
522
+ return p.plugins.apply(p)
409
523
  }
410
524
 
411
525
  // Diagnostics returns project diagnostics that must block compilation or
412
526
  // runtime execution before any JavaScript is emitted or evaluated.
413
527
  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))
528
+ if p == nil || p.TSProgram == nil {
529
+ return []Diagnostic{{Message: "driver: nil program"}}
530
+ }
531
+ ctx := context.Background()
532
+ raw := shimcompiler.GetDiagnosticsOfAnyProgram(
533
+ ctx,
534
+ p.TSProgram,
535
+ nil,
536
+ false,
537
+ p.TSProgram.GetBindDiagnostics,
538
+ p.TSProgram.GetSemanticDiagnostics,
539
+ )
540
+ raw = filterDiagnostics(raw)
541
+ return convertDiagnostics(shimcompiler.SortAndDeduplicateDiagnostics(raw))
428
542
  }
429
543
 
544
+ // filterDiagnostics removes diagnostics that are false positives in ttsc's
545
+ // compilation model. Currently it suppresses unused type-parameter warnings
546
+ // on overload signatures that have no body (see isUnusedOverloadSignatureTypeParameterDiagnostic).
430
547
  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
548
+ out := in[:0]
549
+ for _, d := range in {
550
+ if isUnusedOverloadSignatureTypeParameterDiagnostic(d) {
551
+ continue
552
+ }
553
+ out = append(out, d)
554
+ }
555
+ return out
439
556
  }
440
557
 
558
+ // isUnusedOverloadSignatureTypeParameterDiagnostic reports true when the
559
+ // diagnostic is TS6196 ("unused declaration") or TS6205 ("all type parameters
560
+ // are unused") on a function declaration that has no body — i.e., an overload
561
+ // signature. tsgo fires these on overloads whose type parameters are used only
562
+ // in the implementation signature, which is a false positive: the overload
563
+ // signatures are required for narrowing and their type parameters are
564
+ // effectively forwarded to the implementation.
441
565
  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
566
+ if d == nil || d.File() == nil {
567
+ return false
568
+ }
569
+ switch d.Code() {
570
+ case 6196, 6205: // unused declaration / all type parameters are unused
571
+ default:
572
+ return false
573
+ }
574
+ node := ast.GetNodeAtPosition(d.File(), d.Pos(), false)
575
+ for node != nil {
576
+ if node.Kind == ast.KindFunctionDeclaration {
577
+ return node.Body() == nil
578
+ }
579
+ node = node.Parent
580
+ }
581
+ return false
458
582
  }
459
583
 
460
584
  // convertDiagnostics translates shim-specific diagnostics into the plain
461
585
  // Diagnostic struct with line/column populated via tsgo's ECMALineMap (the
462
586
  // same helper tsc uses for its "file:line:col: message" banner).
463
587
  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
588
+ out := make([]Diagnostic, 0, len(in))
589
+ for _, d := range in {
590
+ if d == nil {
591
+ continue
592
+ }
593
+ diag := Diagnostic{Code: d.Code(), Message: d.String(), raw: d}
594
+ if file := d.File(); file != nil {
595
+ diag.File = file.FileName()
596
+ if pos := d.Pos(); pos >= 0 {
597
+ length := d.Len()
598
+ diag.Start = &pos
599
+ diag.Length = &length
600
+ line, col := shimscanner.GetECMALineAndByteOffsetOfPosition(file, pos)
601
+ diag.Line = line + 1
602
+ diag.Column = col + 1
603
+ }
604
+ }
605
+ out = append(out, diag)
606
+ }
607
+ return out
484
608
  }