ttsc 0.26.0 → 0.26.2

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 (38) hide show
  1. package/README.md +1 -1
  2. package/driver/diagnostics_preamble.go +284 -0
  3. package/driver/emit_plugin.go +51 -16
  4. package/driver/program.go +27 -14
  5. package/driver/rewrite.go +2 -2
  6. package/internal/graph/diagnostics.go +4 -3
  7. package/internal/graph/provenance.go +10 -1
  8. package/lib/compiler/internal/compileProjectInMemory.js +7 -0
  9. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  10. package/lib/compiler/internal/runBuild.d.ts +28 -0
  11. package/lib/compiler/internal/runBuild.js +41 -2
  12. package/lib/compiler/internal/runBuild.js.map +1 -1
  13. package/lib/compiler/internal/runSingleFileEmit.js +7 -0
  14. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  15. package/lib/flags/schema.js +21 -0
  16. package/lib/flags/schema.js.map +1 -1
  17. package/lib/launcher/internal/assertNoSolutionBuild.d.ts +31 -0
  18. package/lib/launcher/internal/assertNoSolutionBuild.js +38 -0
  19. package/lib/launcher/internal/assertNoSolutionBuild.js.map +1 -0
  20. package/lib/launcher/internal/prepareExecution.js +31 -12
  21. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  22. package/lib/launcher/internal/runTtsc.js +2 -0
  23. package/lib/launcher/internal/runTtsc.js.map +1 -1
  24. package/lib/launcher/internal/runTtsx.js +2 -0
  25. package/lib/launcher/internal/runTtsx.js.map +1 -1
  26. package/lib/launcher/internal/runtimeHooks.js +7 -0
  27. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  28. package/package.json +8 -8
  29. package/shim/compiler/emit_sourcemap.go +56 -15
  30. package/src/compiler/internal/compileProjectInMemory.ts +7 -0
  31. package/src/compiler/internal/runBuild.ts +77 -4
  32. package/src/compiler/internal/runSingleFileEmit.ts +7 -0
  33. package/src/flags/schema.ts +23 -0
  34. package/src/launcher/internal/assertNoSolutionBuild.ts +40 -0
  35. package/src/launcher/internal/prepareExecution.ts +28 -12
  36. package/src/launcher/internal/runTtsc.ts +2 -0
  37. package/src/launcher/internal/runTtsx.ts +2 -0
  38. package/src/launcher/internal/runtimeHooks.ts +7 -0
package/README.md CHANGED
@@ -38,7 +38,7 @@ That covers the CLI. The integrations each have a short guide:
38
38
 
39
39
  ## Lint
40
40
 
