Skip to content

Commit 1c82d8f

Browse files
authored
Add emitter scaffolding (#172)
1 parent ebb5380 commit 1c82d8f

15 files changed

+707
-123
lines changed

cmd/tsgo/main.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ var printTypes = false
2626
var pretty = true
2727
var listFiles = false
2828
var pprofDir = ""
29+
var outDir = ""
2930

3031
func printDiagnostic(d *ast.Diagnostic, level int, comparePathOptions tspath.ComparePathsOptions) {
3132
file := d.File()
@@ -57,15 +58,23 @@ func main() {
5758
flag.BoolVar(&pretty, "pretty", true, "Get prettier errors")
5859
flag.BoolVar(&listFiles, "listfiles", false, "List files in the program")
5960
flag.StringVar(&pprofDir, "pprofdir", "", "Generate pprof CPU/memory profiles to the given directory")
61+
flag.StringVar(&outDir, "outdir", "", "Emit to the given directory")
6062
flag.Parse()
6163

6264
rootPath := flag.Arg(0)
63-
compilerOptions := &core.CompilerOptions{Strict: core.TSTrue, Target: core.ScriptTargetESNext, ModuleKind: core.ModuleKindNodeNext}
65+
compilerOptions := &core.CompilerOptions{Strict: core.TSTrue, Target: core.ScriptTargetESNext, ModuleKind: core.ModuleKindNodeNext, NoEmit: core.TSTrue}
66+
6467
currentDirectory, err := os.Getwd()
6568
if err != nil {
6669
fmt.Fprintf(os.Stderr, "Error getting current directory: %v\n", err)
6770
os.Exit(1)
6871
}
72+
73+
if len(outDir) > 0 {
74+
compilerOptions.NoEmit = core.TSFalse
75+
compilerOptions.OutDir = tspath.ResolvePath(currentDirectory, outDir)
76+
}
77+
6978
fs := vfs.FromOS()
7079
useCaseSensitiveFileNames := fs.UseCaseSensitiveFileNames()
7180
host := ts.NewCompilerHost(compilerOptions, currentDirectory, fs)
@@ -99,6 +108,13 @@ func main() {
99108
}
100109
compileTime := time.Since(startTime)
101110

111+
startTime = time.Now()
112+
if len(outDir) > 0 {
113+
result := program.Emit(&ts.EmitOptions{})
114+
diagnostics = append(diagnostics, result.Diagnostics...)
115+
}
116+
emitTime := time.Since(startTime)
117+
102118
var memStats runtime.MemStats
103119
runtime.GC()
104120
runtime.GC()
@@ -133,6 +149,7 @@ func main() {
133149
fmt.Printf("Files: %v\n", len(program.SourceFiles()))
134150
fmt.Printf("Types: %v\n", program.TypeCount())
135151
fmt.Printf("Compile time: %v\n", compileTime)
152+
fmt.Printf("Emit time: %v\n", emitTime)
136153
fmt.Printf("Memory used: %vK\n", memStats.Alloc/1024)
137154
}
138155

internal/compiler/emitHost.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package compiler
2+
3+
import (
4+
"github.com/microsoft/typescript-go/internal/ast"
5+
"github.com/microsoft/typescript-go/internal/core"
6+
)
7+
8+
type WriteFileData struct {
9+
SourceMapUrlPos int
10+
// BuildInfo BuildInfo
11+
Diagnostics []*ast.Diagnostic
12+
DiffersOnlyInMap bool
13+
SkippedDtsWrite bool
14+
}
15+
16+
// NOTE: EmitHost operations must be thread-safe
17+
type EmitHost interface {
18+
Options() *core.CompilerOptions
19+
SourceFiles() []*ast.SourceFile
20+
UseCaseSensitiveFileNames() bool
21+
GetCurrentDirectory() string
22+
CommonSourceDirectory() string
23+
IsEmitBlocked(file string) bool
24+
WriteFile(fileName string, text string, writeByteOrderMark bool, relatedSourceFiles []*ast.SourceFile, data *WriteFileData) error
25+
}
26+
27+
var _ EmitHost = (*emitHost)(nil)
28+
29+
// NOTE: emitHost operations must be thread-safe
30+
type emitHost struct {
31+
program *Program
32+
}
33+
34+
func (host *emitHost) Options() *core.CompilerOptions { return host.program.Options() }
35+
func (host *emitHost) SourceFiles() []*ast.SourceFile { return host.program.SourceFiles() }
36+
func (host *emitHost) GetCurrentDirectory() string { return host.program.host.GetCurrentDirectory() }
37+
func (host *emitHost) CommonSourceDirectory() string { return host.program.CommonSourceDirectory() }
38+
func (host *emitHost) UseCaseSensitiveFileNames() bool {
39+
return host.program.host.FS().UseCaseSensitiveFileNames()
40+
}
41+
func (host *emitHost) IsEmitBlocked(file string) bool {
42+
// !!!
43+
return false
44+
}
45+
func (host *emitHost) WriteFile(fileName string, text string, writeByteOrderMark bool, _ []*ast.SourceFile, _ *WriteFileData) error {
46+
return host.program.host.FS().WriteFile(fileName, text, writeByteOrderMark)
47+
}

internal/compiler/emitter.go

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
package compiler
2+
3+
import (
4+
"github.com/microsoft/typescript-go/internal/ast"
5+
"github.com/microsoft/typescript-go/internal/compiler/diagnostics"
6+
"github.com/microsoft/typescript-go/internal/core"
7+
"github.com/microsoft/typescript-go/internal/printer"
8+
"github.com/microsoft/typescript-go/internal/tspath"
9+
)
10+
11+
type emitOnly byte
12+
13+
const (
14+
emitAll emitOnly = iota
15+
emitOnlyJs
16+
emitOnlyDts
17+
emitOnlyBuildInfo
18+
)
19+
20+
type emitter struct {
21+
host EmitHost
22+
emitOnly emitOnly
23+
emittedFilesList []string
24+
emitterDiagnostics DiagnosticsCollection
25+
emitSkipped bool
26+
sourceMapDataList []*sourceMapEmitResult
27+
writer printer.EmitTextWriter
28+
paths *outputPaths
29+
sourceFile *ast.SourceFile
30+
}
31+
32+
func (e *emitter) emit() {
33+
// !!! tracing
34+
e.emitJsFile(e.sourceFile, e.paths.jsFilePath, e.paths.sourceMapFilePath)
35+
e.emitDeclarationFile(e.sourceFile, e.paths.declarationFilePath, e.paths.declarationMapPath)
36+
e.emitBuildInfo(e.paths.buildInfoPath)
37+
}
38+
39+
func (e *emitter) emitJsFile(sourceFile *ast.SourceFile, jsFilePath string, sourceMapFilePath string) {
40+
options := e.host.Options()
41+
42+
if sourceFile == nil || e.emitOnly != emitAll && e.emitOnly != emitOnlyJs || len(jsFilePath) == 0 {
43+
return
44+
}
45+
if options.NoEmit == core.TSTrue || e.host.IsEmitBlocked(jsFilePath) {
46+
return
47+
}
48+
49+
// !!! mark linked references
50+
// !!! transform the source files?
51+
52+
printerOptions := printer.PrinterOptions{
53+
NewLine: options.NewLine,
54+
// !!!
55+
}
56+
57+
// create a printer to print the nodes
58+
printer := printer.NewPrinter(printerOptions, printer.PrintHandlers{
59+
// !!!
60+
})
61+
62+
e.printSourceFile(jsFilePath, sourceMapFilePath, sourceFile, printer)
63+
64+
if e.emittedFilesList != nil {
65+
e.emittedFilesList = append(e.emittedFilesList, jsFilePath)
66+
if sourceMapFilePath != "" {
67+
e.emittedFilesList = append(e.emittedFilesList, sourceMapFilePath)
68+
}
69+
}
70+
}
71+
72+
func (e *emitter) emitDeclarationFile(sourceFile *ast.SourceFile, declarationFilePath string, declarationMapPath string) {
73+
// !!!
74+
}
75+
76+
func (e *emitter) emitBuildInfo(buildInfoPath string) {
77+
// !!!
78+
}
79+
80+
func (e *emitter) printSourceFile(jsFilePath string, sourceMapFilePath string, sourceFile *ast.SourceFile, printer *printer.Printer) bool {
81+
// !!! sourceMapGenerator
82+
// !!! bundles not implemented, may be deprecated
83+
sourceFiles := []*ast.SourceFile{sourceFile}
84+
85+
printer.Write(sourceFile.AsNode(), sourceFile, e.writer /*, sourceMapGenerator*/)
86+
87+
// !!! add sourceMapGenerator to sourceMapDataList
88+
// !!! append sourceMappingURL to output
89+
// !!! write the source map
90+
e.writer.WriteLine()
91+
92+
// Write the output file
93+
text := e.writer.String()
94+
data := &WriteFileData{} // !!!
95+
err := e.host.WriteFile(jsFilePath, text, e.host.Options().EmitBOM == core.TSTrue, sourceFiles, data)
96+
if err != nil {
97+
e.emitterDiagnostics.add(ast.NewCompilerDiagnostic(diagnostics.Could_not_write_file_0_Colon_1, jsFilePath, err.Error()))
98+
}
99+
100+
// Reset state
101+
e.writer.Clear()
102+
return !data.SkippedDtsWrite
103+
}
104+
105+
func getOutputExtension(fileName string, jsx core.JsxEmit) string {
106+
switch {
107+
case tspath.FileExtensionIs(fileName, tspath.ExtensionJson):
108+
return tspath.ExtensionJson
109+
case jsx == core.JsxEmitPreserve && tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionJsx, tspath.ExtensionTsx}):
110+
return tspath.ExtensionJsx
111+
case tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionMts, tspath.ExtensionMjs}):
112+
return tspath.ExtensionMjs
113+
case tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionCts, tspath.ExtensionCjs}):
114+
return tspath.ExtensionCjs
115+
default:
116+
return tspath.ExtensionJs
117+
}
118+
}
119+
120+
func getSourceFilePathInNewDir(fileName string, newDirPath string, currentDirectory string, commonSourceDirectory string, useCaseSensitiveFileNames bool) string {
121+
sourceFilePath := tspath.GetNormalizedAbsolutePath(fileName, currentDirectory)
122+
commonSourceDirectory = tspath.EnsureTrailingDirectorySeparator(commonSourceDirectory)
123+
isSourceFileInCommonSourceDirectory := tspath.ContainsPath(commonSourceDirectory, sourceFilePath, tspath.ComparePathsOptions{
124+
UseCaseSensitiveFileNames: useCaseSensitiveFileNames,
125+
CurrentDirectory: currentDirectory,
126+
})
127+
if isSourceFileInCommonSourceDirectory {
128+
sourceFilePath = sourceFilePath[len(commonSourceDirectory):]
129+
}
130+
return tspath.CombinePaths(newDirPath, sourceFilePath)
131+
}
132+
133+
func getOwnEmitOutputFilePath(fileName string, host EmitHost, extension string) string {
134+
compilerOptions := host.Options()
135+
var emitOutputFilePathWithoutExtension string
136+
if len(compilerOptions.OutDir) > 0 {
137+
currentDirectory := host.GetCurrentDirectory()
138+
emitOutputFilePathWithoutExtension = tspath.RemoveFileExtension(getSourceFilePathInNewDir(
139+
fileName,
140+
compilerOptions.OutDir,
141+
currentDirectory,
142+
host.CommonSourceDirectory(),
143+
host.UseCaseSensitiveFileNames(),
144+
))
145+
} else {
146+
emitOutputFilePathWithoutExtension = tspath.RemoveFileExtension(fileName)
147+
}
148+
return emitOutputFilePathWithoutExtension + extension
149+
}
150+
151+
func getSourceMapFilePath(jsFilePath string, options *core.CompilerOptions) string {
152+
// !!!
153+
return ""
154+
}
155+
156+
func getDeclarationEmitOutputFilePath(file string, host EmitHost) string {
157+
// !!!
158+
return ""
159+
}
160+
161+
type outputPaths struct {
162+
jsFilePath string
163+
sourceMapFilePath string
164+
declarationFilePath string
165+
declarationMapPath string
166+
buildInfoPath string
167+
}
168+
169+
func getOutputPathsFor(sourceFile *ast.SourceFile, host EmitHost, forceDtsEmit bool) *outputPaths {
170+
options := host.Options()
171+
// !!! bundle not implemented, may be deprecated
172+
ownOutputFilePath := getOwnEmitOutputFilePath(sourceFile.FileName(), host, getOutputExtension(sourceFile.FileName(), options.Jsx))
173+
isJsonFile := isJsonSourceFile(sourceFile)
174+
// If json file emits to the same location skip writing it, if emitDeclarationOnly skip writing it
175+
isJsonEmittedToSameLocation := isJsonFile &&
176+
tspath.ComparePaths(sourceFile.FileName(), ownOutputFilePath, tspath.ComparePathsOptions{
177+
CurrentDirectory: host.GetCurrentDirectory(),
178+
UseCaseSensitiveFileNames: host.UseCaseSensitiveFileNames(),
179+
}) == 0
180+
paths := &outputPaths{}
181+
if options.EmitDeclarationOnly != core.TSTrue && !isJsonEmittedToSameLocation {
182+
paths.jsFilePath = ownOutputFilePath
183+
if !isJsonSourceFile(sourceFile) {
184+
paths.sourceMapFilePath = getSourceMapFilePath(paths.jsFilePath, options)
185+
}
186+
}
187+
if forceDtsEmit || options.GetEmitDeclarations() && !isJsonFile {
188+
paths.declarationFilePath = getDeclarationEmitOutputFilePath(sourceFile.FileName(), host)
189+
if options.GetAreDeclarationMapsEnabled() {
190+
paths.declarationMapPath = paths.declarationFilePath + ".map"
191+
}
192+
}
193+
return paths
194+
}
195+
196+
func forEachEmittedFile(host EmitHost, action func(emitFileNames *outputPaths, sourceFile *ast.SourceFile) bool, sourceFiles []*ast.SourceFile, options *EmitOptions) bool {
197+
// !!! outFile not yet implemented, may be deprecated
198+
for _, sourceFile := range sourceFiles {
199+
if action(getOutputPathsFor(sourceFile, host, options.forceDtsEmit), sourceFile) {
200+
return true
201+
}
202+
}
203+
return false
204+
}
205+
206+
func sourceFileMayBeEmitted(sourceFile *ast.SourceFile, host EmitHost, forceDtsEmit bool) bool {
207+
// !!! Js files are emitted only if option is enabled
208+
209+
// Declaration files are not emitted
210+
if sourceFile.IsDeclarationFile {
211+
return false
212+
}
213+
214+
// !!! Source file from node_modules are not emitted
215+
216+
// forcing dts emit => file needs to be emitted
217+
if forceDtsEmit {
218+
return true
219+
}
220+
221+
// !!! Source files from referenced projects are not emitted
222+
223+
// Any non json file should be emitted
224+
if !isJsonSourceFile(sourceFile) {
225+
return true
226+
}
227+
228+
// !!! Should JSON input files be emitted
229+
return false
230+
}
231+
232+
func getSourceFilesToEmit(host EmitHost, targetSourceFile *ast.SourceFile, forceDtsEmit bool) []*ast.SourceFile {
233+
// !!! outFile not yet implemented, may be deprecated
234+
var sourceFiles []*ast.SourceFile
235+
if targetSourceFile != nil {
236+
sourceFiles = []*ast.SourceFile{targetSourceFile}
237+
} else {
238+
sourceFiles = host.SourceFiles()
239+
}
240+
return core.Filter(sourceFiles, func(sourceFile *ast.SourceFile) bool {
241+
return sourceFileMayBeEmitted(sourceFile, host, forceDtsEmit)
242+
})
243+
}

0 commit comments

Comments
 (0)