forked from microsoft/typescript-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathemitter.go
289 lines (243 loc) · 9.84 KB
/
emitter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
package compiler
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/binder"
"github.com/microsoft/typescript-go/internal/compiler/diagnostics"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
"github.com/microsoft/typescript-go/internal/tspath"
)
type emitOnly byte
const (
emitAll emitOnly = iota
emitOnlyJs
emitOnlyDts
emitOnlyBuildInfo
)
type emitter struct {
host EmitHost
emitOnly emitOnly
emittedFilesList []string
emitterDiagnostics ast.DiagnosticsCollection
emitSkipped bool
sourceMapDataList []*sourceMapEmitResult
writer printer.EmitTextWriter
paths *outputPaths
sourceFile *ast.SourceFile
}
func (e *emitter) emit() {
// !!! tracing
e.emitJsFile(e.sourceFile, e.paths.jsFilePath, e.paths.sourceMapFilePath)
e.emitDeclarationFile(e.sourceFile, e.paths.declarationFilePath, e.paths.declarationMapPath)
e.emitBuildInfo(e.paths.buildInfoPath)
}
func (e *emitter) getModuleTransformer(emitContext *printer.EmitContext, resolver binder.ReferenceResolver, sourceFileMetaDataProvider printer.SourceFileMetaDataProvider) *transformers.Transformer {
options := e.host.Options()
switch options.GetEmitModuleKind() {
case core.ModuleKindPreserve:
// `ESModuleTransformer` contains logic for preserving CJS input syntax in `--module preserve`
return transformers.NewESModuleTransformer(emitContext, options, resolver, sourceFileMetaDataProvider)
case core.ModuleKindESNext,
core.ModuleKindES2022,
core.ModuleKindES2020,
core.ModuleKindES2015,
core.ModuleKindNode16,
core.ModuleKindNodeNext,
core.ModuleKindCommonJS:
return transformers.NewImpliedModuleTransformer(emitContext, options, resolver, sourceFileMetaDataProvider)
default:
return transformers.NewCommonJSModuleTransformer(emitContext, options, resolver, sourceFileMetaDataProvider)
}
}
func (e *emitter) getScriptTransformers(emitContext *printer.EmitContext, sourceFile *ast.SourceFile) []*transformers.Transformer {
var tx []*transformers.Transformer
options := e.host.Options()
// JS files don't use reference calculations as they don't do import elision, no need to calculate it
importElisionEnabled := !options.VerbatimModuleSyntax.IsTrue() && !ast.IsInJSFile(sourceFile.AsNode())
var emitResolver printer.EmitResolver
var referenceResolver binder.ReferenceResolver
if importElisionEnabled {
emitResolver = e.host.GetEmitResolver(sourceFile, false /*skipDiagnostics*/) // !!! conditionally skip diagnostics
emitResolver.MarkLinkedReferencesRecursively(sourceFile)
referenceResolver = emitResolver
} else {
referenceResolver = binder.NewReferenceResolver(options, binder.ReferenceResolverHooks{})
}
// erase types
tx = append(tx, transformers.NewTypeEraserTransformer(emitContext, options))
// elide imports
if importElisionEnabled {
tx = append(tx, transformers.NewImportElisionTransformer(emitContext, options, emitResolver))
}
// transform `enum`, `namespace`, and parameter properties
tx = append(tx, transformers.NewRuntimeSyntaxTransformer(emitContext, options, referenceResolver))
// transform module syntax
tx = append(tx, e.getModuleTransformer(emitContext, referenceResolver, e.host))
return tx
}
func (e *emitter) emitJsFile(sourceFile *ast.SourceFile, jsFilePath string, sourceMapFilePath string) {
options := e.host.Options()
if sourceFile == nil || e.emitOnly != emitAll && e.emitOnly != emitOnlyJs || len(jsFilePath) == 0 {
return
}
if options.NoEmit == core.TSTrue || e.host.IsEmitBlocked(jsFilePath) {
return
}
emitContext := printer.NewEmitContext()
for _, transformer := range e.getScriptTransformers(emitContext, sourceFile) {
sourceFile = transformer.TransformSourceFile(sourceFile)
}
printerOptions := printer.PrinterOptions{
NewLine: options.NewLine,
NoEmitHelpers: options.NoEmitHelpers.IsTrue(),
// !!!
}
// create a printer to print the nodes
printer := printer.NewPrinter(printerOptions, printer.PrintHandlers{
// !!!
}, emitContext)
e.printSourceFile(jsFilePath, sourceMapFilePath, sourceFile, printer)
if e.emittedFilesList != nil {
e.emittedFilesList = append(e.emittedFilesList, jsFilePath)
if sourceMapFilePath != "" {
e.emittedFilesList = append(e.emittedFilesList, sourceMapFilePath)
}
}
}
func (e *emitter) emitDeclarationFile(sourceFile *ast.SourceFile, declarationFilePath string, declarationMapPath string) {
// !!!
}
func (e *emitter) emitBuildInfo(buildInfoPath string) {
// !!!
}
func (e *emitter) printSourceFile(jsFilePath string, sourceMapFilePath string, sourceFile *ast.SourceFile, printer *printer.Printer) bool {
// !!! sourceMapGenerator
// !!! bundles not implemented, may be deprecated
sourceFiles := []*ast.SourceFile{sourceFile}
printer.Write(sourceFile.AsNode(), sourceFile, e.writer /*, sourceMapGenerator*/)
// !!! add sourceMapGenerator to sourceMapDataList
// !!! append sourceMappingURL to output
// !!! write the source map
e.writer.WriteLine()
// Write the output file
text := e.writer.String()
data := &WriteFileData{} // !!!
err := e.host.WriteFile(jsFilePath, text, e.host.Options().EmitBOM == core.TSTrue, sourceFiles, data)
if err != nil {
e.emitterDiagnostics.Add(ast.NewCompilerDiagnostic(diagnostics.Could_not_write_file_0_Colon_1, jsFilePath, err.Error()))
}
// Reset state
e.writer.Clear()
return !data.SkippedDtsWrite
}
func getSourceFilePathInNewDir(fileName string, newDirPath string, currentDirectory string, commonSourceDirectory string, useCaseSensitiveFileNames bool) string {
sourceFilePath := tspath.GetNormalizedAbsolutePath(fileName, currentDirectory)
commonSourceDirectory = tspath.EnsureTrailingDirectorySeparator(commonSourceDirectory)
isSourceFileInCommonSourceDirectory := tspath.ContainsPath(commonSourceDirectory, sourceFilePath, tspath.ComparePathsOptions{
UseCaseSensitiveFileNames: useCaseSensitiveFileNames,
CurrentDirectory: currentDirectory,
})
if isSourceFileInCommonSourceDirectory {
sourceFilePath = sourceFilePath[len(commonSourceDirectory):]
}
return tspath.CombinePaths(newDirPath, sourceFilePath)
}
func getOwnEmitOutputFilePath(fileName string, host EmitHost, extension string) string {
compilerOptions := host.Options()
var emitOutputFilePathWithoutExtension string
if len(compilerOptions.OutDir) > 0 {
currentDirectory := host.GetCurrentDirectory()
emitOutputFilePathWithoutExtension = tspath.RemoveFileExtension(getSourceFilePathInNewDir(
fileName,
compilerOptions.OutDir,
currentDirectory,
host.CommonSourceDirectory(),
host.UseCaseSensitiveFileNames(),
))
} else {
emitOutputFilePathWithoutExtension = tspath.RemoveFileExtension(fileName)
}
return emitOutputFilePathWithoutExtension + extension
}
func getSourceMapFilePath(jsFilePath string, options *core.CompilerOptions) string {
// !!!
return ""
}
func getDeclarationEmitOutputFilePath(file string, host EmitHost) string {
// !!!
return ""
}
type outputPaths struct {
jsFilePath string
sourceMapFilePath string
declarationFilePath string
declarationMapPath string
buildInfoPath string
}
func getOutputPathsFor(sourceFile *ast.SourceFile, host EmitHost, forceDtsEmit bool) *outputPaths {
options := host.Options()
// !!! bundle not implemented, may be deprecated
ownOutputFilePath := getOwnEmitOutputFilePath(sourceFile.FileName(), host, core.GetOutputExtension(sourceFile.FileName(), options.Jsx))
isJsonFile := ast.IsJsonSourceFile(sourceFile)
// If json file emits to the same location skip writing it, if emitDeclarationOnly skip writing it
isJsonEmittedToSameLocation := isJsonFile &&
tspath.ComparePaths(sourceFile.FileName(), ownOutputFilePath, tspath.ComparePathsOptions{
CurrentDirectory: host.GetCurrentDirectory(),
UseCaseSensitiveFileNames: host.UseCaseSensitiveFileNames(),
}) == 0
paths := &outputPaths{}
if options.EmitDeclarationOnly != core.TSTrue && !isJsonEmittedToSameLocation {
paths.jsFilePath = ownOutputFilePath
if !ast.IsJsonSourceFile(sourceFile) {
paths.sourceMapFilePath = getSourceMapFilePath(paths.jsFilePath, options)
}
}
if forceDtsEmit || options.GetEmitDeclarations() && !isJsonFile {
paths.declarationFilePath = getDeclarationEmitOutputFilePath(sourceFile.FileName(), host)
if options.GetAreDeclarationMapsEnabled() {
paths.declarationMapPath = paths.declarationFilePath + ".map"
}
}
return paths
}
func forEachEmittedFile(host EmitHost, action func(emitFileNames *outputPaths, sourceFile *ast.SourceFile) bool, sourceFiles []*ast.SourceFile, options *EmitOptions) bool {
// !!! outFile not yet implemented, may be deprecated
for _, sourceFile := range sourceFiles {
if action(getOutputPathsFor(sourceFile, host, options.forceDtsEmit), sourceFile) {
return true
}
}
return false
}
func sourceFileMayBeEmitted(sourceFile *ast.SourceFile, host EmitHost, forceDtsEmit bool) bool {
// !!! Js files are emitted only if option is enabled
// Declaration files are not emitted
if sourceFile.IsDeclarationFile {
return false
}
// !!! Source file from node_modules are not emitted
// forcing dts emit => file needs to be emitted
if forceDtsEmit {
return true
}
// !!! Source files from referenced projects are not emitted
// Any non json file should be emitted
if !ast.IsJsonSourceFile(sourceFile) {
return true
}
// !!! Should JSON input files be emitted
return false
}
func getSourceFilesToEmit(host EmitHost, targetSourceFile *ast.SourceFile, forceDtsEmit bool) []*ast.SourceFile {
// !!! outFile not yet implemented, may be deprecated
var sourceFiles []*ast.SourceFile
if targetSourceFile != nil {
sourceFiles = []*ast.SourceFile{targetSourceFile}
} else {
sourceFiles = host.SourceFiles()
}
return core.Filter(sourceFiles, func(sourceFile *ast.SourceFile) bool {
return sourceFileMayBeEmitted(sourceFile, host, forceDtsEmit)
})
}