ttsc 0.7.3 → 0.8.0-dev.20260505

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 (33) hide show
  1. package/README.md +3 -4
  2. package/driver/rewrite.go +41 -0
  3. package/lib/compiler/internal/runBuild.js +47 -49
  4. package/lib/compiler/internal/runBuild.js.map +1 -1
  5. package/lib/compiler/internal/transformProjectInMemory.js +44 -4
  6. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  7. package/lib/launcher/internal/runTtsc.js +19 -1
  8. package/lib/launcher/internal/runTtsc.js.map +1 -1
  9. package/lib/plugin/internal/buildSourcePlugin.js +6 -0
  10. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  11. package/lib/plugin/internal/loadProjectPlugins.js +55 -2
  12. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  13. package/lib/structures/ITtscPlugin.d.ts +19 -3
  14. package/lib/structures/ITtscPlugin.js +4 -0
  15. package/lib/structures/ITtscPlugin.js.map +1 -1
  16. package/lib/structures/TtscPluginStage.d.ts +4 -10
  17. package/lib/structures/internal/ITtscLoadedNativePlugin.d.ts +5 -0
  18. package/package.json +11 -10
  19. package/shim/ast/lint.go +1 -0
  20. package/shim/ast/shim.go +1 -0
  21. package/shim/compiler/emit_hooks.go +494 -0
  22. package/shim/core/shim.go +3 -0
  23. package/src/compiler/internal/runBuild.ts +54 -85
  24. package/src/compiler/internal/transformProjectInMemory.ts +51 -6
  25. package/src/launcher/internal/runTtsc.ts +17 -1
  26. package/src/plugin/internal/buildSourcePlugin.ts +23 -0
  27. package/src/plugin/internal/loadProjectPlugins.ts +81 -2
  28. package/src/structures/ITtscPlugin.ts +30 -3
  29. package/src/structures/TtscPluginStage.ts +4 -10
  30. package/src/structures/internal/ITtscLoadedNativePlugin.ts +5 -0
  31. package/test/compiler.test.ts +3 -3
  32. package/test/tsgo.test.ts +9 -6
  33. package/utility/host.go +707 -0
