Skip to content

Commit 66be8e5

Browse files
authored
add simple watch mode (microsoft#336)
1 parent 9535557 commit 66be8e5

23 files changed

+1466
-145
lines changed

cmd/tsgo/sys.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io"
66
"os"
77
"runtime"
8+
"time"
89

910
"github.com/microsoft/typescript-go/internal/bundled"
1011
"github.com/microsoft/typescript-go/internal/core"
@@ -22,6 +23,10 @@ type osSys struct {
2223
cwd string
2324
}
2425

26+
func (s *osSys) Now() time.Time {
27+
return time.Now()
28+
}
29+
2530
func (s *osSys) FS() vfs.FS {
2631
return s.fs
2732
}

internal/bundled/embed.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,9 @@ func (vfs *wrappedFS) Stat(path string) vfs.FileInfo {
8585
if rest == "" || rest == "libs" {
8686
return &fileInfo{name: rest, mode: fs.ModeDir}
8787
}
88-
if libName, ok := strings.CutPrefix(rest, "libs/"); ok {
89-
if lib, ok := embeddedContents[libName]; ok {
90-
return &fileInfo{name: libName, size: int64(len(lib))}
91-
}
88+
if lib, ok := embeddedContents[rest]; ok {
89+
libName, _ := strings.CutPrefix(rest, "libs/")
90+
return &fileInfo{name: libName, size: int64(len(lib))}
9291
}
9392
return nil
9493
}

internal/execute/export_test.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,31 @@
11
package execute
22

3-
import "github.com/microsoft/typescript-go/internal/tsoptions"
3+
import (
4+
"github.com/microsoft/typescript-go/internal/compiler"
5+
"github.com/microsoft/typescript-go/internal/tsoptions"
6+
)
47

58
func CommandLineTest(sys System, cb cbType, commandLineArgs []string) (*tsoptions.ParsedCommandLine, ExitStatus) {
69
parsedCommandLine := tsoptions.ParseCommandLine(commandLineArgs, sys)
7-
return parsedCommandLine, executeCommandLineWorker(sys, cb, parsedCommandLine)
10+
e, _ := executeCommandLineWorker(sys, cb, parsedCommandLine)
11+
return parsedCommandLine, e
12+
}
13+
14+
func CommandLineTestWatch(sys System, cb cbType, commandLineArgs []string) (*tsoptions.ParsedCommandLine, *watcher) {
15+
parsedCommandLine := tsoptions.ParseCommandLine(commandLineArgs, sys)
16+
_, w := executeCommandLineWorker(sys, cb, parsedCommandLine)
17+
return parsedCommandLine, w
18+
}
19+
20+
func RunWatchCycle(w *watcher) {
21+
// this function should perform the same stuff as w.doCycle() without printing time-related output
22+
if w.hasErrorsInTsConfig() {
23+
// these are unrecoverable errors--report them and do not build
24+
return
25+
}
26+
// todo: updateProgram()
27+
w.program = compiler.NewProgramFromParsedCommandLine(w.options, w.host)
28+
if w.hasBeenModified(w.program) {
29+
w.compileAndEmit()
30+
}
831
}

internal/execute/system.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package execute
22

33
import (
44
"io"
5+
"time"
56

67
"github.com/microsoft/typescript-go/internal/vfs"
78
)
89

910
type System interface {
1011
Writer() io.Writer
1112
EndWrite() // needed for testing
13+
Now() time.Time
1214
FS() vfs.FS
1315
DefaultLibraryPath() string
1416
GetCurrentDirectory() string

internal/execute/testsys_test.go

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"maps"
99
"slices"
1010
"strings"
11+
"time"
1112

1213
"github.com/microsoft/typescript-go/internal/bundled"
1314
"github.com/microsoft/typescript-go/internal/vfs"
@@ -32,15 +33,26 @@ func newTestSys(fileOrFolderList FileMap, cwd string, args ...string) *testSys {
3233

3334
type testSys struct {
3435
// todo: original has write to output as a string[] because the separations are needed for baselining
35-
output []string
36-
currentWrite *strings.Builder
37-
serializedDiff map[string]string
36+
output []string
37+
currentWrite *strings.Builder
38+
serializedDiff map[string]string
39+
3840
fs vfs.FS
3941
defaultLibraryPath string
4042
cwd string
4143
files []string
4244
}
4345

46+
func (s *testSys) IsTestDone() bool {
47+
// todo: test is done if there are no edits left. Edits are not yet implemented
48+
return true
49+
}
50+
51+
func (s *testSys) Now() time.Time {
52+
// todo: make a "test time" structure
53+
return time.Now()
54+
}
55+
4456
func (s *testSys) FS() vfs.FS {
4557
return s.fs
4658
}
@@ -82,16 +94,18 @@ func (s *testSys) baselineOutput(baseline io.Writer) {
8294
fmt.Fprint(baseline, "\nOutput::\n")
8395
if len(s.output) == 0 {
8496
fmt.Fprint(baseline, "No output\n")
97+
return
8598
}
8699
// todo screen clears
87-
s.baselineOutputs(baseline, 0, len(s.output))
100+
s.printOutputs(baseline)
101+
s.output = []string{}
88102
}
89103

90104
func (s *testSys) baselineFSwithDiff(baseline io.Writer) {
91105
// todo: baselines the entire fs, possibly doesn't correctly diff all cases of emitted files, since emit isn't fully implemented and doesn't always emit the same way as strada
92106
snap := map[string]string{}
93107

94-
err := s.FS().WalkDir(s.GetCurrentDirectory(), func(path string, d vfs.DirEntry, e error) error {
108+
err := s.FS().WalkDir("/", func(path string, d vfs.DirEntry, e error) error {
95109
if e != nil {
96110
return e
97111
}
@@ -138,15 +152,7 @@ func reportFSEntryDiff(baseline io.Writer, oldDirContent string, newDirContent s
138152
}
139153
}
140154

141-
func (s *testSys) baselineOutputs(baseline io.Writer, start int, end int) {
155+
func (s *testSys) printOutputs(baseline io.Writer) {
142156
// todo sanitize sys output
143-
fmt.Fprint(baseline, strings.Join(s.output[start:end], "\n"))
157+
fmt.Fprint(baseline, strings.Join(s.output, "\n"))
144158
}
145-
146-
type serializeOutputOrder int
147-
148-
const (
149-
serializeOutputOrderNone serializeOutputOrder = iota
150-
serializeOutputOrderBefore serializeOutputOrder = 1
151-
serializeOutputOrderAfter serializeOutputOrder = 2
152-
)

internal/execute/tsc.go

Lines changed: 54 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ type cbType = func(p any) any
1515

1616
func CommandLine(sys System, cb cbType, commandLineArgs []string) ExitStatus {
1717
parsedCommandLine := tsoptions.ParseCommandLine(commandLineArgs, sys)
18-
return executeCommandLineWorker(sys, cb, parsedCommandLine)
18+
e, watcher := executeCommandLineWorker(sys, cb, parsedCommandLine)
19+
if watcher != nil {
20+
return e
21+
}
22+
return start(watcher)
1923
}
2024

21-
func executeCommandLineWorker(sys System, cb cbType, commandLine *tsoptions.ParsedCommandLine) ExitStatus {
25+
func executeCommandLineWorker(sys System, cb cbType, commandLine *tsoptions.ParsedCommandLine) (ExitStatus, *watcher) {
2226
configFileName := ""
2327
reportDiagnostic := createDiagnosticReporter(sys, commandLine.CompilerOptions().Pretty)
2428
// if commandLine.Options().Locale != nil
@@ -27,35 +31,35 @@ func executeCommandLineWorker(sys System, cb cbType, commandLine *tsoptions.Pars
2731
for _, e := range commandLine.Errors {
2832
reportDiagnostic(e)
2933
}
30-
return ExitStatusDiagnosticsPresent_OutputsSkipped
34+
return ExitStatusDiagnosticsPresent_OutputsSkipped, nil
3135
}
3236

3337
if commandLine.CompilerOptions().Init.IsTrue() ||
3438
commandLine.CompilerOptions().Version.IsTrue() ||
3539
// commandLine.CompilerOptions().Help != nil ||
3640
// commandLine.CompilerOptions().All != nil ||
3741
commandLine.CompilerOptions().Watch.IsTrue() && commandLine.CompilerOptions().ListFilesOnly.IsTrue() {
38-
return ExitStatusNotImplemented
42+
return ExitStatusNotImplemented, nil
3943
}
4044

4145
if commandLine.CompilerOptions().Project != "" {
4246
if len(commandLine.FileNames()) != 0 {
4347
reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line))
44-
return ExitStatusDiagnosticsPresent_OutputsSkipped
48+
return ExitStatusDiagnosticsPresent_OutputsSkipped, nil
4549
}
4650

4751
fileOrDirectory := tspath.NormalizePath(commandLine.CompilerOptions().Project)
4852
if fileOrDirectory != "" || sys.FS().DirectoryExists(fileOrDirectory) {
4953
configFileName = tspath.CombinePaths(fileOrDirectory, "tsconfig.json")
5054
if !sys.FS().FileExists(configFileName) {
5155
reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.Cannot_find_a_tsconfig_json_file_at_the_current_directory_Colon_0, configFileName))
52-
return ExitStatusDiagnosticsPresent_OutputsSkipped
56+
return ExitStatusDiagnosticsPresent_OutputsSkipped, nil
5357
}
5458
} else {
5559
configFileName = fileOrDirectory
5660
if !sys.FS().FileExists(configFileName) {
5761
reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.The_specified_path_does_not_exist_Colon_0, fileOrDirectory))
58-
return ExitStatusDiagnosticsPresent_OutputsSkipped
62+
return ExitStatusDiagnosticsPresent_OutputsSkipped, nil
5963
}
6064
}
6165
} else if len(commandLine.FileNames()) == 0 {
@@ -70,10 +74,10 @@ func executeCommandLineWorker(sys System, cb cbType, commandLine *tsoptions.Pars
7074
// print version
7175
// print help
7276
}
73-
return ExitStatusDiagnosticsPresent_OutputsSkipped
77+
return ExitStatusDiagnosticsPresent_OutputsSkipped, nil
7478
}
7579

76-
// !!! convert to options with absolute paths is usualy done here, but for ease of implementation, it's done in `tsoptions.ParseCommandLine`
80+
// !!! convert to options with absolute paths is usualy done here, but for ease of implementation, it's done in `tsoptions.ParseCommandLine()`
7781
compilerOptionsFromCommandLine := commandLine.CompilerOptions()
7882

7983
if configFileName != "" {
@@ -84,48 +88,41 @@ func executeCommandLineWorker(sys System, cb cbType, commandLine *tsoptions.Pars
8488
for _, e := range errors {
8589
reportDiagnostic(e)
8690
}
87-
return ExitStatusDiagnosticsPresent_OutputsGenerated
91+
return ExitStatusDiagnosticsPresent_OutputsGenerated, nil
8892
}
8993
if compilerOptionsFromCommandLine.ShowConfig.IsTrue() {
90-
// write show config
91-
return ExitStatusNotImplemented
94+
return ExitStatusNotImplemented, nil
9295
}
9396
// updateReportDiagnostic
9497
if isWatchSet(configParseResult.CompilerOptions()) {
95-
// todo watch
96-
return ExitStatusNotImplementedWatch
98+
return ExitStatusSuccess, createWatcher(sys, configParseResult, reportDiagnostic)
9799
} else if isIncrementalCompilation(configParseResult.CompilerOptions()) {
98-
// todo performIncrementalCompilation
99-
return ExitStatusNotImplementedIncremental
100-
} else {
101-
return performCompilation(
102-
sys,
103-
cb,
104-
configParseResult,
105-
reportDiagnostic,
106-
)
100+
return ExitStatusNotImplementedIncremental, nil
107101
}
102+
return performCompilation(
103+
sys,
104+
cb,
105+
configParseResult,
106+
reportDiagnostic,
107+
), nil
108108
} else {
109109
if compilerOptionsFromCommandLine.ShowConfig.IsTrue() {
110-
// write show config
111-
return ExitStatusNotImplemented
110+
return ExitStatusNotImplemented, nil
112111
}
113112
// todo update reportDiagnostic
114113
if isWatchSet(compilerOptionsFromCommandLine) {
115-
// todo watch
116-
// return ExitStatusDiagnosticsPresent_OutputsSkipped
117-
return ExitStatusNotImplementedWatch
114+
// !!! reportWatchModeWithoutSysSupport
115+
return ExitStatusSuccess, createWatcher(sys, commandLine, reportDiagnostic)
118116
} else if isIncrementalCompilation(compilerOptionsFromCommandLine) {
119-
// todo incremental
120-
return ExitStatusNotImplementedIncremental
117+
return ExitStatusNotImplementedIncremental, nil
121118
}
122119
}
123120
return performCompilation(
124121
sys,
125122
cb,
126123
commandLine,
127124
reportDiagnostic,
128-
)
125+
), nil
129126
}
130127

131128
func findConfigFile(searchPath string, fileExists func(string) bool, configName string) string {
@@ -171,6 +168,29 @@ func performCompilation(sys System, cb cbType, config *tsoptions.ParsedCommandLi
171168
host := compiler.NewCompilerHost(config.CompilerOptions(), sys.GetCurrentDirectory(), sys.FS(), sys.DefaultLibraryPath())
172169
// todo: cache, statistics, tracing
173170
program := compiler.NewProgramFromParsedCommandLine(config, host)
171+
172+
diagnostics, emitResult, exitStatus := compileAndEmit(sys, program, reportDiagnostic)
173+
if exitStatus != ExitStatusSuccess {
174+
// compile exited early
175+
return exitStatus
176+
}
177+
178+
reportStatistics(sys, program)
179+
if cb != nil {
180+
cb(program)
181+
}
182+
183+
if emitResult.EmitSkipped && diagnostics != nil && len(diagnostics) > 0 {
184+
return ExitStatusDiagnosticsPresent_OutputsSkipped
185+
} else if len(diagnostics) > 0 {
186+
return ExitStatusDiagnosticsPresent_OutputsGenerated
187+
}
188+
return ExitStatusSuccess
189+
}
190+
191+
func compileAndEmit(sys System, program *compiler.Program, reportDiagnostic diagnosticReporter) ([]*ast.Diagnostic, *compiler.EmitResult, ExitStatus) {
192+
// todo: check if third return needed after execute is fully implemented
193+
174194
options := program.Options()
175195
allDiagnostics := program.GetConfigFileParsingDiagnostics()
176196

@@ -188,7 +208,7 @@ func performCompilation(sys System, cb cbType, config *tsoptions.ParsedCommandLi
188208
}
189209
// TODO: declaration diagnostics
190210
if len(diagnostics) == 0 && options.NoEmit == core.TSTrue && (options.Declaration.IsTrue() && options.Composite.IsTrue()) {
191-
return ExitStatusNotImplemented
211+
return nil, nil, ExitStatusNotImplemented
192212
// addRange(allDiagnostics, program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken));
193213
}
194214

@@ -213,19 +233,8 @@ func performCompilation(sys System, cb cbType, config *tsoptions.ParsedCommandLi
213233
// todo: listFiles(program, sys.Writer())
214234
}
215235

216-
createReportErrorSummary(sys, config.CompilerOptions())(allDiagnostics)
217-
218-
reportStatistics(sys, program)
219-
if cb != nil {
220-
cb(program)
221-
}
222-
223-
if emitResult.EmitSkipped && diagnostics != nil && len(diagnostics) > 0 {
224-
return ExitStatusDiagnosticsPresent_OutputsSkipped
225-
} else if len(diagnostics) > 0 {
226-
return ExitStatusDiagnosticsPresent_OutputsGenerated
227-
}
228-
return ExitStatusSuccess
236+
createReportErrorSummary(sys, program.Options())(allDiagnostics)
237+
return allDiagnostics, emitResult, ExitStatusSuccess
229238
}
230239

231240
// func isBuildCommand(args []string) bool {

0 commit comments

Comments
 (0)