41
- Lint and format inside the type-check you already run. 720+ rules across 21 families, plus a formatter that reproduces Prettier's output.
41
+ Lint and format inside the type-check you already run. 720+ rules across 21 families, plus a formatter whose rules are ported from Prettier 3.8.3 — see the [format guide](https://ttsc.dev/docs/lint/format) for the shapes it covers and the ones it leaves alone.
42
42
 
43
43
  ```ts
44
44
  // src/index.ts
@@ -0,0 +1,284 @@
1
+ package driver
2
+
3
+ import (
4
+ "strings"
5
+
6
+ "github.com/microsoft/typescript-go/shim/ast"
7
+ "github.com/microsoft/typescript-go/shim/core"
8
+ shimparser "github.com/microsoft/typescript-go/shim/parser"
9
+ )
10
+
11
+ // Source-preamble correction for compiler diagnostics.
12
+ //
13
+ // ttsc injects a plugin's source preamble (e.g. @ttsc/banner's copyright block)
14
+ // at the SOURCE level: sourcePreambleFS prepends it before TypeScript-Go parses,
15
+ // so the preamble participates in comment emission, removeComments, JSDoc
16
+ // association, and `.d.ts` emit naturally. The side effect is that every
17
+ // position tsgo records — including every diagnostic's — is shifted down by the
18
+ // preamble's line count, while the file the user reads has no preamble. The
19
+ // emitted source map already undoes that shift (AdjustEmittedSourceMap); this is
20
+ // the same invariant applied to the other coordinate the user is shown.
21
+ //
22
+ // The correction lives here, at the Program boundary where the preamble is
23
+ // known, rather than in each renderer. `WritePrettyDiagnostics`, the structured
24
+ // diagnostic list the JS launcher serializes, the graph snapshot, and any future
25
+ // consumer all read a Diagnostic that is already authored-relative, so none of
26
+ // them has to remember.
27
+
28
+ // preambleView is the authored (preamble-free) view of one preamble-injected
29
+ // source file, plus the byte region the preamble occupies in the buffer tsgo
30
+ // parsed.
31
+ //
32
+ // file is a re-parse of the authored text. The diagnostic renderer reads a
33
+ // source file's FileName, Text, and ECMA line map to place a location and quote
34
+ // a code frame, and all three have to describe the text the user wrote — moving
35
+ // the position alone would still quote the preamble-bearing buffer. Re-parsing
36
+ // is what produces a line map tsgo itself computed rather than one this package
37
+ // re-derived, and it costs one parse per file that actually carries a
38
+ // diagnostic.
39
+ type preambleView struct {
40
+ file *ast.SourceFile
41
+ // start is the byte offset where the preamble was inserted, and length its
42
+ // byte length. ApplySourcePreamble inserts after a BOM and after a hashbang
43
+ // line, so start is not always zero.
44
+ start int
45
+ length int
46
+ }
47
+
48
+ // authoredPos maps a position in the preamble-bearing buffer to the authored
49
+ // file. ok is false for a position inside the injected region, which has no
50
+ // authored counterpart at all.
51
+ func (v *preambleView) authoredPos(pos int) (int, bool) {
52
+ if pos < v.start {
53
+ return pos, true
54
+ }
55
+ if pos >= v.start+v.length {
56
+ return pos - v.length, true
57
+ }
58
+ return 0, false
59
+ }
60
+
61
+ // authoredEnd maps an exclusive end offset. An end inside the injected region is
62
+ // truncated to the region's authored boundary, so a range that starts before the
63
+ // preamble never stretches over text the user did not write.
64
+ func (v *preambleView) authoredEnd(end int) int {
65
+ if end <= v.start {
66
+ return end
67
+ }
68
+ if end >= v.start+v.length {
69
+ return end - v.length
70
+ }
71
+ return v.start
72
+ }
73
+
74
+ // sourcePreambleBOM is the UTF-8 byte order mark ApplySourcePreamble keeps
75
+ // ahead of an injected preamble.
76
+ const sourcePreambleBOM = "\ufeff"
77
+
78
+ // sourcePreambleRegion locates the injected preamble inside the text tsgo
79
+ // parsed, mirroring ApplySourcePreamble's insertion point: after a BOM, and
80
+ // after a hashbang line when one is present.
81
+ //
82
+ // The region is read back out of the parsed text rather than recomputed from the
83
+ // on-disk file, so it stays correct whichever of the BOM or hashbang the host
84
+ // stripped before parsing, and the leading-prefix check makes the result
85
+ // self-verifying: a file the preamble was never injected into (a declaration
86
+ // file, a `.json` input, a library file) simply does not match and is reported
87
+ // as having no region.
88
+ //
89
+ // One degenerate shape is nominally off by one, matching the same allowance
90
+ // AdjustSourceMapForPreamble states: a file whose entire content is a hashbang
91
+ // with no trailing newline gets the preamble appended after an inserted newline,
92
+ // which is indistinguishable from an authored trailing newline. Only a position
93
+ // at that file's very end could differ, and such a file has no code to diagnose.
94
+ func sourcePreambleRegion(filePath, text, preamble string) (start int, length int, ok bool) {
95
+ if preamble == "" || !isSourcePreambleTarget(filePath) {
96
+ return 0, 0, false
97
+ }
98
+ start = 0
99
+ if strings.HasPrefix(text, sourcePreambleBOM) {
100
+ start = len(sourcePreambleBOM)
101
+ }
102
+ if strings.HasPrefix(text[start:], "#!") {
103
+ newline := strings.IndexByte(text[start:], '\n')
104
+ if newline < 0 {
105
+ return 0, 0, false
106
+ }
107
+ start += newline + 1
108
+ }
109
+ if !strings.HasPrefix(text[start:], preamble) {
110
+ return 0, 0, false
111
+ }
112
+ return start, len(preamble), true
113
+ }
114
+
115
+ // preambleMapper rebuilds diagnostics against the authored source files of one
116
+ // preamble-bearing Program. It caches one authored view per source file so every
117
+ // diagnostic in a file shares a single re-parsed file object — the renderer's
118
+ // "Found N errors in M files" summary groups by that object's identity.
119
+ //
120
+ // A mapper is built per diagnostics call and thrown away with it: it holds
121
+ // re-parsed source files, which have no reason to outlive the report they were
122
+ // built for.
123
+ type preambleMapper struct {
124
+ preamble string
125
+ views map[*ast.SourceFile]*preambleView
126
+ }
127
+
128
+ // newPreambleMapper returns nil when this Program has no source preamble, which
129
+ // is the signal to leave every diagnostic exactly as tsgo produced it.
130
+ func (p *Program) newPreambleMapper() *preambleMapper {
131
+ if p == nil || p.SourcePreamble == "" {
132
+ return nil
133
+ }
134
+ return &preambleMapper{
135
+ preamble: p.SourcePreamble,
136
+ views: map[*ast.SourceFile]*preambleView{},
137
+ }
138
+ }
139
+
140
+ // viewOf returns the authored view of file, or nil when the preamble was never
141
+ // injected into it.
142
+ func (m *preambleMapper) viewOf(file *ast.SourceFile) *preambleView {
143
+ if m == nil || file == nil {
144
+ return nil
145
+ }
146
+ if view, cached := m.views[file]; cached {
147
+ return view
148
+ }
149
+ view := newPreambleView(file, m.preamble)
150
+ m.views[file] = view
151
+ return view
152
+ }
153
+
154
+ // newPreambleView re-parses file without its injected preamble. It returns nil
155
+ // when the file carries no preamble, or when the re-parse yields nothing to
156
+ // anchor against — an uncorrected position is still better than a nil file.
157
+ func newPreambleView(file *ast.SourceFile, preamble string) *preambleView {
158
+ text := file.Text()
159
+ start, length, ok := sourcePreambleRegion(file.FileName(), text, preamble)
160
+ if !ok {
161
+ return nil
162
+ }
163
+ authored := shimparser.ParseSourceFile(file.ParseOptions(), text[:start]+text[start+length:], file.ScriptKind)
164
+ if authored == nil {
165
+ return nil
166
+ }
167
+ return &preambleView{file: authored, start: start, length: length}
168
+ }
169
+
170
+ // remapDiagnostic returns d anchored to its authored source file. ok is false
171
+ // when d's own position falls inside the injected preamble, which is the one
172
+ // case with no authored answer; see unanchoredPreambleDiagnostic for what the
173
+ // caller does with it.
174
+ //
175
+ // The original diagnostic is never mutated: it belongs to the Program's own
176
+ // diagnostic collection, which other callers read.
177
+ func (m *preambleMapper) remapDiagnostic(d *ast.Diagnostic) (*ast.Diagnostic, bool) {
178
+ if m == nil || d == nil {
179
+ return d, d != nil
180
+ }
181
+ related, relatedChanged := m.remapRelated(d.RelatedInformation())
182
+ view := m.viewOf(d.File())
183
+ if view == nil {
184
+ if !relatedChanged {
185
+ return d, true
186
+ }
187
+ clone := d.Clone()
188
+ clone.SetRelatedInfo(related)
189
+ return clone, true
190
+ }
191
+ pos, ok := view.authoredPos(d.Pos())
192
+ if !ok {
193
+ return nil, false
194
+ }
195
+ clone := d.Clone()
196
+ clone.SetFile(view.file)
197
+ clone.SetLocation(core.NewTextRange(pos, view.authoredEnd(d.End())))
198
+ if relatedChanged {
199
+ clone.SetRelatedInfo(related)
200
+ }
201
+ return clone, true
202
+ }
203
+
204
+ // remapRelated remaps a diagnostic's related information, each entry against its
205
+ // own file. An entry with no authored counterpart is dropped rather than
206
+ // reported without a position: related information exists only to point at a
207
+ // second location, and the primary diagnostic still carries the message.
208
+ func (m *preambleMapper) remapRelated(in []*ast.Diagnostic) ([]*ast.Diagnostic, bool) {
209
+ if len(in) == 0 {
210
+ return nil, false
211
+ }
212
+ out := make([]*ast.Diagnostic, 0, len(in))
213
+ changed := false
214
+ for _, related := range in {
215
+ mapped, ok := m.remapDiagnostic(related)
216
+ if !ok {
217
+ changed = true
218
+ continue
219
+ }
220
+ if mapped != related {
221
+ changed = true
222
+ }
223
+ out = append(out, mapped)
224
+ }
225
+ return out, changed
226
+ }
227
+
228
+ // unanchoredPreambleDiagnostic reports a diagnostic whose position lies inside
229
+ // an injected source preamble.
230
+ //
231
+ // The preamble is text the user never wrote, so there is no authored line to
232
+ // name. The source-map lane drops such segments, but a diagnostic is not a
233
+ // mapping: dropping it would leave a failing build with nothing on stderr to
234
+ // explain the failure, and clamping it to the top of the file would print a
235
+ // coordinate that is simply false — the exact defect this correction exists to
236
+ // remove. So the report is kept and only the coordinate is dropped: the message,
237
+ // the code, and the file the preamble was injected into survive, and the
238
+ // diagnostic renders through the anchor-less `path: message` form ttsc already
239
+ // uses for diagnostics that have no source range. It counts as an error exactly
240
+ // as it did before, so the exit code is unchanged.
241
+ //
242
+ // One consequence to know: the JS launcher recovers structured diagnostics by
243
+ // parsing rendered text, and the anchor-less form carries no `line:col` for that
244
+ // parser to match — so a preamble-region diagnostic reaches stderr and the exit
245
+ // code but not `IFailure.diagnostics`. That is the same deal every anchor-less
246
+ // driver diagnostic has always had (`driver: linked plugins failed to apply`),
247
+ // and it is preferable to publishing a structured position that is not real.
248
+ func unanchoredPreambleDiagnostic(d *ast.Diagnostic) Diagnostic {
249
+ out := Diagnostic{
250
+ Code: d.Code(),
251
+ Message: d.String(),
252
+ Severity: SeverityError,
253
+ }
254
+ if file := d.File(); file != nil {
255
+ out.File = file.FileName()
256
+ }
257
+ return out
258
+ }
259
+
260
+ // convertProgramDiagnostics converts tsgo diagnostics produced by this Program,
261
+ // undoing any source-preamble shift first. Every path that turns this Program's
262
+ // raw diagnostics into driver Diagnostics goes through here, so the correction
263
+ // cannot be forgotten by a new caller. Diagnostics that never pass through a
264
+ // Program — a tsconfig parse failure, a bad CLI flag — keep using
265
+ // convertDiagnostics directly, because no preamble is ever injected into them.
266
+ func (p *Program) convertProgramDiagnostics(in []*ast.Diagnostic) []Diagnostic {
267
+ mapper := p.newPreambleMapper()
268
+ if mapper == nil {
269
+ return convertDiagnostics(in)
270
+ }
271
+ out := make([]Diagnostic, 0, len(in))
272
+ for _, d := range in {
273
+ if d == nil {
274
+ continue
275
+ }
276
+ mapped, ok := mapper.remapDiagnostic(d)
277
+ if !ok {
278
+ out = append(out, unanchoredPreambleDiagnostic(d))
279
+ continue
280
+ }
281
+ out = append(out, convertDiagnostic(mapped))
282
+ }
283
+ return out
284
+ }
@@ -133,13 +133,16 @@ func restoreOriginalDeclarationSymbols(ec *shimprinter.EmitContext, node *shimas
133
133
  // EmitTransformPlugins are chained after the caller's transforms in
134
134
  // registration order.
135
135
  //
136
- // Because the JavaScript side bypasses tsgo's own emitter, it reproduces the
137
- // emitter's source-map step via PrintFileWithSourceMap: a `sourceMap` /
138
- // `inlineSourceMap` build emits a `.js.map` (and `//# sourceMappingURL=`
139
- // trailer) just like a plain build, even when a transform expanded one source
140
- // line into many. All non-JavaScript outputs stay delegated to tsgo's normal
141
- // dts-only emitter so declaration files, declaration maps, and any future
142
- // declaration-lane outputs are not silently lost by the hand-assembled JS path.
136
+ // Because the JavaScript side bypasses tsgo's own emitter, it reproduces that
137
+ // emitter's whole printSourceFile step via PrintFileWithSourceMap: a
138
+ // `sourceMap` / `inlineSourceMap` build emits a `.js.map` (and
139
+ // `//# sourceMappingURL=` trailer) just like a plain build, even when a
140
+ // transform expanded one source line into many; an `emitBOM` build still starts
141
+ // with the byte order mark; and the caller's WriteFile still receives the
142
+ // WriteFileData the emitter would hand it. All non-JavaScript outputs stay
143
+ // delegated to tsgo's normal dts-only emitter so declaration files, declaration
144
+ // maps, and any future declaration-lane outputs are not silently lost by the
145
+ // hand-assembled JS path.
143
146
  func (p *Program) EmitWithPluginTransformers(transforms []PluginTransform, writeFile shimcompiler.WriteFile) ([]Diagnostic, error) {
144
147
  if p == nil || p.TSProgram == nil {
145
148
  return nil, errors.New("driver: nil program")
@@ -183,10 +186,10 @@ func (p *Program) EmitWithPluginTransformers(transforms []PluginTransform, write
183
186
  }
184
187
  // Print through the source-map-aware helper so a `sourceMap` /
185
188
  // `inlineSourceMap` build still gets its `.js.map` and sourceMappingURL
186
- // trailer: the hand-assembled emit pipeline does not run tsgo's emitter,
187
- // so the source-map step printSourceFile would otherwise perform has to
188
- // happen here. With maps off this is the same bare-printer output as
189
- // before.
189
+ // trailer, and an `emitBOM` build its leading mark: the hand-assembled
190
+ // emit pipeline does not run tsgo's emitter, so everything printSourceFile
191
+ // would otherwise do around the printer has to happen here. With maps and
192
+ // emitBOM off this is the same bare-printer output as before.
190
193
  printed := shimcompiler.PrintFileWithSourceMap(ec, out.AsNode(), out, options, host, paths.JsFilePath(), paths.SourceMapFilePath())
191
194
  // A source-level preamble (e.g. @ttsc/banner linked into a typia host)
192
195
  // shifts the map's source coordinates; correct them here too, so the
@@ -204,10 +207,17 @@ func (p *Program) EmitWithPluginTransformers(transforms []PluginTransform, write
204
207
  }
205
208
  }
206
209
  }
207
- if err := p.writePluginEmitOutput(paths.JsFilePath(), printed.JS, writeFile); err != nil {
210
+ // The emitter hands its writeFile callback a WriteFileData for the
211
+ // JavaScript and a nil one for the map (printSourceFile:
212
+ // `writeText(sourceMapFilePath, sourceMap, nil)`), so this lane does the
213
+ // same. See writePluginEmitOutput for what the struct carries here and
214
+ // why the remaining fields stay zero.
215
+ if err := p.writePluginEmitOutput(paths.JsFilePath(), printed.JS, &shimcompiler.WriteFileData{
216
+ SourceMapUrlPos: printed.SourceMapUrlPos,
217
+ }, writeFile); err != nil {
208
218
  return nil, err
209
219
  }
210
- if err := p.writePluginEmitOutput(printed.MapPath, printed.MapText, writeFile); err != nil {
220
+ if err := p.writePluginEmitOutput(printed.MapPath, printed.MapText, nil, writeFile); err != nil {
211
221
  return nil, err
212
222
  }
213
223
  }
@@ -235,17 +245,42 @@ func (p *Program) EmitWithPluginTransformers(transforms []PluginTransform, write
235
245
  },
236
246
  })