@@ -0,0 +1,494 @@
1
+ package compiler
2
+
3
+ import (
4
+ "context"
5
+
6
+ "github.com/microsoft/typescript-go/internal/ast"
7
+ "github.com/microsoft/typescript-go/internal/binder"
8
+ innercompiler "github.com/microsoft/typescript-go/internal/compiler"
9
+ "github.com/microsoft/typescript-go/internal/core"
10
+ "github.com/microsoft/typescript-go/internal/diagnostics"
11
+ "github.com/microsoft/typescript-go/internal/module"
12
+ "github.com/microsoft/typescript-go/internal/outputpaths"
13
+ "github.com/microsoft/typescript-go/internal/packagejson"
14
+ "github.com/microsoft/typescript-go/internal/printer"
15
+ "github.com/microsoft/typescript-go/internal/sourcemap"
16
+ "github.com/microsoft/typescript-go/internal/stringutil"
17
+ "github.com/microsoft/typescript-go/internal/symlinks"
18
+ "github.com/microsoft/typescript-go/internal/transformers"
19
+ "github.com/microsoft/typescript-go/internal/transformers/declarations"
20
+ "github.com/microsoft/typescript-go/internal/transformers/estransforms"
21
+ "github.com/microsoft/typescript-go/internal/transformers/inliners"
22
+ "github.com/microsoft/typescript-go/internal/transformers/jsxtransforms"
23
+ "github.com/microsoft/typescript-go/internal/transformers/moduletransforms"
24
+ "github.com/microsoft/typescript-go/internal/transformers/tstransforms"
25
+ "github.com/microsoft/typescript-go/internal/tsoptions"
26
+ "github.com/microsoft/typescript-go/internal/tspath"
27
+ )
28
+
29
+ type EmitContext = printer.EmitContext
30
+
31
+ type EmitSourceFileHook func(context *EmitContext, file *ast.SourceFile) *ast.SourceFile
32
+
33
+ type EmitHooks struct {
34
+ BeforePrintDeclaration EmitSourceFileHook
35
+ BeforePrintJS EmitSourceFileHook
36
+ }
37
+
38
+ type hookEmitHost struct {
39
+ program *innercompiler.Program
40
+ emitResolver printer.EmitResolver
41
+ }
42
+
43
+ type hookEmitter struct {
44
+ host *hookEmitHost
45
+ emitOnly innercompiler.EmitOnly
46
+ emitResult innercompiler.EmitResult
47
+ hooks EmitHooks
48
+ paths *outputpaths.OutputPaths
49
+ sourceFile *ast.SourceFile
50
+ writeFile innercompiler.WriteFile
51
+ writer printer.EmitTextWriter
52
+ }
53
+
54
+ func EmitWithHooks(ctx context.Context, program *innercompiler.Program, options innercompiler.EmitOptions, hooks EmitHooks) *innercompiler.EmitResult {
55
+ if program == nil {
56
+ return nil
57
+ }
58
+ if options.EmitOnly != innercompiler.EmitOnlyForcedDts {
59
+ result := HandleNoEmitOnError(ctx, program, options.TargetSourceFile)
60
+ if result != nil || ctx.Err() != nil {
61
+ return result
62
+ }
63
+ }
64
+ var results []*innercompiler.EmitResult
65
+ newLine := program.Options().NewLine.GetNewLineCharacter()
66
+ for _, sourceFile := range hookSourceFilesToEmit(program, options.TargetSourceFile, options.EmitOnly == innercompiler.EmitOnlyForcedDts) {
67
+ host, done := newHookEmitHost(ctx, program, sourceFile)
68
+ emitter := &hookEmitter{
69
+ host: host,
70
+ emitOnly: options.EmitOnly,
71
+ hooks: hooks,
72
+ paths: outputpaths.GetOutputPathsFor(sourceFile, host.Options(), host, options.EmitOnly == innercompiler.EmitOnlyForcedDts),
73
+ sourceFile: sourceFile,
74
+ writeFile: options.WriteFile,
75
+ writer: printer.NewTextWriter(newLine, 0),
76
+ }
77
+ emitter.emit()
78
+ done()
79
+ results = append(results, &emitter.emitResult)
80
+ }
81
+ return CombineEmitResults(results)
82
+ }
83
+
84
+ func hookSourceFilesToEmit(program *innercompiler.Program, target *ast.SourceFile, forceDtsEmit bool) []*ast.SourceFile {
85
+ if target != nil {
86
+ if program.SourceFileMayBeEmitted(target, forceDtsEmit) {
87
+ return []*ast.SourceFile{target}
88
+ }
89
+ return nil
90
+ }
91
+ var out []*ast.SourceFile
92
+ for _, sourceFile := range program.SourceFiles() {
93
+ if program.SourceFileMayBeEmitted(sourceFile, forceDtsEmit) {
94
+ out = append(out, sourceFile)
95
+ }
96
+ }
97
+ return out
98
+ }
99
+
100
+ func newHookEmitHost(ctx context.Context, program *innercompiler.Program, file *ast.SourceFile) (*hookEmitHost, func()) {
101
+ checker, done := program.GetTypeCheckerForFile(ctx, file)
102
+ return &hookEmitHost{
103
+ program: program,
104
+ emitResolver: checker.GetEmitResolver(),
105
+ }, done
106
+ }
107
+
108
+ func (e *hookEmitter) emit() {
109
+ e.emitJSFile(e.sourceFile, e.paths.JsFilePath(), e.paths.SourceMapFilePath())
110
+ e.emitDeclarationFile(e.sourceFile, e.paths.DeclarationFilePath(), e.paths.DeclarationMapPath())
111
+ e.emitResult.Diagnostics = e.emitterDiagnostics()
112
+ }
113
+
114
+ func (e *hookEmitter) emitterDiagnostics() []*ast.Diagnostic {
115
+ return e.emitResult.Diagnostics
116
+ }
117
+
118
+ func (e *hookEmitter) addDiagnostic(diag *ast.Diagnostic) {
119
+ e.emitResult.Diagnostics = append(e.emitResult.Diagnostics, diag)
120
+ }
121
+
122
+ func (e *hookEmitter) runScriptTransformers(emitContext *printer.EmitContext, sourceFile *ast.SourceFile) *ast.SourceFile {
123
+ for _, transformer := range hookScriptTransformers(emitContext, e.host, sourceFile) {
124
+ sourceFile = transformer.TransformSourceFile(sourceFile)
125
+ }
126
+ return sourceFile
127
+ }
128
+
129
+ func (e *hookEmitter) runDeclarationTransformers(emitContext *printer.EmitContext, sourceFile *ast.SourceFile, declarationFilePath, declarationMapPath string) (*ast.SourceFile, []*ast.Diagnostic) {
130
+ transform := declarations.NewDeclarationTransformer(e.host, emitContext, e.host.Options(), declarationFilePath, declarationMapPath)
131
+ sourceFile = transform.TransformSourceFile(sourceFile)
132
+ return sourceFile, transform.GetDiagnostics()
133
+ }
134
+
135
+ func (e *hookEmitter) emitJSFile(sourceFile *ast.SourceFile, jsFilePath string, sourceMapFilePath string) {
136
+ options := e.host.Options()
137
+ if sourceFile == nil || e.emitOnly != innercompiler.EmitAll && e.emitOnly != innercompiler.EmitOnlyJs || len(jsFilePath) == 0 {
138
+ return
139
+ }
140
+ if options.NoEmit == core.TSTrue || e.host.IsEmitBlocked(jsFilePath) {
141
+ e.emitResult.EmitSkipped = true
142
+ return
143
+ }
144
+ emitContext, putEmitContext := printer.GetEmitContext()
145
+ defer putEmitContext()
146
+ sourceFile = e.runScriptTransformers(emitContext, sourceFile)
147
+ if e.hooks.BeforePrintJS != nil {
148
+ sourceFile = e.hooks.BeforePrintJS(emitContext, sourceFile)
149
+ }
150
+ printerOptions := printer.PrinterOptions{
151
+ RemoveComments: options.RemoveComments.IsTrue(),
152
+ NewLine: options.NewLine,
153
+ NoEmitHelpers: options.NoEmitHelpers.IsTrue(),
154
+ SourceMap: options.SourceMap.IsTrue(),
155
+ InlineSourceMap: options.InlineSourceMap.IsTrue(),
156
+ InlineSources: options.InlineSources.IsTrue(),
157
+ Target: options.Target,
158
+ }
159
+ p := printer.NewPrinter(printerOptions, printer.PrintHandlers{}, emitContext)
160
+ e.printSourceFile(jsFilePath, sourceMapFilePath, sourceFile, p, hookShouldEmitSourceMaps(options, sourceFile))
161
+ }
162
+
163
+ func (e *hookEmitter) emitDeclarationFile(sourceFile *ast.SourceFile, declarationFilePath string, declarationMapPath string) {
164
+ options := e.host.Options()
165
+ if sourceFile == nil || e.emitOnly == innercompiler.EmitOnlyJs || len(declarationFilePath) == 0 {
166
+ return
167
+ }
168
+ if e.emitOnly != innercompiler.EmitOnlyForcedDts && (options.NoEmit == core.TSTrue || e.host.IsEmitBlocked(declarationFilePath)) {
169
+ e.emitResult.EmitSkipped = true
170
+ return
171
+ }
172
+ emitContext, putEmitContext := printer.GetEmitContext()
173
+ defer putEmitContext()
174
+ sourceFile, diags := e.runDeclarationTransformers(emitContext, sourceFile, declarationFilePath, declarationMapPath)
175
+ if e.hooks.BeforePrintDeclaration != nil {
176
+ sourceFile = e.hooks.BeforePrintDeclaration(emitContext, sourceFile)
177
+ }
178
+ printerOptions := printer.PrinterOptions{
179
+ RemoveComments: options.RemoveComments.IsTrue(),
180
+ OnlyPrintJSDocStyle: true,
181
+ NewLine: options.NewLine,
182
+ NoEmitHelpers: options.NoEmitHelpers.IsTrue(),
183
+ SourceMap: options.DeclarationMap.IsTrue(),
184
+ InlineSourceMap: options.InlineSourceMap.IsTrue(),
185
+ InlineSources: options.InlineSources.IsTrue(),
186
+ }
187
+ p := printer.NewPrinter(printerOptions, printer.PrintHandlers{}, emitContext)
188
+ for _, diag := range diags {
189
+ e.addDiagnostic(diag)
190
+ }
191
+ e.printSourceFile(declarationFilePath, declarationMapPath, sourceFile, p, e.emitOnly != innercompiler.EmitOnlyForcedDts && hookShouldEmitDeclarationSourceMaps(options, sourceFile))
192
+ }
193
+
194
+ func (e *hookEmitter) printSourceFile(filePath string, sourceMapFilePath string, sourceFile *ast.SourceFile, printer_ *printer.Printer, shouldEmitSourceMaps bool) {
195
+ options := e.host.Options()
196
+ var sourceMapGenerator *sourcemap.Generator
197
+ if shouldEmitSourceMaps {
198
+ sourceMapGenerator = sourcemap.NewGenerator(
199
+ tspath.GetBaseFileName(tspath.NormalizeSlashes(filePath)),
200
+ hookSourceRoot(options),
201
+ e.sourceMapDirectory(options, filePath, sourceFile),
202
+ tspath.ComparePathsOptions{
203
+ UseCaseSensitiveFileNames: e.host.UseCaseSensitiveFileNames(),
204
+ CurrentDirectory: e.host.GetCurrentDirectory(),
205
+ },
206
+ )
207
+ }
208
+ printer_.Write(sourceFile.AsNode(), sourceFile, e.writer, sourceMapGenerator)
209
+ sourceMapURLPos := -1
210
+ if sourceMapGenerator != nil {
211
+ if options.SourceMap.IsTrue() || options.InlineSourceMap.IsTrue() || options.GetAreDeclarationMapsEnabled() {
212
+ e.emitResult.SourceMaps = append(e.emitResult.SourceMaps, &innercompiler.SourceMapEmitResult{
213
+ InputSourceFileNames: sourceMapGenerator.Sources(),
214
+ SourceMap: sourceMapGenerator.RawSourceMap(),
215
+ GeneratedFile: filePath,
216
+ })
217
+ }
218
+ sourceMappingURL := e.sourceMappingURL(options, sourceMapGenerator, filePath, sourceMapFilePath, sourceFile)
219
+ if len(sourceMappingURL) > 0 {
220
+ if !e.writer.IsAtStartOfLine() {
221
+ e.writer.RawWrite(core.IfElse(options.NewLine == core.NewLineKindCRLF, "\r\n", "\n"))
222
+ }
223
+ sourceMapURLPos = e.writer.GetTextPos()
224
+ e.writer.WriteComment("//# sourceMappingURL=")
225
+ e.writer.WriteComment(sourceMappingURL)
226
+ }
227
+ if len(sourceMapFilePath) > 0 {
228
+ sourceMap := sourceMapGenerator.String()
229
+ if err := e.writeText(sourceMapFilePath, sourceMap, nil); err != nil {
230
+ e.addDiagnostic(ast.NewCompilerDiagnostic(diagnostics.Could_not_write_file_0_Colon_1, filePath, err.Error()))
231
+ } else {
232
+ e.emitResult.EmittedFiles = append(e.emitResult.EmittedFiles, sourceMapFilePath)
233
+ }
234
+ }
235
+ } else {
236
+ e.writer.WriteLine()
237
+ }
238
+ text := e.writer.String()
239
+ if options.EmitBOM.IsTrue() {
240
+ text = stringutil.AddUTF8ByteOrderMark(text)
241
+ }
242
+ data := &innercompiler.WriteFileData{
243
+ SourceMapUrlPos: sourceMapURLPos,
244
+ Diagnostics: e.emitterDiagnostics(),
245
+ }
246
+ err := e.writeText(filePath, text, data)
247
+ skippedDtsWrite := data.SkippedDtsWrite
248
+ if err != nil {
249
+ e.addDiagnostic(ast.NewCompilerDiagnostic(diagnostics.Could_not_write_file_0_Colon_1, filePath, err.Error()))
250
+ } else if !skippedDtsWrite {
251
+ e.emitResult.EmittedFiles = append(e.emitResult.EmittedFiles, filePath)
252
+ }
253
+ e.writer.Clear()
254
+ }
255
+
256
+ func (e *hookEmitter) writeText(fileName string, text string, data *innercompiler.WriteFileData) error {
257
+ if e.writeFile != nil {
258
+ return e.writeFile(fileName, text, data)
259
+ }
260
+ return e.host.WriteFile(fileName, text)
261
+ }
262
+
263
+ func (e *hookEmitter) sourceMapDirectory(options *core.CompilerOptions, filePath string, sourceFile *ast.SourceFile) string {
264
+ if len(options.SourceRoot) > 0 {
265
+ return e.host.CommonSourceDirectory()
266
+ }
267
+ if len(options.MapRoot) > 0 {
268
+ sourceMapDir := tspath.NormalizeSlashes(options.MapRoot)
269
+ if sourceFile != nil {
270
+ sourceMapDir = tspath.GetDirectoryPath(outputpaths.GetSourceFilePathInNewDir(
271
+ sourceFile.FileName(),
272
+ sourceMapDir,
273
+ e.host.GetCurrentDirectory(),
274
+ e.host.CommonSourceDirectory(),
275
+ e.host.UseCaseSensitiveFileNames(),
276
+ ))
277
+ }
278
+ if tspath.GetRootLength(sourceMapDir) == 0 {
279
+ sourceMapDir = tspath.CombinePaths(e.host.CommonSourceDirectory(), sourceMapDir)
280
+ }
281
+ return sourceMapDir
282
+ }
283
+ return tspath.GetDirectoryPath(tspath.NormalizePath(filePath))
284
+ }
285
+
286
+ func (e *hookEmitter) sourceMappingURL(options *core.CompilerOptions, sourceMapGenerator *sourcemap.Generator, filePath string, sourceMapFilePath string, sourceFile *ast.SourceFile) string {
287
+ if options.InlineSourceMap.IsTrue() {
288
+ return sourceMapGenerator.Base64DataURL()
289
+ }
290
+ sourceMapFile := tspath.GetBaseFileName(tspath.NormalizeSlashes(sourceMapFilePath))
291
+ if len(options.MapRoot) > 0 {
292
+ sourceMapDir := tspath.NormalizeSlashes(options.MapRoot)
293
+ if sourceFile != nil {
294
+ sourceMapDir = tspath.GetDirectoryPath(outputpaths.GetSourceFilePathInNewDir(
295
+ sourceFile.FileName(),
296
+ sourceMapDir,
297
+ e.host.GetCurrentDirectory(),
298
+ e.host.CommonSourceDirectory(),
299
+ e.host.UseCaseSensitiveFileNames(),
300
+ ))
301
+ }
302
+ if tspath.GetRootLength(sourceMapDir) == 0 {
303
+ sourceMapDir = tspath.CombinePaths(e.host.CommonSourceDirectory(), sourceMapDir)
304
+ return stringutil.EncodeURI(
305
+ tspath.GetRelativePathToDirectoryOrUrl(
306
+ tspath.GetDirectoryPath(tspath.NormalizePath(filePath)),
307
+ tspath.CombinePaths(sourceMapDir, sourceMapFile),
308
+ true,
309
+ tspath.ComparePathsOptions{
310
+ UseCaseSensitiveFileNames: e.host.UseCaseSensitiveFileNames(),
311
+ CurrentDirectory: e.host.GetCurrentDirectory(),
312
+ },
313
+ ),
314
+ )
315
+ }
316
+ return stringutil.EncodeURI(tspath.CombinePaths(sourceMapDir, sourceMapFile))
317
+ }
318
+ return stringutil.EncodeURI(sourceMapFile)
319
+ }
320
+
321
+ func hookShouldEmitSourceMaps(options *core.CompilerOptions, sourceFile *ast.SourceFile) bool {
322
+ return (options.SourceMap.IsTrue() || options.InlineSourceMap.IsTrue()) &&
323
+ !tspath.FileExtensionIs(sourceFile.FileName(), tspath.ExtensionJson)
324
+ }
325
+
326
+ func hookShouldEmitDeclarationSourceMaps(options *core.CompilerOptions, sourceFile *ast.SourceFile) bool {
327
+ return options.DeclarationMap.IsTrue() &&
328
+ !tspath.FileExtensionIs(sourceFile.FileName(), tspath.ExtensionJson)
329
+ }
330
+
331
+ func hookSourceRoot(options *core.CompilerOptions) string {
332
+ sourceRoot := tspath.NormalizeSlashes(options.SourceRoot)
333
+ if len(sourceRoot) > 0 {
334
+ sourceRoot = tspath.EnsureTrailingDirectorySeparator(sourceRoot)
335
+ }
336
+ return sourceRoot
337
+ }
338
+
339
+ func hookScriptTransformers(emitContext *printer.EmitContext, host printer.EmitHost, sourceFile *ast.SourceFile) []*transformers.Transformer {
340
+ var tx []*transformers.Transformer
341
+ options := host.Options()
342
+ importElisionEnabled := !options.VerbatimModuleSyntax.IsTrue() && !ast.IsInJSFile(sourceFile.AsNode())
343
+ jsxTransformEnabled := options.GetJSXTransformEnabled() && sourceFile.LanguageVariant == core.LanguageVariantJSX
344
+ emitResolver := host.GetEmitResolver()
345
+ var referenceResolver binder.ReferenceResolver
346
+ if importElisionEnabled || jsxTransformEnabled || !options.GetIsolatedModules() || options.EmitDecoratorMetadata.IsTrue() {
347
+ emitResolver.MarkLinkedReferencesRecursively(sourceFile)
348
+ referenceResolver = emitResolver
349
+ } else {
350
+ referenceResolver = binder.NewReferenceResolver(options, binder.ReferenceResolverHooks{})
351
+ }
352
+ opts := transformers.TransformOptions{
353
+ Context: emitContext,
354
+ CompilerOptions: options,
355
+ Resolver: referenceResolver,
356
+ EmitResolver: emitResolver,
357
+ GetEmitModuleFormatOfFile: host.GetEmitModuleFormatOfFile,
358
+ }
359
+ if options.EmitDecoratorMetadata.IsTrue() {
360
+ tx = append(tx, tstransforms.NewMetadataTransformer(&opts))
361
+ }
362
+ tx = append(tx, tstransforms.NewTypeEraserTransformer(&opts))
363
+ if importElisionEnabled {
364
+ tx = append(tx, tstransforms.NewImportElisionTransformer(&opts))
365
+ }
366
+ tx = append(tx, tstransforms.NewRuntimeSyntaxTransformer(&opts))
367
+ if options.ExperimentalDecorators.IsTrue() {
368
+ tx = append(tx, tstransforms.NewLegacyDecoratorsTransformer(&opts))
369
+ }
370
+ if jsxTransformEnabled {
371
+ tx = append(tx, jsxtransforms.NewJSXTransformer(&opts))
372
+ }
373
+ downleveler := estransforms.GetESTransformer(&opts)
374
+ if downleveler != nil {
375
+ tx = append(tx, downleveler)
376
+ }
377
+ tx = append(tx, estransforms.NewUseStrictTransformer(&opts))
378
+ tx = append(tx, hookModuleTransformer(&opts))
379
+ if !options.GetIsolatedModules() {
380
+ tx = append(tx, inliners.NewConstEnumInliningTransformer(&opts))
381
+ }
382
+ return tx
383
+ }
384
+
385
+ func hookModuleTransformer(opts *transformers.TransformOptions) *transformers.Transformer {
386
+ switch opts.CompilerOptions.GetEmitModuleKind() {
387
+ case core.ModuleKindPreserve:
388
+ return moduletransforms.NewESModuleTransformer(opts)
389
+ case core.ModuleKindESNext,
390
+ core.ModuleKindES2022,
391
+ core.ModuleKindES2020,
392
+ core.ModuleKindES2015,
393
+ core.ModuleKindNode20,
394
+ core.ModuleKindNode18,
395
+ core.ModuleKindNode16,
396
+ core.ModuleKindNodeNext,
397
+ core.ModuleKindCommonJS:
398
+ return moduletransforms.NewImpliedModuleTransformer(opts)
399
+ default:
400
+ return moduletransforms.NewCommonJSModuleTransformer(opts)
401
+ }
402
+ }
403
+
404
+ func (host *hookEmitHost) GetModeForUsageLocation(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) core.ResolutionMode {
405
+ return host.program.GetModeForUsageLocation(file, moduleSpecifier)
406
+ }
407
+
408
+ func (host *hookEmitHost) GetResolvedModuleFromModuleSpecifier(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) *module.ResolvedModule {
409
+ return host.program.GetResolvedModuleFromModuleSpecifier(file, moduleSpecifier)
410
+ }
411
+
412
+ func (host *hookEmitHost) GetDefaultResolutionModeForFile(file ast.HasFileName) core.ResolutionMode {
413
+ return host.program.GetDefaultResolutionModeForFile(file)
414
+ }
415
+
416
+ func (host *hookEmitHost) GetEmitModuleFormatOfFile(file ast.HasFileName) core.ModuleKind {
417
+ return host.program.GetEmitModuleFormatOfFile(file)
418
+ }
419
+
420
+ func (host *hookEmitHost) FileExists(path string) bool {
421
+ return host.program.FileExists(path)
422
+ }
423
+
424
+ func (host *hookEmitHost) GetGlobalTypingsCacheLocation() string {
425
+ return host.program.GetGlobalTypingsCacheLocation()
426
+ }
427
+
428
+ func (host *hookEmitHost) GetNearestAncestorDirectoryWithPackageJson(dirname string) string {
429
+ return host.program.GetNearestAncestorDirectoryWithPackageJson(dirname)
430
+ }
431
+
432
+ func (host *hookEmitHost) GetPackageJsonInfo(pkgJSONPath string) *packagejson.InfoCacheEntry {
433
+ return host.program.GetPackageJsonInfo(pkgJSONPath)
434
+ }
435
+
436
+ func (host *hookEmitHost) GetSourceOfProjectReferenceIfOutputIncluded(file ast.HasFileName) string {
437
+ return host.program.GetSourceOfProjectReferenceIfOutputIncluded(file)
438
+ }
439
+
440
+ func (host *hookEmitHost) GetProjectReferenceFromSource(path tspath.Path) *tsoptions.SourceOutputAndProjectReference {
441
+ return host.program.GetProjectReferenceFromSource(path)
442
+ }
443
+
444
+ func (host *hookEmitHost) GetRedirectTargets(path tspath.Path) []string {
445
+ return host.program.GetRedirectTargets(path)
446
+ }
447
+
448
+ func (host *hookEmitHost) GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags {
449
+ return host.GetEmitResolver().GetEffectiveDeclarationFlags(node, flags)
450
+ }
451
+
452
+ func (host *hookEmitHost) GetOutputPathsFor(file *ast.SourceFile, forceDtsPaths bool) declarations.OutputPaths {
453
+ return outputpaths.GetOutputPathsFor(file, host.Options(), host, forceDtsPaths)
454
+ }
455
+
456
+ func (host *hookEmitHost) GetResolutionModeOverride(node *ast.Node) core.ResolutionMode {
457
+ return host.GetEmitResolver().GetResolutionModeOverride(node)
458
+ }
459
+
460
+ func (host *hookEmitHost) GetSourceFileFromReference(origin *ast.SourceFile, ref *ast.FileReference) *ast.SourceFile {
461
+ return host.program.GetSourceFileFromReference(origin, ref)
462
+ }
463
+
464
+ func (host *hookEmitHost) Options() *core.CompilerOptions { return host.program.Options() }
465
+ func (host *hookEmitHost) SourceFiles() []*ast.SourceFile { return host.program.SourceFiles() }
466
+ func (host *hookEmitHost) GetCurrentDirectory() string { return host.program.GetCurrentDirectory() }
467
+ func (host *hookEmitHost) CommonSourceDirectory() string { return host.program.CommonSourceDirectory() }
468
+ func (host *hookEmitHost) UseCaseSensitiveFileNames() bool {
469
+ return host.program.UseCaseSensitiveFileNames()
470
+ }
471
+
472
+ func (host *hookEmitHost) IsEmitBlocked(file string) bool {
473
+ return host.program.IsEmitBlocked(file)
474
+ }
475
+
476
+ func (host *hookEmitHost) WriteFile(fileName string, text string) error {
477
+ return host.program.Host().FS().WriteFile(fileName, text)
478
+ }
479
+
480
+ func (host *hookEmitHost) GetEmitResolver() printer.EmitResolver {
481
+ return host.emitResolver
482
+ }
483
+
484
+ func (host *hookEmitHost) IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool {
485
+ return host.program.IsSourceFileFromExternalLibrary(file)
486
+ }
487
+
488
+ func (host *hookEmitHost) GetSymlinkCache() *symlinks.KnownSymlinks {
489
+ return host.program.GetSymlinkCache()
490
+ }
491
+
492
+ func (host *hookEmitHost) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule {
493
+ return host.program.ResolveModuleName(moduleName, containingFile, resolutionMode)
494
+ }
package/shim/core/shim.go CHANGED
@@ -24,3 +24,6 @@ const (
24
24
 
25
25
  // NewTextRange constructs a closed/open [pos, end) text range.
26
26
  func NewTextRange(pos, end int) TextRange { return innercore.NewTextRange(pos, end) }
27
+
28
+ // UndefinedTextRange marks synthesized AST nodes that do not map to source.
29
+ func UndefinedTextRange() TextRange { return innercore.UndefinedTextRange() }
@@ -99,9 +99,6 @@ export function runBuild(
99
99
  const compilers = execution.nativePlugins.filter(
100
100
  (plugin) => plugin.stage === "transform",
101
101
  );
102
- const outputs = execution.nativePlugins.filter(
103
- (plugin) => plugin.stage === "output",
104
- );
105
102
  const checked = runNativeCheckPlugins(options, execution);
106
103
  if (checked.status !== 0) {
107
104
  return checked;
@@ -109,7 +106,7 @@ export function runBuild(
109
106
 
110
107
  if (options.emit === false) {
111
108
  if (compilers.length !== 0) {
112
- assertSingleCompilerHost(compilers);
109
+ assertCompilerHostCompatibility(compilers);
113
110
  return appendBuildOutput(
114
111
  checked,
115
112
  buildWithNativeCompilerPlugins(options, execution, compilers),
@@ -123,7 +120,7 @@ export function runBuild(
123
120
 
124
121
  let result: TtscBuildResult;
125
122
  if (compilers.length !== 0) {
126
- assertSingleCompilerHost(compilers);
123
+ assertCompilerHostCompatibility(compilers);
127
124
  result = appendBuildOutput(
128
125
  checked,
129
126
  buildWithNativeCompilerPlugins(options, execution, compilers),
@@ -140,25 +137,13 @@ export function runBuild(
140
137
  }
141
138
  }
142
139
  const args = createTsgoBuildArgs(execution, options, {
143
- listEmittedFiles:
144
- outputs.length !== 0 || options.forceListEmittedFiles === true,
140
+ listEmittedFiles: options.forceListEmittedFiles === true,
145
141
  });
146
142
  const emitted = runTsgoBuild(execution, options, args);
147
143
  result = appendBuildOutput(checked, emitted);
148
144
  }
149
145
 
150
- if (result.status !== 0 || outputs.length === 0) {
151
- return result;
152
- }
153
- return appendBuildOutput(
154
- result,
155
- applyOutputPlugins(
156
- options,
157
- execution,
158
- result.emittedFiles ?? [],
159
- outputs,
160
- ),
161
- );
146
+ return result;
162
147
  }
163
148
 
164
149
  if (options.emit !== false && options.skipDiagnosticsCheck !== true) {
@@ -332,30 +317,13 @@ function createNativeCheckArgs(
332
317
  return args;
333
318
  }
334
319
 
335
- function createNativeOutputArgs(
336
- execution: ReturnType<typeof resolveExecutionContext>,
337
- options: TtscBuildOptions,
338
- file: string,
339
- ): string[] {
340
- const args = [
341
- "output",
342
- "--file=" + file,
343
- "--tsconfig=" + execution.tsconfig,
344
- "--plugins-json=" + serializeNativePlugins(execution.nativePlugins),
345
- "--cwd=" + execution.projectRoot,
346
- ];
347
- if (options.outDir) {
348
- args.push("--outDir=" + path.resolve(execution.cwd, options.outDir));
349
- }
350
- return args;
351
- }
352
-
353
320
  function serializeNativePlugins(
354
321
  plugins: readonly ITtscLoadedNativePlugin[],
355
322
  ): string {
356
323
  return JSON.stringify(
357
324
  plugins.map((plugin) => ({
358
325
  config: plugin.config,
326
+ hooks: plugin.hooks,
359
327
  name: plugin.name,
360
328
  stage: plugin.stage,
361
329
  })),
@@ -390,48 +358,6 @@ function runNativeCheckPlugins(
390
358
  return out;
391
359
  }
392
360
 
393
- function applyOutputPlugins(
394
- options: TtscBuildOptions,
395
- execution: ReturnType<typeof resolveExecutionContext>,
396
- emittedFiles: readonly string[],
397
- plugins: readonly ITtscLoadedNativePlugin[],
398
- ): TtscBuildResult {
399
- let out: TtscBuildResult = {
400
- diagnostics: [],
401
- status: 0,
402
- stdout: "",
403
- stderr: "",
404
- };
405
- for (const plugin of plugins) {
406
- for (const file of emittedFiles) {
407
- if (!fs.existsSync(file)) {
408
- continue;
409
- }
410
- const result = runNativeOutputPlugin(options, execution, plugin, file);
411
- out = appendBuildOutput(out, result);
412
- if (result.status !== 0) {
413
- return out;
414
- }
415
- }
416
- }
417
- return out;
418
- }
419
-
420
- function runNativeOutputPlugin(
421
- options: TtscBuildOptions,
422
- execution: ReturnType<typeof resolveExecutionContext>,
423
- plugin: ITtscLoadedNativePlugin,
424
- file: string,
425
- ): TtscBuildResult {
426
- return runNativePluginCommand(
427
- plugin,
428
- createNativeOutputArgs(execution, options, file),
429
- options,
430
- execution,
431
- "ttsc.output",
432
- );
433
- }
434
-
435
361
  function runNativePluginCommand(
436
362
  plugin: ITtscLoadedNativePlugin,
437
363
  args: readonly string[],
@@ -473,16 +399,59 @@ function appendBuildOutput(
473
399
  });
474
400
  }
475
401
 
476
- function assertSingleCompilerHost(
402
+ function assertCompilerHostCompatibility(
477
403
  plugins: readonly ITtscLoadedNativePlugin[],
478
404
  ): void {
479
405
  const binaries = [...new Set(plugins.map((plugin) => plugin.binary))];
480
- if (binaries.length > 1) {
481
- throw new Error(
482
- "ttsc: multiple compiler native backends cannot share one emit pass; " +
483
- "use output-capability plugins for post-emit transforms",
484
- );
406
+ if (binaries.length <= 1) {
407
+ return;
408
+ }
409
+ if (plugins.every(isFirstPartyUtilityTransformPlugin)) {
410
+ return;
411
+ }
412
+ throw new Error(
413
+ "ttsc: multiple compiler native backends cannot share one emit pass; " +
414
+ "compose transform hook libraries through one aggregate host",
415
+ );
416
+ }
417
+
418
+ function isFirstPartyUtilityTransformPlugin(
419
+ plugin: ITtscLoadedNativePlugin,
420
+ ): boolean {
421
+ if (plugin.stage !== "transform") return false;
422
+ if (!firstPartyUtilityPluginNames.has(plugin.name)) return false;
423
+ const manifest = readNearestPackageManifest(plugin.source);
424
+ return manifest?.name === plugin.name;
425
+ }
426
+
427
+ const firstPartyUtilityPluginNames = new Set([
428
+ "@ttsc/banner",
429
+ "@ttsc/paths",
430
+ "@ttsc/strip",
431
+ ]);
432
+
433
+ function readNearestPackageManifest(
434
+ source: string,
435
+ ): { name?: unknown } | undefined {
436
+ try {
437
+ let current = fs.statSync(source).isDirectory()
438
+ ? source
439
+ : path.dirname(source);
440
+ for (let i = 0; i < 4; i += 1) {
441
+ const manifest = path.join(current, "package.json");
442
+ if (fs.existsSync(manifest)) {
443
+ return JSON.parse(fs.readFileSync(manifest, "utf8")) as {
444
+ name?: unknown;
445
+ };
446
+ }
447
+ const parent = path.dirname(current);
448
+ if (parent === current) break;
449
+ current = parent;
450
+ }
451
+ } catch {
452
+ return undefined;
485
453
  }
454
+ return undefined;
486
455
  }
487
456
 
488
457
  function resolveExecutionContext(