ttsc 0.26.1 → 0.27.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.
- package/README.md +10 -1
- package/cmd/ttsc/api_transform.go +17 -3
- package/cmd/ttscgraph/auxiliary_identity_state_tracks_retargets_not_source_contents_test.go +85 -0
- package/cmd/ttscgraph/disk_realpath_other.go +16 -0
- package/cmd/ttscgraph/disk_realpath_windows.go +56 -0
- package/cmd/ttscgraph/serve.go +104 -35
- package/driver/diagnostics_preamble.go +284 -0
- package/driver/emit_plugin.go +71 -19
- package/driver/graph.go +64 -11
- package/driver/input_observation.go +273 -0
- package/driver/input_observation_test.go +149 -0
- package/driver/plugins.go +384 -4
- package/driver/program.go +81 -15
- package/driver/resolution_candidates.go +13 -5
- package/driver/rewrite.go +47 -4
- package/internal/graph/diagnostics.go +4 -3
- package/internal/graph/provenance.go +10 -1
- package/lib/TtscCompiler.d.ts +12 -11
- package/lib/TtscCompiler.js +44 -29
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/captureProcessOutput.js +2 -2
- package/lib/compiler/internal/captureProcessOutput.js.map +1 -1
- package/lib/compiler/internal/compileProjectInMemory.js +11 -3
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.js +3 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +29 -1
- package/lib/compiler/internal/runBuild.js +117 -32
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.js +9 -2
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +28 -0
- package/lib/compiler/internal/sharedHostHelpers.js +39 -0
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.js +12 -4
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.d.ts +3 -0
- package/lib/compiler/internal/transformProjectInMemory.js +239 -35
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/schema.js +33 -5
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/createCanonicalTempDirectory.d.ts +17 -0
- package/lib/internal/createCanonicalTempDirectory.js +42 -0
- package/lib/internal/createCanonicalTempDirectory.js.map +1 -0
- package/lib/internal/projectInputPathIdentity.d.ts +3 -0
- package/lib/internal/projectInputPathIdentity.js +11 -5
- package/lib/internal/projectInputPathIdentity.js.map +1 -1
- package/lib/internal/resolveNodeBinary.d.ts +14 -0
- package/lib/internal/resolveNodeBinary.js +150 -0
- package/lib/internal/resolveNodeBinary.js.map +1 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.d.ts +15 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.js +61 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.js.map +1 -0
- package/lib/launcher/internal/assertNoSolutionBuild.d.ts +31 -0
- package/lib/launcher/internal/assertNoSolutionBuild.js +38 -0
- package/lib/launcher/internal/assertNoSolutionBuild.js.map +1 -0
- package/lib/launcher/internal/prepareExecution.d.ts +3 -0
- package/lib/launcher/internal/prepareExecution.js +73 -26
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +27 -13
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +18 -3
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +4 -0
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +37 -1
- package/lib/launcher/internal/runtimeHooks.js +660 -62
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.d.ts +62 -8
- package/lib/plugin/internal/buildSourcePlugin.js +754 -72
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +17 -5
- package/lib/plugin/internal/loadProjectPlugins.js +1339 -77
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/register.d.ts +1 -0
- package/lib/register.js +46 -0
- package/lib/register.js.map +1 -0
- package/lib/structures/ITtscCompilerContext.d.ts +6 -3
- package/lib/structures/ITtscCompilerTransformation.d.ts +31 -0
- package/lib/structures/ITtscPlugin.d.ts +33 -0
- package/package.json +16 -9
- package/shim/checker/go.mod +9 -0
- package/shim/checker/go.sum +22 -0
- package/shim/checker/shim.go +75 -0
- package/shim/compiler/emit_sourcemap.go +56 -15
- package/shim/compiler/incremental.go +45 -8
- package/src/TtscCompiler.ts +55 -33
- package/src/compiler/internal/captureProcessOutput.ts +3 -2
- package/src/compiler/internal/compileProjectInMemory.ts +11 -3
- package/src/compiler/internal/resolveEmittedJavaScript.ts +6 -0
- package/src/compiler/internal/runBuild.ts +172 -36
- package/src/compiler/internal/runSingleFileEmit.ts +9 -2
- package/src/compiler/internal/sharedHostHelpers.ts +44 -0
- package/src/compiler/internal/startResidentTransform.ts +12 -3
- package/src/compiler/internal/transformProjectInMemory.ts +410 -39
- package/src/flags/schema.ts +35 -5
- package/src/internal/createCanonicalTempDirectory.ts +53 -0
- package/src/internal/projectInputPathIdentity.ts +17 -4
- package/src/internal/resolveNodeBinary.ts +173 -0
- package/src/internal/resolveSafeCacheCleanupTargets.ts +85 -0
- package/src/launcher/internal/assertNoSolutionBuild.ts +40 -0
- package/src/launcher/internal/prepareExecution.ts +78 -26
- package/src/launcher/internal/runTtsc.ts +26 -13
- package/src/launcher/internal/runTtscserver.ts +18 -3
- package/src/launcher/internal/runTtsx.ts +4 -0
- package/src/launcher/internal/runtimeHooks.ts +830 -66
- package/src/plugin/internal/buildSourcePlugin.ts +984 -79
- package/src/plugin/internal/loadProjectPlugins.ts +1718 -104
- package/src/register.ts +47 -0
- package/src/structures/ITtscCompilerContext.ts +6 -3
- package/src/structures/ITtscCompilerTransformation.ts +39 -0
- package/src/structures/ITtscPlugin.ts +36 -0
- package/tools/shim_audit/baseline.json +0 -1
- package/utility/host.go +9 -3
- package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +0 -5
- package/lib/internal/assertSafeExplicitCacheDirectory.js +0 -26
- package/lib/internal/assertSafeExplicitCacheDirectory.js.map +0 -1
- package/src/internal/assertSafeExplicitCacheDirectory.ts +0 -29
|
@@ -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
|
+
}
|
package/driver/emit_plugin.go
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package driver
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"context"
|
|
5
4
|
"errors"
|
|
6
5
|
"strings"
|
|
7
6
|
"sync"
|
|
@@ -133,13 +132,16 @@ func restoreOriginalDeclarationSymbols(ec *shimprinter.EmitContext, node *shimas
|
|
|
133
132
|
// EmitTransformPlugins are chained after the caller's transforms in
|
|
134
133
|
// registration order.
|
|
135
134
|
//
|
|
136
|
-
// Because the JavaScript side bypasses tsgo's own emitter, it reproduces
|
|
137
|
-
// emitter's
|
|
138
|
-
// `inlineSourceMap` build emits a `.js.map` (and
|
|
139
|
-
// trailer) just like a plain build, even when a
|
|
140
|
-
// line into many
|
|
141
|
-
//
|
|
142
|
-
//
|
|
135
|
+
// Because the JavaScript side bypasses tsgo's own emitter, it reproduces that
|
|
136
|
+
// emitter's whole printSourceFile step via PrintFileWithSourceMap: a
|
|
137
|
+
// `sourceMap` / `inlineSourceMap` build emits a `.js.map` (and
|
|
138
|
+
// `//# sourceMappingURL=` trailer) just like a plain build, even when a
|
|
139
|
+
// transform expanded one source line into many; an `emitBOM` build still starts
|
|
140
|
+
// with the byte order mark; and the caller's WriteFile still receives the
|
|
141
|
+
// WriteFileData the emitter would hand it. All non-JavaScript outputs stay
|
|
142
|
+
// delegated to tsgo's normal dts-only emitter so declaration files, declaration
|
|
143
|
+
// maps, and any future declaration-lane outputs are not silently lost by the
|
|
144
|
+
// hand-assembled JS path.
|
|
143
145
|
func (p *Program) EmitWithPluginTransformers(transforms []PluginTransform, writeFile shimcompiler.WriteFile) ([]Diagnostic, error) {
|
|
144
146
|
if p == nil || p.TSProgram == nil {
|
|
145
147
|
return nil, errors.New("driver: nil program")
|
|
@@ -183,10 +185,10 @@ func (p *Program) EmitWithPluginTransformers(transforms []PluginTransform, write
|
|
|
183
185
|
}
|
|
184
186
|
// Print through the source-map-aware helper so a `sourceMap` /
|
|
185
187
|
// `inlineSourceMap` build still gets its `.js.map` and sourceMappingURL
|
|
186
|
-
// trailer
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
// before.
|
|
188
|
+
// trailer, and an `emitBOM` build its leading mark: the hand-assembled
|
|
189
|
+
// emit pipeline does not run tsgo's emitter, so everything printSourceFile
|
|
190
|
+
// would otherwise do around the printer has to happen here. With maps and
|
|
191
|
+
// emitBOM off this is the same bare-printer output as before.
|
|
190
192
|
printed := shimcompiler.PrintFileWithSourceMap(ec, out.AsNode(), out, options, host, paths.JsFilePath(), paths.SourceMapFilePath())
|
|
191
193
|
// A source-level preamble (e.g. @ttsc/banner linked into a typia host)
|
|
192
194
|
// shifts the map's source coordinates; correct them here too, so the
|
|
@@ -204,20 +206,45 @@ func (p *Program) EmitWithPluginTransformers(transforms []PluginTransform, write
|
|
|
204
206
|
}
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
|
-
|
|
209
|
+
// The emitter hands its writeFile callback a WriteFileData for the
|
|
210
|
+
// JavaScript and a nil one for the map (printSourceFile:
|
|
211
|
+
// `writeText(sourceMapFilePath, sourceMap, nil)`), so this lane does the
|
|
212
|
+
// same. See writePluginEmitOutput for what the struct carries here and
|
|
213
|
+
// why the remaining fields stay zero.
|
|
214
|
+
if err := p.writePluginEmitOutput(paths.JsFilePath(), printed.JS, &shimcompiler.WriteFileData{
|
|
215
|
+
SourceMapUrlPos: printed.SourceMapUrlPos,
|
|
216
|
+
}, writeFile); err != nil {
|
|
208
217
|
return nil, err
|
|
209
218
|
}
|
|
210
|
-
if err := p.writePluginEmitOutput(printed.MapPath, printed.MapText, writeFile); err != nil {
|
|
219
|
+
if err := p.writePluginEmitOutput(printed.MapPath, printed.MapText, nil, writeFile); err != nil {
|
|
211
220
|
return nil, err
|
|
212
221
|
}
|
|
213
222
|
}
|
|
214
223
|
}
|
|
215
|
-
|
|
224
|
+
// The declaration pass below doubles as this lane's build-information pass,
|
|
225
|
+
// so it also runs for a JavaScript-only `incremental` / `composite` project
|
|
226
|
+
// that has no declarations to write at all.
|
|
227
|
+
if !options.GetEmitDeclarations() && !p.emitsBuildInfo() {
|
|
216
228
|
return nil, nil
|
|
217
229
|
}
|
|
218
230
|
|
|
231
|
+
// What the build information this pass writes does and does not claim.
|
|
232
|
+
//
|
|
233
|
+
// The JavaScript above was hand-assembled and written outside tsgo's
|
|
234
|
+
// emitter, so tsgo's snapshot never saw it happen and records the JS emit of
|
|
235
|
+
// every file as still pending. That error is one-directional and safe: a
|
|
236
|
+
// consumer reading it can only decide to emit again, never to skip a file
|
|
237
|
+
// ttsc actually transformed. Making the record exact would mean running
|
|
238
|
+
// tsgo's own JavaScript emit a second time and discarding its output, paying
|
|
239
|
+
// a full emit to describe work already done. Everything else in the file —
|
|
240
|
+
// the compiler version, the resolved options, per-file versions and
|
|
241
|
+
// signatures, and the declaration state this pass does produce — is accurate.
|
|
242
|
+
//
|
|
243
|
+
// ttsc itself never reads build information back (see
|
|
244
|
+
// `shimcompiler.EmitFreshWithBuildInfo`), so this asymmetry costs a ttsc
|
|
245
|
+
// rebuild nothing.
|
|
219
246
|
var wfMu sync.Mutex
|
|
220
|
-
result := p.
|
|
247
|
+
result := p.emitProgram(shimcompiler.EmitOptions{
|
|
221
248
|
EmitOnly: shimcompiler.EmitOnlyDts,
|
|
222
249
|
WriteFile: func(fileName string, text string, data *shimcompiler.WriteFileData) error {
|
|
223
250
|
wfMu.Lock()
|
|
@@ -235,17 +262,42 @@ func (p *Program) EmitWithPluginTransformers(transforms []PluginTransform, write
|
|
|
235
262
|
},
|
|
236
263
|
})
|
|
237
264
|
if result != nil && len(result.Diagnostics) != 0 {
|
|
238
|
-
return
|
|
265
|
+
return p.convertProgramDiagnostics(result.Diagnostics), nil
|
|
239
266
|
}
|
|
240
267
|
return nil, nil
|
|
241
268
|
}
|
|
242
269
|
|
|
243
|
-
|
|
270
|
+
// writePluginEmitOutput writes one artifact of the hand-assembled emit, passing
|
|
271
|
+
// the caller's WriteFile the same WriteFileData tsgo's emitter would.
|
|
272
|
+
//
|
|
273
|
+
// What this lane can populate, and what it deliberately cannot:
|
|
274
|
+
//
|
|
275
|
+
// - SourceMapUrlPos: the offset of the `//# sourceMappingURL=` trailer, or -1
|
|
276
|
+
// when none was written. PrintFileWithSourceMap records it exactly where
|
|
277
|
+
// printSourceFile does, so a consumer that relocates or rewrites the trailer
|
|
278
|
+
// works the same on both lanes. This is why a nil data was a real loss and
|
|
279
|
+
// not merely a cosmetic one.
|
|
280
|
+
// - Diagnostics: always empty. The emitter's field carries its accumulated
|
|
281
|
+
// emitterDiagnostics, which on the JavaScript lane are its own write
|
|
282
|
+
// failures; here a write failure is returned as an `error` from
|
|
283
|
+
// EmitWithPluginTransformers instead, and the declaration lane's diagnostics
|
|
284
|
+
// reach the caller through tsgo's own EmitOnlyDts pass and its own
|
|
285
|
+
// WriteFileData. Empty therefore means "nothing to report", not "never
|
|
286
|
+
// populated".
|
|
287
|
+
// - BuildInfo: always nil. It is the `.tsbuildinfo` payload, and this lane
|
|
288
|
+
// emits only JavaScript and its map.
|
|
289
|
+
// - SkippedDtsWrite: an out-parameter for the callee, left zero. Each write
|
|
290
|
+
// gets its own struct so one file's callback cannot observe another's.
|
|
291
|
+
//
|
|
292
|
+
// Nothing on this lane reads the struct back afterwards: unlike tsgo's emitter,
|
|
293
|
+
// EmitWithPluginTransformers builds no EmitResult, so there is no EmittedFiles
|
|
294
|
+
// list for a callee-set SkippedDtsWrite to keep a file out of.
|
|
295
|
+
func (p *Program) writePluginEmitOutput(fileName, text string, data *shimcompiler.WriteFileData, writeFile shimcompiler.WriteFile) error {
|
|
244
296
|
if fileName == "" || p.outputEscapesOutDir(fileName) {
|
|
245
297
|
return nil
|
|
246
298
|
}
|
|
247
299
|
if writeFile != nil {
|
|
248
|
-
return writeFile(fileName, text,
|
|
300
|
+
return writeFile(fileName, text, data)
|
|
249
301
|
}
|
|
250
302
|
return DefaultWriteFile(fileName, text)
|
|
251
303
|
}
|
package/driver/graph.go
CHANGED
|
@@ -23,9 +23,10 @@ const bundledScheme = "bundled:///"
|
|
|
23
23
|
//
|
|
24
24
|
// - Edges maps each file to its direct resolved references — imports,
|
|
25
25
|
// re-exports, `/// <reference>` targets, type reference directives, and
|
|
26
|
-
// ambient-module declaration files, type-only edges included.
|
|
27
|
-
//
|
|
28
|
-
//
|
|
26
|
+
// ambient-module declaration files, type-only edges included. A leaf file
|
|
27
|
+
// has an empty list so the node and its compiler-time input proof remain
|
|
28
|
+
// explicit. Direct edges are the minimal sufficient statistic; consumers
|
|
29
|
+
// that need a flat per-file list compute the reachability closure themselves.
|
|
29
30
|
// - Globals lists the files that contribute to the global scope (ambient
|
|
30
31
|
// declaration files, script files, global augmentations, `typeRoots`
|
|
31
32
|
// entries). A change to any of them can affect every file.
|
|
@@ -39,10 +40,12 @@ const bundledScheme = "bundled:///"
|
|
|
39
40
|
// map: project-relative slash paths, falling back to slash-normalized
|
|
40
41
|
// absolute paths outside the project root (see TransformOutputKey).
|
|
41
42
|
type TransformGraph struct {
|
|
42
|
-
Edges
|
|
43
|
-
Globals
|
|
44
|
-
Configs
|
|
45
|
-
Candidates
|
|
43
|
+
Edges map[string][]string `json:"edges"`
|
|
44
|
+
Globals []string `json:"globals"`
|
|
45
|
+
Configs []string `json:"configs"`
|
|
46
|
+
Candidates map[string][]string `json:"candidates,omitempty"`
|
|
47
|
+
InputHashes map[string]*string `json:"inputHashes,omitempty"`
|
|
48
|
+
InputRealpaths map[string]*string `json:"inputRealpaths,omitempty"`
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
// NewTransformGraph computes the reference graph of a loaded program, keyed
|
|
@@ -69,16 +72,66 @@ func NewTransformGraph(prog *Program, cwd string) *TransformGraph {
|
|
|
69
72
|
if shimcompiler.FileAffectsGlobalScope(file) {
|
|
70
73
|
graph.Globals = append(graph.Globals, key)
|
|
71
74
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
// Keep leaf modules as empty adjacency-list entries. Besides making the
|
|
76
|
+
// graph's node universe explicit, this lets attachInputProof bind every
|
|
77
|
+
// source file to the bytes TypeScript-Go actually read. Omitting a leaf
|
|
78
|
+
// would let an A-B-A edit during compilation pair B's output with identical
|
|
79
|
+
// pre/post project snapshots for A.
|
|
80
|
+
graph.Edges[key] = referenceTargets(prog, cwd, file)
|
|
76
81
|
}
|
|
77
82
|
sort.Strings(graph.Globals)
|
|
78
83
|
graph.Configs = configChain(prog, cwd)
|
|
84
|
+
graph.attachInputProof(prog, cwd)
|
|
79
85
|
return graph
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
// attachInputProof pairs every graph path with the state the compiler
|
|
89
|
+
// filesystem actually returned while constructing the resident Program. A
|
|
90
|
+
// missing entry means proof was incomplete or contradictory; persistent hosts
|
|
91
|
+
// then keep the fresh result but decline cross-build reuse.
|
|
92
|
+
func (graph *TransformGraph) attachInputProof(prog *Program, cwd string) {
|
|
93
|
+
if prog == nil || prog.inputObserver == nil {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
inputs := map[string]struct{}{}
|
|
97
|
+
for source, targets := range graph.Edges {
|
|
98
|
+
inputs[source] = struct{}{}
|
|
99
|
+
for _, target := range targets {
|
|
100
|
+
inputs[target] = struct{}{}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for _, input := range graph.Globals {
|
|
104
|
+
inputs[input] = struct{}{}
|
|
105
|
+
}
|
|
106
|
+
for _, input := range graph.Configs {
|
|
107
|
+
inputs[input] = struct{}{}
|
|
108
|
+
}
|
|
109
|
+
for source, candidates := range graph.Candidates {
|
|
110
|
+
inputs[source] = struct{}{}
|
|
111
|
+
for _, candidate := range candidates {
|
|
112
|
+
inputs[candidate] = struct{}{}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
hashes := map[string]*string{}
|
|
116
|
+
realpaths := map[string]*string{}
|
|
117
|
+
for input := range inputs {
|
|
118
|
+
file := filepath.FromSlash(input)
|
|
119
|
+
if !filepath.IsAbs(file) {
|
|
120
|
+
file = filepath.Join(cwd, file)
|
|
121
|
+
}
|
|
122
|
+
hash, realpath, ok := prog.inputObserver.proof(file)
|
|
123
|
+
if !ok {
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
hashes[input] = hash
|
|
127
|
+
realpaths[input] = realpath
|
|
128
|
+
}
|
|
129
|
+
if len(hashes) != 0 {
|
|
130
|
+
graph.InputHashes = hashes
|
|
131
|
+
graph.InputRealpaths = realpaths
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
82
135
|
// referenceTargets resolves one file's direct reference set to sorted envelope
|
|
83
136
|
// keys, dropping bundled library files and the file itself.
|
|
84
137
|
func referenceTargets(prog *Program, cwd string, file *ast.SourceFile) []string {
|