237
247
  if result != nil && len(result.Diagnostics) != 0 {
238
- return convertDiagnostics(result.Diagnostics), nil
248
+ return p.convertProgramDiagnostics(result.Diagnostics), nil
239
249
  }
240
250
  return nil, nil
241
251
  }
242
252
 
243
- func (p *Program) writePluginEmitOutput(fileName, text string, writeFile shimcompiler.WriteFile) error {
253
+ // writePluginEmitOutput writes one artifact of the hand-assembled emit, passing
254
+ // the caller's WriteFile the same WriteFileData tsgo's emitter would.
255
+ //
256
+ // What this lane can populate, and what it deliberately cannot:
257
+ //
258
+ // - SourceMapUrlPos: the offset of the `//# sourceMappingURL=` trailer, or -1
259
+ // when none was written. PrintFileWithSourceMap records it exactly where
260
+ // printSourceFile does, so a consumer that relocates or rewrites the trailer
261
+ // works the same on both lanes. This is why a nil data was a real loss and
262
+ // not merely a cosmetic one.
263
+ // - Diagnostics: always empty. The emitter's field carries its accumulated
264
+ // emitterDiagnostics, which on the JavaScript lane are its own write
265
+ // failures; here a write failure is returned as an `error` from
266
+ // EmitWithPluginTransformers instead, and the declaration lane's diagnostics
267
+ // reach the caller through tsgo's own EmitOnlyDts pass and its own
268
+ // WriteFileData. Empty therefore means "nothing to report", not "never
269
+ // populated".
270
+ // - BuildInfo: always nil. It is the `.tsbuildinfo` payload, and this lane
271
+ // emits only JavaScript and its map.
272
+ // - SkippedDtsWrite: an out-parameter for the callee, left zero. Each write
273
+ // gets its own struct so one file's callback cannot observe another's.
274
+ //
275
+ // Nothing on this lane reads the struct back afterwards: unlike tsgo's emitter,
276
+ // EmitWithPluginTransformers builds no EmitResult, so there is no EmittedFiles
277
+ // list for a callee-set SkippedDtsWrite to keep a file out of.
278
+ func (p *Program) writePluginEmitOutput(fileName, text string, data *shimcompiler.WriteFileData, writeFile shimcompiler.WriteFile) error {
244
279
  if fileName == "" || p.outputEscapesOutDir(fileName) {
245
280
  return nil
246
281
  }
247
282
  if writeFile != nil {
248
- return writeFile(fileName, text, nil)
283
+ return writeFile(fileName, text, data)
249
284
  }
250
285
  return DefaultWriteFile(fileName, text)
251
286
  }
package/driver/program.go CHANGED
@@ -638,7 +638,11 @@ func (p *Program) diagnostics(files []*ast.SourceFile) []Diagnostic {
638
638
  }
639
639
  }
640
640
  raw = filterDiagnostics(raw)
641
- return append(out, convertDiagnostics(shimcompiler.SortAndDeduplicateDiagnostics(raw))...)
641
+ // filterDiagnostics runs first because it resolves a node from the position
642
+ // tsgo recorded, which only makes sense against the tree tsgo parsed. The
643
+ // preamble correction happens inside convertProgramDiagnostics, once the
644
+ // positions are no longer used to look anything up.
645
+ return append(out, p.convertProgramDiagnostics(shimcompiler.SortAndDeduplicateDiagnostics(raw))...)
642
646
  }
643
647
 
644
648
  // filterDiagnostics removes diagnostics that are false positives in ttsc's
@@ -684,25 +688,34 @@ func isUnusedOverloadSignatureTypeParameterDiagnostic(d *ast.Diagnostic) bool {
684
688
  // convertDiagnostics translates shim-specific diagnostics into the plain
685
689
  // Diagnostic struct with line/column populated via tsgo's ECMALineMap (the
686
690
  // same helper tsc uses for its "file:line:col: message" banner).
691
+ //
692
+ // Diagnostics produced by a Program go through convertProgramDiagnostics
693
+ // instead, which undoes a source preamble's position shift first.
687
694
  func convertDiagnostics(in []*ast.Diagnostic) []Diagnostic {
688
695
  out := make([]Diagnostic, 0, len(in))
689
696
  for _, d := range in {
690
697
  if d == nil {
691
698
  continue
692
699
  }
693
- diag := Diagnostic{Code: d.Code(), Message: d.String(), raw: d}
694
- if file := d.File(); file != nil {
695
- diag.File = file.FileName()
696
- if pos := d.Pos(); pos >= 0 {
697
- length := d.Len()
698
- diag.Start = &pos
699
- diag.Length = &length
700
- line, col := shimscanner.GetECMALineAndByteOffsetOfPosition(file, pos)
701
- diag.Line = line + 1
702
- diag.Column = col + 1
703
- }
704
- }
705
- out = append(out, diag)
700
+ out = append(out, convertDiagnostic(d))
706
701
  }
707
702
  return out
708
703
  }
704
+
705
+ // convertDiagnostic translates one shim diagnostic, taking its file, line, and
706
+ // column from whatever source file the diagnostic is anchored to.
707
+ func convertDiagnostic(d *ast.Diagnostic) Diagnostic {
708
+ diag := Diagnostic{Code: d.Code(), Message: d.String(), raw: d}
709
+ if file := d.File(); file != nil {
710
+ diag.File = file.FileName()
711
+ if pos := d.Pos(); pos >= 0 {
712
+ length := d.Len()
713
+ diag.Start = &pos
714
+ diag.Length = &length
715
+ line, col := shimscanner.GetECMALineAndByteOffsetOfPosition(file, pos)
716
+ diag.Line = line + 1
717
+ diag.Column = col + 1
718
+ }
719
+ }
720
+ return diag
721
+ }
package/driver/rewrite.go CHANGED
@@ -132,7 +132,7 @@ func (p *Program) EmitAllRaw(writeFile shimcompiler.WriteFile) (*shimcompiler.Em
132
132
  result := p.TSProgram.Emit(context.Background(), shimcompiler.EmitOptions{
133
133
  WriteFile: wf,
134
134
  })
135
- return result, convertDiagnostics(result.Diagnostics), nil
135
+ return result, p.convertProgramDiagnostics(result.Diagnostics), nil
136
136
  }
137
137
 
138
138
  // EmitFile runs tsgo's emitter for one source file, applying the same rewrite
@@ -201,7 +201,7 @@ func (p *Program) emit(rs *RewriteSet, target *ast.SourceFile, writeFile shimcom
201
201
  TargetSourceFile: target,
202
202
  WriteFile: wf,
203
203
  })
204
- return result, convertDiagnostics(result.Diagnostics), nil
204
+ return result, p.convertProgramDiagnostics(result.Diagnostics), nil
205
205
  }
206
206
 
207
207
  // DefaultWriteFile is the default disk writer used when EmitAll's caller does not
@@ -3,9 +3,10 @@ package graph
3
3
  import "github.com/samchon/ttsc/packages/ttsc/driver"
4
4
 
5
5
  // FileDiagnostics returns the resident program's compiler diagnostics for the
6
- // source file at path, in the same code and location tsgo reports. Because the
7
- // graph rides the already-open Program, this is one Program.Diagnostics() call
8
- // over the warm checker, not a second compile.
6
+ // source file at path, with the code tsgo reports and the location the driver
7
+ // resolves (see Diagnostic: authored-relative under a source preamble). Because
8
+ // the graph rides the already-open Program, this is one Program.Diagnostics()
9
+ // call over the warm checker, not a second compile.
9
10
  //
10
11
  // This is the compiler-only slice: it carries the TypeScript semantic
11
12
  // diagnostics, not @ttsc/lint or transform-plugin findings.
@@ -179,7 +179,16 @@ type SourceDigest struct {
179
179
  }
180
180
 
181
181
  // Diagnostic is one compiler diagnostic riding the snapshot that produced the
182
- // facts. Positions are 1-based, matching what tsgo reports.
182
+ // facts. Positions are 1-based and authored-relative: where a SourcePreamble
183
+ // plugin injected text ahead of a file, the driver maps the position back onto
184
+ // the bytes a consumer can read for itself (the file this dump publishes a Disk
185
+ // digest of), rather than onto the augmented text the checker saw. Node spans in
186
+ // this dump stay checker-relative by declaration — see SourceDigest — because
187
+ // they describe the facts, while a diagnostic is an instruction to go look at a
188
+ // line.
189
+ //
190
+ // A diagnostic that points inside the injected preamble itself has no authored
191
+ // counterpart, so it arrives with Line and Column zero and its message intact.
183
192
  type Diagnostic struct {
184
193
  // File uses the dump's path vocabulary.
185
194
  File string `json:"file"`
@@ -91,6 +91,13 @@ function compileProjectWithPlugins(options, cwd, project) {
91
91
  emit: true,
92
92
  forceListEmittedFiles: true,
93
93
  outDir: tempOutDir,
94
+ // The temp directory is an `outDir` this lane injected so the emit can be
95
+ // read back as strings; the project need not declare one at all. tsgo
96
+ // answers an inferred common source directory with TS5011 as soon as any
97
+ // `outDir` is in play, so pin the root it would infer. The keys
98
+ // `outputKeyMapper` builds are unchanged by it — that root is exactly the
99
+ // layout tsgo already lays the emit out against (issue #1172).
100
+ pinInferredRootDir: true,
94
101
  quiet: true,
95
102
  resolvedProject: project,
96
103
  structuredDiagnostics: true,
@@ -1 +1 @@
1
- {"version":3,"file":"compileProjectInMemory.js","sourceRoot":"","sources":["../../../src/compiler/internal/compileProjectInMemory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compileProjectInMemory.js","sourceRoot":"","sources":["../../../src/compiler/internal/compileProjectInMemory.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,34 @@ export type RunBuildOptions = TtscBuildOptions & {
11
11
  forceListEmittedFiles?: boolean;
12
12
  /** Keep every compiler-owned side product inside this private directory. */
13
13
  isolateOutputsTo?: string;
14
+ /**
15
+ * Hand tsgo the `rootDir` it would otherwise infer, for a build whose
16
+ * `outDir` this process injected rather than the project declaring it.
17
+ *
18
+ * Tsgo answers an inferred common source directory with TS5011 the moment an
19
+ * `outDir` is in play, so injecting one turns a project that declares no
20
+ * output at all — the `noEmit` check-only shape `tsgo`, `ttsc`, and `ttsc
21
+ * --emit` all accept — into one that must configure the layout of output the
22
+ * user never asked for and never sees (issue #1172).
23
+ *
24
+ * The pinned value is the one tsgo itself infers: with a config file in play
25
+ * its common source directory is that file's directory, never the computed
26
+ * common directory of the input files (`outputpaths.GetCommonSourceDirectory`
27
+ * consults the file list only for a config-less program). Pinning it
28
+ * therefore silences the demand without moving a single output, and it is the
29
+ * same source root `prepareExecution.ts::resolveRuntimeSourceRoot`,
30
+ * `runtimeHooks.ts::resolveDependencySourceRoot`, and
31
+ * `watchTopology.ts::inferPerSourceCompilerOutputs` already model on the
32
+ * JavaScript side.
33
+ *
34
+ * Ignored when the project declares its own `rootDir`: that project already
35
+ * satisfies tsgo, and overriding it would relocate the emit out from under
36
+ * every consumer that mirrors the declared root.
37
+ *
38
+ * Never set for a user-supplied `--outDir`. That outDir is the user's own
39
+ * request, and TS5011 is then tsgo's genuine answer to it.
40
+ */
41
+ pinInferredRootDir?: boolean;
14
42
  /**
15
43
  * Receives selected native-plugin source roots after the project resolves.
16
44
  * The watch launcher uses these roots to invalidate a sidecar when its Go
@@ -861,6 +861,9 @@ function createTsgoBuildArgs(execution, options, flags) {
861
861
  if (options.forceRuntimeSourceMap === true) {
862
862
  args.push("--sourceMap", "true");
863
863
  }
864
+ // Pushed before passthrough for the same reason: a user `--rootDir` still
865
+ // wins over the value ttsc pins for its own injected `outDir`.
866
+ args.push(...pinnedRootDirArgs(execution, options));
864
867
  }
865
868
  else if (options.emit === false) {
866
869
  args.push("--noEmit");
@@ -880,6 +883,36 @@ function createTsgoBuildArgs(execution, options, flags) {
880
883
  }
881
884
  return args;
882
885
  }
886
+ /**
887
+ * The explicit `--rootDir` a caller-injected `outDir` needs, or nothing.
888
+ *
889
+ * See {@link RunBuildOptions.pinInferredRootDir} for why an injected `outDir`
890
+ * needs one at all. Three conditions gate it, and each one is load-bearing:
891
+ *
892
+ * - The caller injected the `outDir` itself, so a user `--outDir` keeps tsgo's
893
+ * own TS5011 answer;
894
+ * - This pass emits, because a no-emit pass has no layout to pin (tsgo skips the
895
+ * check for `noEmit` too);
896
+ * - The project declares no `rootDir` of its own, so a declared layout is never
897
+ * overridden.
898
+ *
899
+ * `execution.projectRoot` is the directory of the tsconfig ttsc resolved and
900
+ * hands tsgo as `-p`, which is exactly the directory tsgo infers, and it is
901
+ * spelled the way tsgo will spell the input file names it compares against it —
902
+ * both come from the same `fs.realpathSync` pass. Resolving it any further (a
903
+ * Windows 8.3 expansion, say) would leave the comparison lexically mixed, and
904
+ * `ContainsPath` is lexical: every input would count as outside `rootDir` and
905
+ * tsgo would emit it beside the user's source instead of under `outDir`.
906
+ */
907
+ function pinnedRootDirArgs(execution, options) {
908
+ if (options.pinInferredRootDir !== true)
909
+ return [];
910
+ if (options.emit !== true)
911
+ return [];
912
+ if (typeof execution.project.compilerOptions.rootDir === "string")
913
+ return [];
914
+ return ["--rootDir", execution.projectRoot];
915
+ }
883
916
  /**
884
917
  * Return `["--pretty", "false"]` when structured diagnostics are requested so
885
918
  * that the output can be parsed line-by-line, or an empty array otherwise.
@@ -945,7 +978,10 @@ function createNativeBuildArgs(execution, options, plugins) {
945
978
  args.push("--quiet");
946
979
  }
947
980
  }
948
- args.push(...createNativeTsgoArgs(options));
981
+ // The sidecar builds its Program in-process, so the pinned `rootDir` has to
982
+ // travel the same channel every other tsgo option takes to it — the host flag
983
+ // set does not declare `--rootDir`, and `filterHostArgs` would strip it.
984
+ args.push(...createNativeTsgoArgs(options, pinnedRootDirArgs(execution, options)));
949
985
  return args;
950
986
  }
951
987
  /** Build the argument list for a native plugin check/fix/format invocation. */
@@ -1059,8 +1095,11 @@ function transformHostTimingLabel(plugins) {
1059
1095
  * a plugin build the same way it reaches the plain tsgo lane. Encoded as a
1060
1096
  * single token so the sidecars' unknown-flag filters keep it intact.
1061
1097
  */
1062
- function createNativeTsgoArgs(options) {
1098
+ function createNativeTsgoArgs(options, leading = []) {
1063
1099
  const passthrough = [
1100
+ // Ahead of the user's own flags, so the same precedence holds here as on
1101
+ // the direct tsgo lane: whatever the user forwarded wins.
1102
+ ...leading,
1064
1103
  ...(nativeTsgoPassthroughArgs(options) ?? []),
1065
1104
  ...isolatedTsgoOutputArgs(options),
1066
1105
  ];