diff --git a/internal/execute/tsctests/tscwatch_test.go b/internal/execute/tsctests/tscwatch_test.go
index e577131250b..fabe775d025 100644
--- a/internal/execute/tsctests/tscwatch_test.go
+++ b/internal/execute/tsctests/tscwatch_test.go
@@ -3,6 +3,8 @@ package tsctests
import (
"strings"
"testing"
+
+ "github.com/microsoft/typescript-go/internal/vfs/vfstest"
)
func TestWatch(t *testing.T) {
@@ -23,6 +25,476 @@ func TestWatch(t *testing.T) {
},
commandLineArgs: []string{"--watch", "--incremental"},
},
+ {
+ subScenario: "watch skips build when no files change",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `const x: number = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": "{}",
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ noChange,
+ },
+ },
+ {
+ subScenario: "watch rebuilds when file is modified",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `const x: number = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": "{}",
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("modify file", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/index.ts", `const x: number = 2;`)
+ }),
+ },
+ },
+ {
+ subScenario: "watch rebuilds when source file is deleted",
+ files: FileMap{
+ "/home/src/workspaces/project/a.ts": `import { b } from "./b";`,
+ "/home/src/workspaces/project/b.ts": `export const b = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": "{}",
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ {
+ caption: "delete imported file",
+ edit: func(sys *TestSys) {
+ sys.removeNoError("/home/src/workspaces/project/b.ts")
+ },
+ expectedDiff: "incremental resolves to .js output from prior build (TS7016) while clean build cannot find module at all (TS2307)",
+ },
+ },
+ },
+ {
+ subScenario: "watch detects new file resolving failed import",
+ files: FileMap{
+ "/home/src/workspaces/project/a.ts": `import { b } from "./b";`,
+ "/home/src/workspaces/project/tsconfig.json": "{}",
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("create missing file", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/b.ts", `export const b = 1;`)
+ }),
+ },
+ },
+ // Directory-level change detection via imports
+ {
+ subScenario: "watch detects imported file added in new directory",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import { util } from "./lib/util";`,
+ "/home/src/workspaces/project/tsconfig.json": "{}",
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("create directory and imported file", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/lib/util.ts", `export const util = "hello";`)
+ }),
+ },
+ },
+ {
+ subScenario: "watch detects imported directory removed",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import { util } from "./lib/util";`,
+ "/home/src/workspaces/project/lib/util.ts": `export const util = "hello";`,
+ "/home/src/workspaces/project/tsconfig.json": "{}",
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ {
+ caption: "remove directory with imported file",
+ edit: func(sys *TestSys) {
+ sys.removeNoError("/home/src/workspaces/project/lib/util.ts")
+ },
+ expectedDiff: "incremental resolves to .js output from prior build (TS7016) while clean build cannot find module at all (TS2307)",
+ },
+ },
+ },
+ {
+ subScenario: "watch detects import path restructured",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import { util } from "./lib/util";`,
+ "/home/src/workspaces/project/lib/util.ts": `export const util = "v1";`,
+ "/home/src/workspaces/project/tsconfig.json": "{}",
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("move file to new path and update import", func(sys *TestSys) {
+ sys.removeNoError("/home/src/workspaces/project/lib/util.ts")
+ sys.writeFileNoError("/home/src/workspaces/project/src/util.ts", `export const util = "v2";`)
+ sys.writeFileNoError("/home/src/workspaces/project/index.ts", `import { util } from "./src/util";`)
+ }),
+ },
+ },
+ // tsconfig include/exclude change detection
+ {
+ subScenario: "watch rebuilds when tsconfig include pattern adds file",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `const x = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": `{
+ "compilerOptions": {},
+ "include": ["*.ts"]
+}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("widen include pattern to add src dir", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/src/extra.ts", `export const extra = 2;`)
+ sys.writeFileNoError("/home/src/workspaces/project/tsconfig.json", `{
+ "compilerOptions": {},
+ "include": ["*.ts", "src/**/*.ts"]
+}`)
+ }),
+ },
+ },
+ {
+ subScenario: "watch rebuilds when tsconfig is modified to change strict",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `const x = null; const y: string = x;`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("enable strict mode", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/tsconfig.json", `{"compilerOptions": {"strict": true}}`)
+ }),
+ },
+ },
+ // Path resolution: tsconfig include pointing to non-existent directory
+ {
+ subScenario: "watch detects file added to previously non-existent include path",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `const x = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": `{
+ "compilerOptions": {},
+ "include": ["index.ts", "src/**/*.ts"]
+}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("create src dir with ts file matching include", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/src/helper.ts", `export const helper = "added";`)
+ }),
+ },
+ },
+ {
+ subScenario: "watch detects new file in existing include directory",
+ files: FileMap{
+ "/home/src/workspaces/project/src/a.ts": `export const a = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": `{
+ "compilerOptions": {},
+ "include": ["src/**/*.ts"]
+}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("add new file to existing src directory", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/src/b.ts", `export const b = 2;`)
+ }),
+ },
+ },
+ // Wildcard include: nested subdirectory detection
+ {
+ subScenario: "watch detects file added in new nested subdirectory",
+ files: FileMap{
+ "/home/src/workspaces/project/src/a.ts": `export const a = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": `{
+ "compilerOptions": {},
+ "include": ["src/**/*.ts"]
+}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("create nested dir with ts file", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/src/deep/nested/util.ts", `export const util = "nested";`)
+ }),
+ },
+ },
+ {
+ subScenario: "watch detects file added in multiple new subdirectories simultaneously",
+ files: FileMap{
+ "/home/src/workspaces/project/src/a.ts": `export const a = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": `{
+ "compilerOptions": {},
+ "include": ["src/**/*.ts"]
+}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("create multiple new subdirs with files", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/src/models/user.ts", `export interface User { name: string; }`)
+ sys.writeFileNoError("/home/src/workspaces/project/src/utils/format.ts", `export function format(s: string): string { return s.trim(); }`)
+ }),
+ },
+ },
+ {
+ subScenario: "watch detects nested subdirectory removed and recreated",
+ files: FileMap{
+ "/home/src/workspaces/project/src/lib/helper.ts": `export const helper = "v1";`,
+ "/home/src/workspaces/project/tsconfig.json": `{
+ "compilerOptions": {},
+ "include": ["src/**/*.ts"]
+}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ {
+ caption: "remove nested dir",
+ expectedDiff: "incremental has prior state and does not report no-inputs error",
+ edit: func(sys *TestSys) {
+ sys.removeNoError("/home/src/workspaces/project/src/lib/helper.ts")
+ },
+ },
+ newTscEdit("recreate nested dir with new content", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/src/lib/helper.ts", `export const helper = "v2";`)
+ }),
+ },
+ },
+ // Path resolution: import from non-existent node_modules package
+ {
+ subScenario: "watch detects node modules package added",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import { lib } from "mylib";`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("install package in node_modules", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/node_modules/mylib/package.json", `{"name": "mylib", "main": "index.js", "types": "index.d.ts"}`)
+ sys.writeFileNoError("/home/src/workspaces/project/node_modules/mylib/index.js", `exports.lib = "hello";`)
+ sys.writeFileNoError("/home/src/workspaces/project/node_modules/mylib/index.d.ts", `export declare const lib: string;`)
+ }),
+ },
+ },
+ // Path resolution: node_modules package removed
+ {
+ subScenario: "watch detects node modules package removed",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import { lib } from "mylib";`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ "/home/src/workspaces/project/node_modules/mylib/package.json": `{"name": "mylib", "main": "index.js", "types": "index.d.ts"}`,
+ "/home/src/workspaces/project/node_modules/mylib/index.js": `exports.lib = "hello";`,
+ "/home/src/workspaces/project/node_modules/mylib/index.d.ts": `export declare const lib: string;`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ {
+ caption: "remove node_modules package",
+ edit: func(sys *TestSys) {
+ sys.removeNoError("/home/src/workspaces/project/node_modules/mylib/index.d.ts")
+ sys.removeNoError("/home/src/workspaces/project/node_modules/mylib/index.js")
+ sys.removeNoError("/home/src/workspaces/project/node_modules/mylib/package.json")
+ },
+ },
+ },
+ },
+ // Config file lifecycle
+ {
+ subScenario: "watch handles tsconfig deleted",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `const x = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ {
+ caption: "delete tsconfig",
+ expectedDiff: "incremental reports config read error while clean build without tsconfig prints usage help",
+ edit: func(sys *TestSys) {
+ sys.removeNoError("/home/src/workspaces/project/tsconfig.json")
+ },
+ },
+ },
+ },
+ {
+ subScenario: "watch handles tsconfig with extends base modified",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `const x = null; const y: string = x;`,
+ "/home/src/workspaces/project/base.json": `{
+ "compilerOptions": { "strict": false }
+}`,
+ "/home/src/workspaces/project/tsconfig.json": `{
+ "extends": "./base.json"
+}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("modify base config to enable strict", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/base.json", `{
+ "compilerOptions": { "strict": true }
+}`)
+ }),
+ },
+ },
+ {
+ subScenario: "watch rebuilds when tsconfig is touched but content unchanged",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `const x = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("touch tsconfig without changing content", func(sys *TestSys) {
+ content := sys.readFileNoError("/home/src/workspaces/project/tsconfig.json")
+ sys.writeFileNoError("/home/src/workspaces/project/tsconfig.json", content)
+ }),
+ },
+ },
+ {
+ subScenario: "watch with tsconfig files list entry deleted",
+ files: FileMap{
+ "/home/src/workspaces/project/a.ts": `export const a = 1;`,
+ "/home/src/workspaces/project/b.ts": `export const b = 2;`,
+ "/home/src/workspaces/project/tsconfig.json": `{
+ "compilerOptions": {},
+ "files": ["a.ts", "b.ts"]
+}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("delete file listed in files array", func(sys *TestSys) {
+ sys.removeNoError("/home/src/workspaces/project/b.ts")
+ }),
+ },
+ },
+ // Module resolution & dependencies
+ {
+ subScenario: "watch detects module going missing then coming back",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import { util } from "./util";`,
+ "/home/src/workspaces/project/util.ts": `export const util = "v1";`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ {
+ caption: "delete util module",
+ edit: func(sys *TestSys) {
+ sys.removeNoError("/home/src/workspaces/project/util.ts")
+ },
+ expectedDiff: "incremental resolves to .js output from prior build while clean build cannot find module",
+ },
+ newTscEdit("recreate util module with new content", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/util.ts", `export const util = "v2";`)
+ }),
+ },
+ },
+ {
+ subScenario: "watch detects scoped package installed",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import { lib } from "@scope/mylib";`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("install scoped package", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/node_modules/@scope/mylib/package.json", `{"name": "@scope/mylib", "types": "index.d.ts"}`)
+ sys.writeFileNoError("/home/src/workspaces/project/node_modules/@scope/mylib/index.d.ts", `export declare const lib: string;`)
+ }),
+ },
+ },
+ {
+ subScenario: "watch detects package json types field edited",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import { lib } from "mylib";`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ "/home/src/workspaces/project/node_modules/mylib/package.json": `{"name": "mylib", "types": "old.d.ts"}`,
+ "/home/src/workspaces/project/node_modules/mylib/old.d.ts": `export declare const lib: number;`,
+ "/home/src/workspaces/project/node_modules/mylib/new.d.ts": `export declare const lib: string;`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("change package.json types field", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/node_modules/mylib/package.json", `{"name": "mylib", "types": "new.d.ts"}`)
+ }),
+ },
+ },
+ {
+ subScenario: "watch detects at-types package installed later",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import * as lib from "untyped-lib";`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ "/home/src/workspaces/project/node_modules/untyped-lib/index.js": `module.exports = {};`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("install @types for the library", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/project/node_modules/@types/untyped-lib/index.d.ts", `declare module "untyped-lib" { export const value: string; }`)
+ sys.writeFileNoError("/home/src/workspaces/project/node_modules/@types/untyped-lib/package.json", `{"name": "@types/untyped-lib", "types": "index.d.ts"}`)
+ }),
+ },
+ },
+ // File operations
+ {
+ subScenario: "watch detects file renamed and renamed back",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import { helper } from "./helper";`,
+ "/home/src/workspaces/project/helper.ts": `export const helper = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ {
+ caption: "rename helper to helper2",
+ edit: func(sys *TestSys) {
+ sys.renameFileNoError("/home/src/workspaces/project/helper.ts", "/home/src/workspaces/project/helper2.ts")
+ },
+ expectedDiff: "incremental resolves to .js output from prior build while clean build cannot find module",
+ },
+ newTscEdit("rename back to helper", func(sys *TestSys) {
+ sys.renameFileNoError("/home/src/workspaces/project/helper2.ts", "/home/src/workspaces/project/helper.ts")
+ }),
+ },
+ },
+ {
+ subScenario: "watch detects file deleted and new file added simultaneously",
+ files: FileMap{
+ "/home/src/workspaces/project/a.ts": `import { b } from "./b";`,
+ "/home/src/workspaces/project/b.ts": `export const b = 1;`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("delete b.ts and create c.ts with updated import", func(sys *TestSys) {
+ sys.removeNoError("/home/src/workspaces/project/b.ts")
+ sys.writeFileNoError("/home/src/workspaces/project/c.ts", `export const c = 2;`)
+ sys.writeFileNoError("/home/src/workspaces/project/a.ts", `import { c } from "./c";`)
+ }),
+ },
+ },
+ {
+ subScenario: "watch handles file rapidly recreated",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import { val } from "./data";`,
+ "/home/src/workspaces/project/data.ts": `export const val = "original";`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("delete and immediately recreate with new content", func(sys *TestSys) {
+ sys.removeNoError("/home/src/workspaces/project/data.ts")
+ sys.writeFileNoError("/home/src/workspaces/project/data.ts", `export const val = "recreated";`)
+ }),
+ },
+ },
+ // Symlinks
+ {
+ subScenario: "watch detects change in symlinked file",
+ files: FileMap{
+ "/home/src/workspaces/project/index.ts": `import { shared } from "./link";`,
+ "/home/src/workspaces/shared/index.ts": `export const shared = "v1";`,
+ "/home/src/workspaces/project/link.ts": vfstest.Symlink("/home/src/workspaces/shared/index.ts"),
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ edits: []*tscEdit{
+ newTscEdit("modify symlink target", func(sys *TestSys) {
+ sys.writeFileNoError("/home/src/workspaces/shared/index.ts", `export const shared = "v2";`)
+ }),
+ },
+ },
}
for _, test := range testCases {
@@ -93,7 +565,7 @@ func noEmitWatchTestInput(
}
func newTscEdit(name string, edit func(sys *TestSys)) *tscEdit {
- return &tscEdit{name, []string{}, edit, ""}
+ return &tscEdit{caption: name, edit: edit}
}
func TestTscNoEmitWatch(t *testing.T) {
diff --git a/internal/execute/tsctests/watcher_race_test.go b/internal/execute/tsctests/watcher_race_test.go
new file mode 100644
index 00000000000..90206d498ca
--- /dev/null
+++ b/internal/execute/tsctests/watcher_race_test.go
@@ -0,0 +1,262 @@
+package tsctests
+
+import (
+ "fmt"
+ "sync"
+ "testing"
+
+ "github.com/microsoft/typescript-go/internal/execute"
+)
+
+// createTestWatcher sets up a minimal project with a tsconfig and
+// returns a Watcher ready for concurrent testing, plus the TestSys
+// for file manipulation.
+func createTestWatcher(t *testing.T) (*execute.Watcher, *TestSys) {
+ t.Helper()
+ input := &tscInput{
+ files: FileMap{
+ "/home/src/workspaces/project/a.ts": `const a: number = 1;`,
+ "/home/src/workspaces/project/b.ts": `import { a } from "./a"; export const b = a;`,
+ "/home/src/workspaces/project/tsconfig.json": `{}`,
+ },
+ commandLineArgs: []string{"--watch"},
+ }
+ sys := newTestSys(input, false)
+ result := execute.CommandLine(sys, []string{"--watch"}, sys)
+ if result.Watcher == nil {
+ t.Fatal("expected Watcher to be non-nil in watch mode")
+ }
+ w, ok := result.Watcher.(*execute.Watcher)
+ if !ok {
+ t.Fatalf("expected *execute.Watcher, got %T", result.Watcher)
+ }
+ return w, sys
+}
+
+// TestWatcherConcurrentDoCycle calls DoCycle from multiple goroutines
+// while modifying source files, exposing data races on Watcher fields
+// such as configModified, program, config, and the underlying
+// FileWatcher state. Run with -race to detect.
+func TestWatcherConcurrentDoCycle(t *testing.T) {
+ t.Parallel()
+ w, sys := createTestWatcher(t)
+
+ var wg sync.WaitGroup
+
+ for i := range 8 {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ for j := range 10 {
+ _ = sys.fsFromFileMap().WriteFile(
+ "/home/src/workspaces/project/a.ts",
+ fmt.Sprintf("const a: number = %d;", i*10+j),
+ )
+ w.DoCycle()
+ }
+ }(i)
+ }
+
+ wg.Wait()
+}
+
+// TestWatcherDoCycleWithConcurrentStateReads calls DoCycle from
+// multiple goroutines, some modifying files and some not, to test
+// concurrent access to all Watcher and FileWatcher state.
+func TestWatcherDoCycleWithConcurrentStateReads(t *testing.T) {
+ t.Parallel()
+ w, sys := createTestWatcher(t)
+
+ var wg sync.WaitGroup
+
+ // DoCycle goroutines
+ for i := range 4 {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ for j := range 15 {
+ _ = sys.fsFromFileMap().WriteFile(
+ "/home/src/workspaces/project/a.ts",
+ fmt.Sprintf("const a: number = %d;", i*15+j),
+ )
+ w.DoCycle()
+ }
+ }(i)
+ }
+
+ // State reader goroutines
+ for range 8 {
+ wg.Go(func() {
+ for range 50 {
+ w.DoCycle()
+ w.DoCycle()
+ w.DoCycle()
+ w.DoCycle()
+ }
+ })
+ }
+
+ wg.Wait()
+}
+
+// TestWatcherConcurrentFileChangesAndDoCycle creates, modifies, and
+// deletes files from multiple goroutines while DoCycle runs, testing
+// races between FS mutations and watch state updates.
+func TestWatcherConcurrentFileChangesAndDoCycle(t *testing.T) {
+ t.Parallel()
+ w, sys := createTestWatcher(t)
+
+ var wg sync.WaitGroup
+
+ // File creators
+ for i := range 4 {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ for j := range 20 {
+ path := fmt.Sprintf("/home/src/workspaces/project/gen_%d_%d.ts", i, j)
+ _ = sys.fsFromFileMap().WriteFile(path, fmt.Sprintf("export const x%d_%d = %d;", i, j, j))
+ }
+ }(i)
+ }
+
+ // File deleters
+ wg.Go(func() {
+ for j := range 20 {
+ _ = sys.fsFromFileMap().Remove(
+ fmt.Sprintf("/home/src/workspaces/project/gen_0_%d.ts", j),
+ )
+ }
+ })
+
+ // DoCycle callers
+ for range 4 {
+ wg.Go(func() {
+ for range 10 {
+ w.DoCycle()
+ }
+ })
+ }
+
+ wg.Wait()
+}
+
+// TestWatcherRapidConfigChanges modifies tsconfig.json rapidly from
+// multiple goroutines while DoCycle runs, testing races on
+// config-related fields (configModified, configHasErrors,
+// configFilePaths, config, extendedConfigCache).
+func TestWatcherRapidConfigChanges(t *testing.T) {
+ t.Parallel()
+ w, sys := createTestWatcher(t)
+
+ var wg sync.WaitGroup
+
+ configs := []string{
+ `{}`,
+ `{"compilerOptions": {"strict": true}}`,
+ `{"compilerOptions": {"target": "ES2020"}}`,
+ `{"compilerOptions": {"noEmit": true}}`,
+ }
+
+ // Config modifiers + DoCycle
+ for i := range 3 {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ for j := range 10 {
+ _ = sys.fsFromFileMap().WriteFile(
+ "/home/src/workspaces/project/tsconfig.json",
+ configs[(i+j)%len(configs)],
+ )
+ w.DoCycle()
+ }
+ }(i)
+ }
+
+ // Concurrent source file modifications
+ for i := range 2 {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ for j := range 15 {
+ _ = sys.fsFromFileMap().WriteFile(
+ "/home/src/workspaces/project/a.ts",
+ fmt.Sprintf("const a: number = %d;", i*15+j),
+ )
+ w.DoCycle()
+ }
+ }(i)
+ }
+
+ // State readers
+ for range 4 {
+ wg.Go(func() {
+ for range 30 {
+ w.DoCycle()
+ w.DoCycle()
+ }
+ })
+ }
+
+ wg.Wait()
+}
+
+// TestWatcherConcurrentDoCycleNoChanges calls DoCycle from many
+// goroutines when no files have changed, testing the early-return
+// path where WatchState is read and HasChanges is called.
+func TestWatcherConcurrentDoCycleNoChanges(t *testing.T) {
+ t.Parallel()
+ w, _ := createTestWatcher(t)
+
+ var wg sync.WaitGroup
+
+ for range 16 {
+ wg.Go(func() {
+ for range 50 {
+ w.DoCycle()
+ }
+ })
+ }
+
+ wg.Wait()
+}
+
+// TestWatcherAlternatingModifyAndDoCycle alternates between modifying
+// a file and calling DoCycle from different goroutines, creating a
+// realistic scenario where the file watcher detects changes mid-cycle.
+func TestWatcherAlternatingModifyAndDoCycle(t *testing.T) {
+ t.Parallel()
+ w, sys := createTestWatcher(t)
+
+ var wg sync.WaitGroup
+
+ // Writer goroutine: continuously modifies files
+ wg.Go(func() {
+ for j := range 100 {
+ _ = sys.fsFromFileMap().WriteFile(
+ "/home/src/workspaces/project/a.ts",
+ fmt.Sprintf("const a: number = %d;", j),
+ )
+ }
+ })
+
+ // Multiple DoCycle goroutines
+ for range 4 {
+ wg.Go(func() {
+ for range 25 {
+ w.DoCycle()
+ }
+ })
+ }
+
+ // State reader goroutines
+ for range 4 {
+ wg.Go(func() {
+ for range 100 {
+ w.DoCycle()
+ }
+ })
+ }
+
+ wg.Wait()
+}
diff --git a/internal/execute/watcher.go b/internal/execute/watcher.go
index 10e09d85e8c..a47945dd4f1 100644
--- a/internal/execute/watcher.go
+++ b/internal/execute/watcher.go
@@ -1,30 +1,76 @@
package execute
import (
- "fmt"
"reflect"
+ "sync"
"time"
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/execute/incremental"
"github.com/microsoft/typescript-go/internal/execute/tsc"
"github.com/microsoft/typescript-go/internal/tsoptions"
+ "github.com/microsoft/typescript-go/internal/tspath"
+ "github.com/microsoft/typescript-go/internal/vfs/cachedvfs"
+ "github.com/microsoft/typescript-go/internal/vfs/trackingvfs"
+ "github.com/microsoft/typescript-go/internal/vfs/vfswatch"
)
+type cachedSourceFile struct {
+ file *ast.SourceFile
+ modTime time.Time
+}
+
+type watchCompilerHost struct {
+ compiler.CompilerHost
+ cache *collections.SyncMap[tspath.Path, *cachedSourceFile]
+}
+
+func (h *watchCompilerHost) GetSourceFile(opts ast.SourceFileParseOptions) *ast.SourceFile {
+ info := h.CompilerHost.FS().Stat(opts.FileName)
+
+ if cached, ok := h.cache.Load(opts.Path); ok {
+ if info != nil && info.ModTime().Equal(cached.modTime) {
+ return cached.file
+ }
+ }
+
+ file := h.CompilerHost.GetSourceFile(opts)
+ if file != nil {
+ if info != nil {
+ h.cache.Store(opts.Path, &cachedSourceFile{
+ file: file,
+ modTime: info.ModTime(),
+ })
+ }
+ } else {
+ h.cache.Delete(opts.Path)
+ }
+ return file
+}
+
type Watcher struct {
+ mu sync.Mutex
sys tsc.System
configFileName string
config *tsoptions.ParsedCommandLine
compilerOptionsFromCommandLine *core.CompilerOptions
reportDiagnostic tsc.DiagnosticReporter
reportErrorSummary tsc.DiagnosticsReporter
+ reportWatchStatus tsc.DiagnosticReporter
testing tsc.CommandLineTesting
- host compiler.CompilerHost
- program *incremental.Program
- prevModified map[string]time.Time
- configModified bool
+ program *incremental.Program
+ extendedConfigCache *tsc.ExtendedConfigCache
+ configModified bool
+ configHasErrors bool
+ configFilePaths []string
+
+ sourceFileCache *collections.SyncMap[tspath.Path, *cachedSourceFile]
+ fileWatcher *vfswatch.FileWatcher
}
var _ tsc.Watcher = (*Watcher)(nil)
@@ -43,65 +89,119 @@ func createWatcher(
compilerOptionsFromCommandLine: compilerOptionsFromCommandLine,
reportDiagnostic: reportDiagnostic,
reportErrorSummary: reportErrorSummary,
+ reportWatchStatus: tsc.CreateWatchStatusReporter(sys, configParseResult.Locale(), configParseResult.CompilerOptions(), testing),
testing: testing,
- // reportWatchStatus: createWatchStatusReporter(sys, configParseResult.CompilerOptions().Pretty),
+ sourceFileCache: &collections.SyncMap[tspath.Path, *cachedSourceFile]{},
}
if configParseResult.ConfigFile != nil {
w.configFileName = configParseResult.ConfigFile.SourceFile.FileName()
}
+ w.fileWatcher = vfswatch.NewFileWatcher(
+ sys.FS(),
+ w.config.ParsedConfig.WatchOptions.WatchInterval(),
+ testing != nil,
+ w.DoCycle,
+ )
return w
}
func (w *Watcher) start() {
- w.host = compiler.NewCompilerHost(w.sys.GetCurrentDirectory(), w.sys.FS(), w.sys.DefaultLibraryPath(), nil, getTraceFromSys(w.sys, w.config.Locale(), w.testing))
- w.program = incremental.ReadBuildInfoProgram(w.config, incremental.NewBuildInfoReader(w.host), w.host)
+ w.mu.Lock()
+ w.extendedConfigCache = &tsc.ExtendedConfigCache{}
+ host := compiler.NewCompilerHost(w.sys.GetCurrentDirectory(), w.sys.FS(), w.sys.DefaultLibraryPath(), w.extendedConfigCache, getTraceFromSys(w.sys, w.config.Locale(), w.testing))
+ w.program = incremental.ReadBuildInfoProgram(w.config, incremental.NewBuildInfoReader(host), host)
+
+ if w.configFileName != "" {
+ w.configFilePaths = append([]string{w.configFileName}, w.config.ExtendedSourceFiles()...)
+ }
+
+ w.reportWatchStatus(ast.NewCompilerDiagnostic(diagnostics.Starting_compilation_in_watch_mode))
+ w.doBuild()
+ w.mu.Unlock()
if w.testing == nil {
- watchInterval := w.config.ParsedConfig.WatchOptions.WatchInterval()
- for {
- w.DoCycle()
- time.Sleep(watchInterval)
- }
- } else {
- // Initial compilation in test mode
- w.DoCycle()
+ w.fileWatcher.Run(w.sys.Now)
}
}
func (w *Watcher) DoCycle() {
- // if this function is updated, make sure to update `RunWatchCycle` in export_test.go as needed
-
- if w.hasErrorsInTsConfig() {
- // these are unrecoverable errors--report them and do not build
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ if w.recheckTsConfig() {
return
}
- // updateProgram()
+ if !w.fileWatcher.WatchStateUninitialized() && !w.configModified && !w.fileWatcher.HasChangesFromWatchState() {
+ if w.testing != nil {
+ w.testing.OnProgram(w.program)
+ }
+ return
+ }
+
+ w.reportWatchStatus(ast.NewCompilerDiagnostic(diagnostics.File_change_detected_Starting_incremental_compilation))
+ w.doBuild()
+}
+
+func (w *Watcher) doBuild() {
+ if w.configModified {
+ w.sourceFileCache = &collections.SyncMap[tspath.Path, *cachedSourceFile]{}
+ }
+
+ cached := cachedvfs.From(w.sys.FS())
+ tfs := &trackingvfs.FS{Inner: cached}
+ innerHost := compiler.NewCompilerHost(w.sys.GetCurrentDirectory(), tfs, w.sys.DefaultLibraryPath(), w.extendedConfigCache, getTraceFromSys(w.sys, w.config.Locale(), w.testing))
+ host := &watchCompilerHost{CompilerHost: innerHost, cache: w.sourceFileCache}
+
+ var wildcardDirs map[string]bool
+ if w.config.ConfigFile != nil {
+ wildcardDirs = w.config.WildcardDirectories()
+ for dir := range wildcardDirs {
+ tfs.SeenFiles.Add(dir)
+ }
+ if len(wildcardDirs) > 0 {
+ w.config = w.config.ReloadFileNamesOfParsedCommandLine(w.sys.FS())
+ }
+ }
+ for _, path := range w.configFilePaths {
+ tfs.SeenFiles.Add(path)
+ }
+
w.program = incremental.NewProgram(compiler.NewProgram(compiler.ProgramOptions{
Config: w.config,
- Host: w.host,
+ Host: host,
}), w.program, nil, w.testing != nil)
- if w.hasBeenModified(w.program.GetProgram()) {
- fmt.Fprintln(w.sys.Writer(), "build starting at", w.sys.Now().Format("03:04:05 PM"))
- timeStart := w.sys.Now()
- w.compileAndEmit()
- fmt.Fprintf(w.sys.Writer(), "build finished in %.3fs\n", w.sys.Now().Sub(timeStart).Seconds())
+ result := w.compileAndEmit()
+ cached.DisableAndClearCache()
+ w.fileWatcher.UpdateWatchState(tfs.SeenFiles.ToSlice(), wildcardDirs)
+ w.fileWatcher.SetPollInterval(w.config.ParsedConfig.WatchOptions.WatchInterval())
+ w.configModified = false
+
+ programFiles := w.program.GetProgram().FilesByPath()
+ w.sourceFileCache.Range(func(path tspath.Path, _ *cachedSourceFile) bool {
+ if _, ok := programFiles[path]; !ok {
+ w.sourceFileCache.Delete(path)
+ }
+ return true
+ })
+
+ errorCount := len(result.Diagnostics)
+ if errorCount == 1 {
+ w.reportWatchStatus(ast.NewCompilerDiagnostic(diagnostics.Found_1_error_Watching_for_file_changes))
} else {
- // print something???
- // fmt.Fprintln(w.sys.Writer(), "no changes detected at ", w.sys.Now())
+ w.reportWatchStatus(ast.NewCompilerDiagnostic(diagnostics.Found_0_errors_Watching_for_file_changes, errorCount))
}
+
if w.testing != nil {
w.testing.OnProgram(w.program)
}
}
-func (w *Watcher) compileAndEmit() {
- // !!! output/error reporting is currently the same as non-watch mode
- // diagnostics, emitResult, exitStatus :=
- tsc.EmitFilesAndReportErrors(tsc.EmitInput{
+func (w *Watcher) compileAndEmit() tsc.CompileAndEmitResult {
+ return tsc.EmitFilesAndReportErrors(tsc.EmitInput{
Sys: w.sys,
ProgramLike: w.program,
Program: w.program.GetProgram(),
+ Config: w.config,
ReportDiagnostic: w.reportDiagnostic,
ReportErrorSummary: w.reportErrorSummary,
Writer: w.sys.Writer(),
@@ -110,58 +210,61 @@ func (w *Watcher) compileAndEmit() {
})
}
-func (w *Watcher) hasErrorsInTsConfig() bool {
- // only need to check and reparse tsconfig options/update host if we are watching a config file
- extendedConfigCache := &tsc.ExtendedConfigCache{}
- if w.configFileName != "" {
- // !!! need to check that this merges compileroptions correctly. This differs from non-watch, since we allow overriding of previous options
- configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFile(w.configFileName, w.compilerOptionsFromCommandLine, nil, w.sys, extendedConfigCache)
- if len(errors) > 0 {
- for _, e := range errors {
- w.reportDiagnostic(e)
+func (w *Watcher) recheckTsConfig() bool {
+ if w.configFileName == "" {
+ return false
+ }
+
+ if !w.configHasErrors && len(w.configFilePaths) > 0 {
+ changed := false
+ for _, path := range w.configFilePaths {
+ old, ok := w.fileWatcher.WatchStateEntry(path)
+ if !ok {
+ changed = true
+ break
+ }
+ s := w.sys.FS().Stat(path)
+ if !old.Exists {
+ if s != nil {
+ changed = true
+ break
+ }
+ } else {
+ if s == nil || !s.ModTime().Equal(old.ModTime) {
+ changed = true
+ break
+ }
}
- return true
}
- // CompilerOptions contain fields which should not be compared; clone to get a copy without those set.
- if !reflect.DeepEqual(w.config.CompilerOptions().Clone(), configParseResult.CompilerOptions().Clone()) {
- // fmt.Fprintln(w.sys.Writer(), "build triggered due to config change")
- w.configModified = true
+ if !changed {
+ return false
}
- w.config = configParseResult
}
- w.host = compiler.NewCompilerHost(w.sys.GetCurrentDirectory(), w.sys.FS(), w.sys.DefaultLibraryPath(), extendedConfigCache, getTraceFromSys(w.sys, w.config.Locale(), w.testing))
- return false
-}
-func (w *Watcher) hasBeenModified(program *compiler.Program) bool {
- // checks watcher's snapshot against program file modified times
- currState := map[string]time.Time{}
- filesModified := w.configModified
- for _, sourceFile := range program.SourceFiles() {
- fileName := sourceFile.FileName()
- s := w.sys.FS().Stat(fileName)
- if s == nil {
- // do nothing; if file is in program.SourceFiles() but is not found when calling Stat, file has been very recently deleted.
- // deleted files are handled outside of this loop
- continue
+ extendedConfigCache := &tsc.ExtendedConfigCache{}
+ configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFile(w.configFileName, w.compilerOptionsFromCommandLine, nil, w.sys, extendedConfigCache)
+ if len(errors) > 0 {
+ for _, e := range errors {
+ w.reportDiagnostic(e)
}
- currState[fileName] = s.ModTime()
- if !filesModified {
- if currState[fileName] != w.prevModified[fileName] {
- // fmt.Fprint(w.sys.Writer(), "build triggered from ", fileName, ": ", w.prevModified[fileName], " -> ", currState[fileName], "\n")
- filesModified = true
- }
- // catch cases where no files are modified, but some were deleted
- delete(w.prevModified, fileName)
+ w.configHasErrors = true
+ errorCount := len(errors)
+ if errorCount == 1 {
+ w.reportWatchStatus(ast.NewCompilerDiagnostic(diagnostics.Found_1_error_Watching_for_file_changes))
+ } else {
+ w.reportWatchStatus(ast.NewCompilerDiagnostic(diagnostics.Found_0_errors_Watching_for_file_changes, errorCount))
}
+ return true
}
- if !filesModified && len(w.prevModified) > 0 {
- // fmt.Fprintln(w.sys.Writer(), "build triggered due to deleted file")
- filesModified = true
+ if w.configHasErrors {
+ w.configModified = true
}
- w.prevModified = currState
-
- // reset state for next cycle
- w.configModified = false
- return filesModified
+ w.configHasErrors = false
+ w.configFilePaths = append([]string{w.configFileName}, configParseResult.ExtendedSourceFiles()...)
+ if !reflect.DeepEqual(w.config.ParsedConfig, configParseResult.ParsedConfig) {
+ w.configModified = true
+ }
+ w.config = configParseResult
+ w.extendedConfigCache = extendedConfigCache
+ return false
}
diff --git a/internal/vfs/trackingvfs/trackingvfs.go b/internal/vfs/trackingvfs/trackingvfs.go
new file mode 100644
index 00000000000..e79aabf41e9
--- /dev/null
+++ b/internal/vfs/trackingvfs/trackingvfs.go
@@ -0,0 +1,69 @@
+// Package trackingvfs provides a VFS wrapper that records every file path
+// accessed during compilation. This allows watch mode to know exactly which
+// files and directories the compiler depended on, including non-existent
+// paths from failed module resolution.
+package trackingvfs
+
+import (
+ "time"
+
+ "github.com/microsoft/typescript-go/internal/collections"
+ "github.com/microsoft/typescript-go/internal/vfs"
+)
+
+// FS wraps a vfs.FS and records every path accessed via read-like operations.
+// Write operations (WriteFile, Remove, Chtimes) are not tracked since they
+// represent outputs, not dependencies.
+type FS struct {
+ Inner vfs.FS
+ SeenFiles collections.SyncSet[string]
+}
+
+var _ vfs.FS = (*FS)(nil)
+
+func (fs *FS) ReadFile(path string) (string, bool) {
+ fs.SeenFiles.Add(path)
+ return fs.Inner.ReadFile(path)
+}
+
+func (fs *FS) FileExists(path string) bool {
+ fs.SeenFiles.Add(path)
+ return fs.Inner.FileExists(path)
+}
+
+func (fs *FS) UseCaseSensitiveFileNames() bool { return fs.Inner.UseCaseSensitiveFileNames() }
+
+func (fs *FS) WriteFile(path string, data string) error {
+ return fs.Inner.WriteFile(path, data)
+}
+
+func (fs *FS) Remove(path string) error { return fs.Inner.Remove(path) }
+
+func (fs *FS) Chtimes(path string, aTime time.Time, mTime time.Time) error {
+ return fs.Inner.Chtimes(path, aTime, mTime)
+}
+
+func (fs *FS) DirectoryExists(path string) bool {
+ fs.SeenFiles.Add(path)
+ return fs.Inner.DirectoryExists(path)
+}
+
+func (fs *FS) GetAccessibleEntries(path string) vfs.Entries {
+ fs.SeenFiles.Add(path)
+ return fs.Inner.GetAccessibleEntries(path)
+}
+
+func (fs *FS) Stat(path string) vfs.FileInfo {
+ fs.SeenFiles.Add(path)
+ return fs.Inner.Stat(path)
+}
+
+func (fs *FS) WalkDir(root string, walkFn vfs.WalkDirFunc) error {
+ fs.SeenFiles.Add(root)
+ return fs.Inner.WalkDir(root, func(path string, d vfs.DirEntry, err error) error {
+ fs.SeenFiles.Add(path)
+ return walkFn(path, d, err)
+ })
+}
+
+func (fs *FS) Realpath(path string) string { return fs.Inner.Realpath(path) }
diff --git a/internal/vfs/vfswatch/vfswatch.go b/internal/vfs/vfswatch/vfswatch.go
new file mode 100644
index 00000000000..dabfcaaf122
--- /dev/null
+++ b/internal/vfs/vfswatch/vfswatch.go
@@ -0,0 +1,262 @@
+// This package implements a polling-based file watcher designed
+// for use by both the CLI watcher and the language server.
+package vfswatch
+
+import (
+ "slices"
+ "sync"
+ "time"
+
+ "github.com/microsoft/typescript-go/internal/vfs"
+ "github.com/zeebo/xxh3"
+)
+
+const debounceWait = 250 * time.Millisecond
+
+type WatchEntry struct {
+ ModTime time.Time
+ Exists bool
+ ChildrenHash uint64 // 0 if not tracked
+}
+
+type FileWatcher struct {
+ fs vfs.FS
+ pollInterval time.Duration
+ testing bool
+ callback func()
+ watchState map[string]WatchEntry
+ wildcardDirectories map[string]bool
+ mu sync.Mutex
+}
+
+func NewFileWatcher(fs vfs.FS, pollInterval time.Duration, testing bool, callback func()) *FileWatcher {
+ return &FileWatcher{
+ fs: fs,
+ pollInterval: pollInterval,
+ testing: testing,
+ callback: callback,
+ }
+}
+
+func (fw *FileWatcher) SetPollInterval(d time.Duration) {
+ fw.mu.Lock()
+ defer fw.mu.Unlock()
+ fw.pollInterval = d
+}
+
+func (fw *FileWatcher) WatchStateEntry(path string) (WatchEntry, bool) {
+ fw.mu.Lock()
+ defer fw.mu.Unlock()
+ e, ok := fw.watchState[path]
+ return e, ok
+}
+
+func (fw *FileWatcher) WatchStateUninitialized() bool {
+ fw.mu.Lock()
+ defer fw.mu.Unlock()
+ return fw.watchState == nil
+}
+
+func (fw *FileWatcher) UpdateWatchState(paths []string, wildcardDirs map[string]bool) {
+ state := snapshotPaths(fw.fs, paths, wildcardDirs)
+ fw.mu.Lock()
+ defer fw.mu.Unlock()
+ fw.watchState = state
+ fw.wildcardDirectories = wildcardDirs
+}
+
+func (fw *FileWatcher) WaitForSettled(now func() time.Time) {
+ if fw.testing {
+ return
+ }
+ fw.mu.Lock()
+ wildcardDirs := fw.wildcardDirectories
+ pollInterval := fw.pollInterval
+ fw.mu.Unlock()
+ current := fw.currentState()
+ settledAt := now()
+ tick := min(pollInterval, debounceWait)
+ for now().Sub(settledAt) < debounceWait {
+ time.Sleep(tick)
+ if fw.hasChanges(current, wildcardDirs) {
+ current = fw.currentState()
+ settledAt = now()
+ }
+ }
+}
+
+func (fw *FileWatcher) currentState() map[string]WatchEntry {
+ fw.mu.Lock()
+ watchState := fw.watchState
+ wildcardDirs := fw.wildcardDirectories
+ fw.mu.Unlock()
+ state := make(map[string]WatchEntry, len(watchState))
+ for fn := range watchState {
+ if s := fw.fs.Stat(fn); s != nil {
+ state[fn] = WatchEntry{ModTime: s.ModTime(), Exists: true}
+ } else {
+ state[fn] = WatchEntry{Exists: false}
+ }
+ }
+ for dir, recursive := range wildcardDirs {
+ if !recursive {
+ snapshotDirEntry(fw.fs, state, dir)
+ continue
+ }
+ _ = fw.fs.WalkDir(dir, func(path string, d vfs.DirEntry, err error) error {
+ if err != nil || !d.IsDir() {
+ return nil
+ }
+ snapshotDirEntry(fw.fs, state, path)
+ return nil
+ })
+ }
+ return state
+}
+
+func snapshotPaths(fs vfs.FS, paths []string, wildcardDirs map[string]bool) map[string]WatchEntry {
+ state := make(map[string]WatchEntry, len(paths))
+ for _, fn := range paths {
+ if s := fs.Stat(fn); s != nil {
+ entry := WatchEntry{ModTime: s.ModTime(), Exists: true}
+ if s.IsDir() {
+ entries := fs.GetAccessibleEntries(fn)
+ entry.ChildrenHash = hashEntries(entries)
+ }
+ state[fn] = entry
+ } else {
+ state[fn] = WatchEntry{Exists: false}
+ }
+ }
+ for dir, recursive := range wildcardDirs {
+ if !recursive {
+ snapshotDirEntry(fs, state, dir)
+ continue
+ }
+ _ = fs.WalkDir(dir, func(path string, d vfs.DirEntry, err error) error {
+ if err != nil || !d.IsDir() {
+ return nil
+ }
+ snapshotDirEntry(fs, state, path)
+ return nil
+ })
+ }
+ return state
+}
+
+func snapshotDirEntry(fs vfs.FS, state map[string]WatchEntry, dir string) {
+ entries := fs.GetAccessibleEntries(dir)
+ h := hashEntries(entries)
+ if existing, ok := state[dir]; ok {
+ existing.ChildrenHash = h
+ state[dir] = existing
+ } else {
+ if s := fs.Stat(dir); s != nil {
+ state[dir] = WatchEntry{ModTime: s.ModTime(), Exists: true, ChildrenHash: h}
+ }
+ }
+}
+
+func hashEntries(entries vfs.Entries) uint64 {
+ dirs := slices.Clone(entries.Directories)
+ files := slices.Clone(entries.Files)
+ slices.Sort(dirs)
+ slices.Sort(files)
+ var h xxh3.Hasher
+ for _, name := range dirs {
+ h.WriteString("d:")
+ h.WriteString(name)
+ h.Write([]byte{0})
+ }
+ for _, name := range files {
+ h.WriteString("f:")
+ h.WriteString(name)
+ h.Write([]byte{0})
+ }
+ return h.Sum64()
+}
+
+func dirChanged(fs vfs.FS, baseline map[string]WatchEntry, dir string) bool {
+ entry, ok := baseline[dir]
+ if !ok {
+ return true
+ }
+ if entry.ChildrenHash != 0 {
+ entries := fs.GetAccessibleEntries(dir)
+ if hashEntries(entries) != entry.ChildrenHash {
+ return true
+ }
+ }
+ return false
+}
+
+func (fw *FileWatcher) hasChanges(baseline map[string]WatchEntry, wildcardDirs map[string]bool) bool {
+ for path, old := range baseline {
+ s := fw.fs.Stat(path)
+ if !old.Exists {
+ if s != nil {
+ return true
+ }
+ } else {
+ if s == nil || !s.ModTime().Equal(old.ModTime) {
+ return true
+ }
+ if old.ChildrenHash != 0 {
+ entries := fw.fs.GetAccessibleEntries(path)
+ if hashEntries(entries) != old.ChildrenHash {
+ return true
+ }
+ }
+ }
+ }
+ for dir, recursive := range wildcardDirs {
+ if !recursive {
+ if dirChanged(fw.fs, baseline, dir) {
+ return true
+ }
+ continue
+ }
+ found := false
+ _ = fw.fs.WalkDir(dir, func(path string, d vfs.DirEntry, err error) error {
+ if err != nil || !d.IsDir() {
+ return nil
+ }
+ if dirChanged(fw.fs, baseline, path) {
+ found = true
+ return vfs.SkipAll
+ }
+ return nil
+ })
+ if found {
+ return true
+ }
+ }
+ return false
+}
+
+// HasChangesFromWatchState compares the current filesystem against the
+// stored watch state. Safe for concurrent use: watchState and
+// wildcardDirectories are snapshotted under lock; the maps themselves
+// are never mutated after creation (UpdateWatchState replaces them).
+func (fw *FileWatcher) HasChangesFromWatchState() bool {
+ fw.mu.Lock()
+ ws := fw.watchState
+ wildcardDirs := fw.wildcardDirectories
+ fw.mu.Unlock()
+ return fw.hasChanges(ws, wildcardDirs)
+}
+
+func (fw *FileWatcher) Run(now func() time.Time) {
+ for {
+ fw.mu.Lock()
+ interval := fw.pollInterval
+ ws := fw.watchState
+ wildcardDirs := fw.wildcardDirectories
+ fw.mu.Unlock()
+ time.Sleep(interval)
+ if ws == nil || fw.hasChanges(ws, wildcardDirs) {
+ fw.WaitForSettled(now)
+ fw.callback()
+ }
+ }
+}
diff --git a/internal/vfs/vfswatch/vfswatch_race_test.go b/internal/vfs/vfswatch/vfswatch_race_test.go
new file mode 100644
index 00000000000..b1004fbc2a6
--- /dev/null
+++ b/internal/vfs/vfswatch/vfswatch_race_test.go
@@ -0,0 +1,319 @@
+package vfswatch_test
+
+import (
+ "fmt"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/microsoft/typescript-go/internal/vfs"
+ "github.com/microsoft/typescript-go/internal/vfs/vfstest"
+ "github.com/microsoft/typescript-go/internal/vfs/vfswatch"
+)
+
+var defaultPaths = []string{
+ "/src/a.ts",
+ "/src/b.ts",
+ "/src/c.ts",
+ "/src/sub/d.ts",
+ "/tsconfig.json",
+}
+
+func newTestFS() vfs.FS {
+ return vfstest.FromMap(map[string]string{
+ "/src/a.ts": "const a = 1;",
+ "/src/b.ts": "const b = 2;",
+ "/src/c.ts": "const c = 3;",
+ "/src/sub/d.ts": "const d = 4;",
+ "/tsconfig.json": `{}`,
+ }, true)
+}
+
+func newWatcherWithState(fs vfs.FS) *vfswatch.FileWatcher {
+ fw := vfswatch.NewFileWatcher(fs, 10*time.Millisecond, true, func() {})
+ fw.UpdateWatchState(defaultPaths, nil)
+ return fw
+}
+
+// TestRaceHasChangesVsUpdateWatchState tests for data races between
+// concurrent HasChanges reads and UpdateWatchState writes on the
+// WatchState map.
+func TestRaceHasChangesVsUpdateWatchState(t *testing.T) {
+ t.Parallel()
+ fs := newTestFS()
+ fw := newWatcherWithState(fs)
+
+ var wg sync.WaitGroup
+
+ for range 10 {
+ wg.Go(func() {
+ for range 200 {
+ fw.HasChangesFromWatchState()
+ }
+ })
+ }
+
+ for range 5 {
+ wg.Go(func() {
+ for range 100 {
+ fw.UpdateWatchState([]string{"/src/a.ts", "/src/b.ts"}, nil)
+ }
+ })
+ }
+
+ wg.Wait()
+}
+
+// TestRaceWildcardDirectoriesAccess tests for data races when
+// WildcardDirectories is read internally by HasChanges while being
+// replaced concurrently via UpdateWatchState.
+func TestRaceWildcardDirectoriesAccess(t *testing.T) {
+ t.Parallel()
+ fs := newTestFS()
+ fw := newWatcherWithState(fs)
+ fw.UpdateWatchState(defaultPaths, map[string]bool{"/src": true})
+
+ var wg sync.WaitGroup
+
+ for range 10 {
+ wg.Go(func() {
+ for range 200 {
+ fw.HasChangesFromWatchState()
+ }
+ })
+ }
+
+ for range 5 {
+ wg.Go(func() {
+ for range 100 {
+ fw.UpdateWatchState(defaultPaths, map[string]bool{"/src": true})
+ }
+ })
+ }
+
+ wg.Wait()
+}
+
+// TestRacePollIntervalAccess tests for data races on the PollInterval
+// field when it is read and written from multiple goroutines.
+func TestRacePollIntervalAccess(t *testing.T) {
+ t.Parallel()
+ fs := newTestFS()
+ fw := newWatcherWithState(fs)
+
+ var wg sync.WaitGroup
+
+ for range 10 {
+ wg.Go(func() {
+ for range 500 {
+ fw.HasChangesFromWatchState()
+ }
+ })
+ }
+
+ for i := range 5 {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ for j := range 200 {
+ fw.SetPollInterval(time.Duration(i*200+j) * time.Millisecond)
+ }
+ }(i)
+ }
+
+ wg.Wait()
+}
+
+// TestRaceMixedOperations hammers all FileWatcher operations
+// concurrently: HasChanges, UpdateWatchState, FS mutations,
+// and PollInterval writes.
+func TestRaceMixedOperations(t *testing.T) {
+ t.Parallel()
+ fs := newTestFS()
+ fw := newWatcherWithState(fs)
+ fw.UpdateWatchState(defaultPaths, map[string]bool{"/src": true})
+
+ var wg sync.WaitGroup
+
+ // HasChanges readers
+ for range 8 {
+ wg.Go(func() {
+ for range 100 {
+ fw.HasChangesFromWatchState()
+ }
+ })
+ }
+
+ // UpdateWatchState writers
+ for i := range 4 {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ for j := range 50 {
+ paths := []string{"/src/a.ts", fmt.Sprintf("/src/new_%d_%d.ts", i, j)}
+ fw.UpdateWatchState(paths, map[string]bool{"/src": true})
+ }
+ }(i)
+ }
+
+ // FS modifiers
+ for i := range 4 {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ for j := range 50 {
+ path := fmt.Sprintf("/src/gen_%d_%d.ts", i, j)
+ _ = fs.WriteFile(path, fmt.Sprintf("const x = %d;", j))
+ if j%3 == 0 {
+ _ = fs.Remove(path)
+ }
+ }
+ }(i)
+ }
+
+ // PollInterval writers
+ for i := range 2 {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ for j := range 100 {
+ fw.SetPollInterval(time.Duration(50+j) * time.Millisecond)
+ }
+ }(i)
+ }
+
+ wg.Wait()
+}
+
+// TestRaceUpdateWithConcurrentFileModifications creates and deletes
+// files on the FS while UpdateWatchState is scanning the same FS,
+// testing for races between the FS walker and concurrent mutations.
+func TestRaceUpdateWithConcurrentFileModifications(t *testing.T) {
+ t.Parallel()
+ fs := newTestFS()
+ fw := newWatcherWithState(fs)
+ fw.UpdateWatchState(defaultPaths, map[string]bool{"/src": true})
+
+ var wg sync.WaitGroup
+
+ // Rapid file creation/deletion
+ for i := range 6 {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ for j := range 100 {
+ path := fmt.Sprintf("/src/churn_%d_%d.ts", i, j)
+ _ = fs.WriteFile(path, fmt.Sprintf("export const v = %d;", j))
+ _ = fs.Remove(path)
+ }
+ }(i)
+ }
+
+ // Concurrent UpdateWatchState (walks the FS tree via WildcardDirectories)
+ for range 4 {
+ wg.Go(func() {
+ for range 50 {
+ fw.UpdateWatchState([]string{"/src/a.ts", "/tsconfig.json"}, map[string]bool{"/src": true})
+ }
+ })
+ }
+
+ wg.Wait()
+}
+
+// FuzzFileWatcherOperations fuzzes random sequences of file operations
+// and watcher state management to find panics and edge cases.
+// Run with -race to also detect data races.
+func FuzzFileWatcherOperations(f *testing.F) {
+ f.Add([]byte{0, 1, 2, 3, 0, 1, 2, 3})
+ f.Add([]byte{2, 2, 2, 0, 0, 1, 3, 3})
+ f.Add([]byte{3, 3, 3, 3, 0, 0, 0, 0})
+ f.Add([]byte{4, 4, 4, 0, 2, 1, 3, 2})
+ f.Add([]byte{5, 5, 5, 5, 5, 5, 5, 5})
+ f.Add([]byte{0, 0, 0, 0, 0, 0, 0, 0})
+ f.Add([]byte{1, 1, 1, 1, 1, 1, 1, 1})
+
+ f.Fuzz(func(t *testing.T, ops []byte) {
+ if len(ops) == 0 {
+ return
+ }
+
+ fs := newTestFS()
+ fw := newWatcherWithState(fs)
+
+ files := []string{"/src/a.ts", "/src/b.ts", "/src/c.ts", "/src/new.ts", "/src/sub/new.ts"}
+
+ for i, op := range ops {
+ path := files[i%len(files)]
+
+ switch op % 6 {
+ case 0: // Write/modify a file
+ _ = fs.WriteFile(path, fmt.Sprintf("const x = %d;", i))
+ case 1: // Remove a file
+ _ = fs.Remove(path)
+ case 2: // Check for changes against current state
+ fw.HasChangesFromWatchState()
+ case 3: // Rebuild watch state
+ fw.UpdateWatchState(files, nil)
+ case 4: // Set wildcard directories and check for changes
+ fw.UpdateWatchState(files, map[string]bool{"/src": true})
+ fw.HasChangesFromWatchState()
+ case 5: // Modify PollInterval
+ fw.SetPollInterval(time.Duration(i*10) * time.Millisecond)
+ }
+ }
+ })
+}
+
+// FuzzFileWatcherConcurrent is a fuzz test that runs random operations
+// from multiple goroutines to find concurrency bugs.
+func FuzzFileWatcherConcurrent(f *testing.F) {
+ f.Add([]byte{0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5})
+ f.Add([]byte{0, 0, 0, 3, 3, 3, 2, 2, 2, 1, 1, 1})
+ f.Add([]byte{2, 3, 2, 3, 2, 3, 0, 0, 0, 0, 0, 0})
+
+ f.Fuzz(func(t *testing.T, ops []byte) {
+ if len(ops) < 4 {
+ return
+ }
+
+ fs := newTestFS()
+ fw := newWatcherWithState(fs)
+ fw.UpdateWatchState(defaultPaths, map[string]bool{"/src": true})
+
+ files := []string{"/src/a.ts", "/src/b.ts", "/src/c.ts", "/src/new.ts"}
+
+ // Split ops into chunks for different goroutines
+ chunkSize := len(ops) / 2
+ if chunkSize == 0 {
+ chunkSize = 1
+ }
+
+ var wg sync.WaitGroup
+
+ for start := 0; start < len(ops); start += chunkSize {
+ end := min(start+chunkSize, len(ops))
+ chunk := ops[start:end]
+
+ wg.Add(1)
+ go func(chunk []byte, goroutineID int) {
+ defer wg.Done()
+ for i, op := range chunk {
+ path := files[(goroutineID*len(chunk)+i)%len(files)]
+ switch op % 4 {
+ case 0:
+ _ = fs.WriteFile(path, fmt.Sprintf("const g%d = %d;", goroutineID, i))
+ case 1:
+ _ = fs.Remove(path)
+ case 2:
+ fw.HasChangesFromWatchState()
+ case 3:
+ fw.UpdateWatchState([]string{path}, map[string]bool{"/src": true})
+ }
+ }
+ }(chunk, start/chunkSize)
+ }
+
+ wg.Wait()
+ })
+}
diff --git a/testdata/baselines/reference/tscWatch/commandLine/Parse-watch-interval-option.js b/testdata/baselines/reference/tscWatch/commandLine/Parse-watch-interval-option.js
index 4542c5c6a19..c4931ed7907 100644
--- a/testdata/baselines/reference/tscWatch/commandLine/Parse-watch-interval-option.js
+++ b/testdata/baselines/reference/tscWatch/commandLine/Parse-watch-interval-option.js
@@ -14,8 +14,10 @@ export const a = 1
tsgo -w --watchInterval 1000
ExitStatus:: Success
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
///
interface Boolean {}
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-at-types-package-installed-later.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-at-types-package-installed-later.js
new file mode 100644
index 00000000000..01adb0dc40d
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-at-types-package-installed-later.js
@@ -0,0 +1,82 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+import * as lib from "untyped-lib";
+//// [/home/src/workspaces/project/node_modules/untyped-lib/index.js] *new*
+module.exports = {};
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[96mindex.ts[0m:[93m1[0m:[93m22[0m - [91merror[0m[90m TS7016: [0mCould not find a declaration file for module 'untyped-lib'. '/home/src/workspaces/project/node_modules/untyped-lib/index.js' implicitly has an 'any' type.
+
+[7m1[0m import * as lib from "untyped-lib";
+[7m [0m [91m ~~~~~~~~~~~~~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: install @types for the library
+//// [/home/src/workspaces/project/node_modules/@types/untyped-lib/index.d.ts] *new*
+declare module "untyped-lib" { export const value: string; }
+//// [/home/src/workspaces/project/node_modules/@types/untyped-lib/package.json] *new*
+{"name": "@types/untyped-lib", "types": "index.d.ts"}
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/node_modules/@types/untyped-lib/index.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(used version) /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+(used version) /home/src/workspaces/project/node_modules/@types/untyped-lib/index.d.ts
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-change-in-symlinked-file.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-change-in-symlinked-file.js
new file mode 100644
index 00000000000..5aa1b5ad18e
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-change-in-symlinked-file.js
@@ -0,0 +1,78 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+import { shared } from "./link";
+//// [/home/src/workspaces/project/link.ts] -> /home/src/workspaces/shared/index.ts *new*
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+//// [/home/src/workspaces/shared/index.ts] *new*
+export const shared = "v1";
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+//// [/home/src/workspaces/project/link.js] *new*
+export const shared = "v1";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/link.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: modify symlink target
+//// [/home/src/workspaces/shared/index.ts] *modified*
+export const shared = "v2";
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+//// [/home/src/workspaces/project/link.js] *modified*
+export const shared = "v2";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/link.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/link.ts
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-multiple-new-subdirectories-simultaneously.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-multiple-new-subdirectories-simultaneously.js
new file mode 100644
index 00000000000..6d26fe6cc46
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-multiple-new-subdirectories-simultaneously.js
@@ -0,0 +1,86 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/src/a.ts] *new*
+export const a = 1;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{
+ "compilerOptions": {},
+ "include": ["src/**/*.ts"]
+}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/src/a.js] *new*
+export const a = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/src/a.ts
+Signatures::
+
+
+Edit [0]:: create multiple new subdirs with files
+//// [/home/src/workspaces/project/src/models/user.ts] *new*
+export interface User { name: string; }
+//// [/home/src/workspaces/project/src/utils/format.ts] *new*
+export function format(s: string): string { return s.trim(); }
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[96msrc/utils/format.ts[0m:[93m1[0m:[93m54[0m - [91merror[0m[90m TS2339: [0mProperty 'trim' does not exist on type 'string'.
+
+[7m1[0m export function format(s: string): string { return s.trim(); }
+[7m [0m [91m ~~~~[0m
+
+
+Found 1 error in src/utils/format.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/workspaces/project/src/models/user.js] *new*
+export {};
+
+//// [/home/src/workspaces/project/src/utils/format.js] *new*
+export function format(s) { return s.trim(); }
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/src/models/user.ts
+*refresh* /home/src/workspaces/project/src/utils/format.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/src/models/user.ts
+(computed .d.ts) /home/src/workspaces/project/src/utils/format.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-new-nested-subdirectory.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-new-nested-subdirectory.js
new file mode 100644
index 00000000000..71da18e80e1
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-new-nested-subdirectory.js
@@ -0,0 +1,71 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/src/a.ts] *new*
+export const a = 1;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{
+ "compilerOptions": {},
+ "include": ["src/**/*.ts"]
+}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/src/a.js] *new*
+export const a = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/src/a.ts
+Signatures::
+
+
+Edit [0]:: create nested dir with ts file
+//// [/home/src/workspaces/project/src/deep/nested/util.ts] *new*
+export const util = "nested";
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/src/deep/nested/util.js] *new*
+export const util = "nested";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/src/deep/nested/util.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/src/deep/nested/util.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-to-previously-non-existent-include-path.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-to-previously-non-existent-include-path.js
new file mode 100644
index 00000000000..9130ae0e4bf
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-to-previously-non-existent-include-path.js
@@ -0,0 +1,72 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+const x = 1;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{
+ "compilerOptions": {},
+ "include": ["index.ts", "src/**/*.ts"]
+}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+"use strict";
+const x = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: create src dir with ts file matching include
+//// [/home/src/workspaces/project/src/helper.ts] *new*
+export const helper = "added";
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/src/helper.js] *new*
+export const helper = "added";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/src/helper.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/src/helper.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-deleted-and-new-file-added-simultaneously.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-deleted-and-new-file-added-simultaneously.js
new file mode 100644
index 00000000000..b9f2d5c8481
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-deleted-and-new-file-added-simultaneously.js
@@ -0,0 +1,80 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/a.ts] *new*
+import { b } from "./b";
+//// [/home/src/workspaces/project/b.ts] *new*
+export const b = 1;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/a.js] *new*
+export {};
+
+//// [/home/src/workspaces/project/b.js] *new*
+export const b = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/b.ts
+*refresh* /home/src/workspaces/project/a.ts
+Signatures::
+
+
+Edit [0]:: delete b.ts and create c.ts with updated import
+//// [/home/src/workspaces/project/a.ts] *modified*
+import { c } from "./c";
+//// [/home/src/workspaces/project/b.ts] *deleted*
+//// [/home/src/workspaces/project/c.ts] *new*
+export const c = 2;
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/a.js] *rewrite with same content*
+//// [/home/src/workspaces/project/c.js] *new*
+export const c = 2;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/c.ts
+*refresh* /home/src/workspaces/project/a.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/c.ts
+(computed .d.ts) /home/src/workspaces/project/a.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-renamed-and-renamed-back.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-renamed-and-renamed-back.js
new file mode 100644
index 00000000000..0496d95a210
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-renamed-and-renamed-back.js
@@ -0,0 +1,119 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/helper.ts] *new*
+export const helper = 1;
+//// [/home/src/workspaces/project/index.ts] *new*
+import { helper } from "./helper";
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/helper.js] *new*
+export const helper = 1;
+
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/helper.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: rename helper to helper2
+//// [/home/src/workspaces/project/helper.ts] *deleted*
+//// [/home/src/workspaces/project/helper2.ts] *new*
+export const helper = 1;
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[96mindex.ts[0m:[93m1[0m:[93m24[0m - [91merror[0m[90m TS7016: [0mCould not find a declaration file for module './helper'. '/home/src/workspaces/project/helper.js' implicitly has an 'any' type.
+
+[7m1[0m import { helper } from "./helper";
+[7m [0m [91m ~~~~~~~~~~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/workspaces/project/helper2.js] *new*
+export const helper = 1;
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/helper2.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/helper2.ts
+(computed .d.ts) /home/src/workspaces/project/index.ts
+
+
+Diff:: incremental resolves to .js output from prior build while clean build cannot find module
+--- nonIncremental.output.txt
++++ incremental.output.txt
+@@ -1,4 +1,4 @@
+-[96mindex.ts[0m:[93m1[0m:[93m24[0m - [91merror[0m[90m TS2307: [0mCannot find module './helper' or its corresponding type declarations.
++[96mindex.ts[0m:[93m1[0m:[93m24[0m - [91merror[0m[90m TS7016: [0mCould not find a declaration file for module './helper'. '/home/src/workspaces/project/helper.js' implicitly has an 'any' type.
+
+ [7m1[0m import { helper } from "./helper";
+ [7m [0m [91m ~~~~~~~~~~[0m
+
+Edit [1]:: rename back to helper
+//// [/home/src/workspaces/project/helper.ts] *new*
+export const helper = 1;
+//// [/home/src/workspaces/project/helper2.ts] *deleted*
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/helper.js] *rewrite with same content*
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/helper.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/helper.ts
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-import-path-restructured.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-import-path-restructured.js
new file mode 100644
index 00000000000..3211131af98
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-import-path-restructured.js
@@ -0,0 +1,80 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+import { util } from "./lib/util";
+//// [/home/src/workspaces/project/lib/util.ts] *new*
+export const util = "v1";
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+//// [/home/src/workspaces/project/lib/util.js] *new*
+export const util = "v1";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/lib/util.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: move file to new path and update import
+//// [/home/src/workspaces/project/index.ts] *modified*
+import { util } from "./src/util";
+//// [/home/src/workspaces/project/lib/util.ts] *deleted*
+//// [/home/src/workspaces/project/src/util.ts] *new*
+export const util = "v2";
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+//// [/home/src/workspaces/project/src/util.js] *new*
+export const util = "v2";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/src/util.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/src/util.ts
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-directory-removed.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-directory-removed.js
new file mode 100644
index 00000000000..5c7c582f53b
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-directory-removed.js
@@ -0,0 +1,90 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+import { util } from "./lib/util";
+//// [/home/src/workspaces/project/lib/util.ts] *new*
+export const util = "hello";
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+//// [/home/src/workspaces/project/lib/util.js] *new*
+export const util = "hello";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/lib/util.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: remove directory with imported file
+//// [/home/src/workspaces/project/lib/util.ts] *deleted*
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[96mindex.ts[0m:[93m1[0m:[93m22[0m - [91merror[0m[90m TS7016: [0mCould not find a declaration file for module './lib/util'. '/home/src/workspaces/project/lib/util.js' implicitly has an 'any' type.
+
+[7m1[0m import { util } from "./lib/util";
+[7m [0m [91m ~~~~~~~~~~~~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/index.ts
+
+
+Diff:: incremental resolves to .js output from prior build (TS7016) while clean build cannot find module at all (TS2307)
+--- nonIncremental.output.txt
++++ incremental.output.txt
+@@ -1,4 +1,4 @@
+-[96mindex.ts[0m:[93m1[0m:[93m22[0m - [91merror[0m[90m TS2307: [0mCannot find module './lib/util' or its corresponding type declarations.
++[96mindex.ts[0m:[93m1[0m:[93m22[0m - [91merror[0m[90m TS7016: [0mCould not find a declaration file for module './lib/util'. '/home/src/workspaces/project/lib/util.js' implicitly has an 'any' type.
+
+ [7m1[0m import { util } from "./lib/util";
+ [7m [0m [91m ~~~~~~~~~~~~[0m
\ No newline at end of file
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-file-added-in-new-directory.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-file-added-in-new-directory.js
new file mode 100644
index 00000000000..e003eba9749
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-file-added-in-new-directory.js
@@ -0,0 +1,79 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+import { util } from "./lib/util";
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[96mindex.ts[0m:[93m1[0m:[93m22[0m - [91merror[0m[90m TS2307: [0mCannot find module './lib/util' or its corresponding type declarations.
+
+[7m1[0m import { util } from "./lib/util";
+[7m [0m [91m ~~~~~~~~~~~~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: create directory and imported file
+//// [/home/src/workspaces/project/lib/util.ts] *new*
+export const util = "hello";
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+//// [/home/src/workspaces/project/lib/util.js] *new*
+export const util = "hello";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/lib/util.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/lib/util.ts
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-module-going-missing-then-coming-back.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-module-going-missing-then-coming-back.js
new file mode 100644
index 00000000000..5e3ddd32d8e
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-module-going-missing-then-coming-back.js
@@ -0,0 +1,113 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+import { util } from "./util";
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+//// [/home/src/workspaces/project/util.ts] *new*
+export const util = "v1";
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+//// [/home/src/workspaces/project/util.js] *new*
+export const util = "v1";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/util.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: delete util module
+//// [/home/src/workspaces/project/util.ts] *deleted*
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[96mindex.ts[0m:[93m1[0m:[93m22[0m - [91merror[0m[90m TS7016: [0mCould not find a declaration file for module './util'. '/home/src/workspaces/project/util.js' implicitly has an 'any' type.
+
+[7m1[0m import { util } from "./util";
+[7m [0m [91m ~~~~~~~~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/index.ts
+
+
+Diff:: incremental resolves to .js output from prior build while clean build cannot find module
+--- nonIncremental.output.txt
++++ incremental.output.txt
+@@ -1,4 +1,4 @@
+-[96mindex.ts[0m:[93m1[0m:[93m22[0m - [91merror[0m[90m TS2307: [0mCannot find module './util' or its corresponding type declarations.
++[96mindex.ts[0m:[93m1[0m:[93m22[0m - [91merror[0m[90m TS7016: [0mCould not find a declaration file for module './util'. '/home/src/workspaces/project/util.js' implicitly has an 'any' type.
+
+ [7m1[0m import { util } from "./util";
+ [7m [0m [91m ~~~~~~~~[0m
+
+Edit [1]:: recreate util module with new content
+//// [/home/src/workspaces/project/util.ts] *new*
+export const util = "v2";
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+//// [/home/src/workspaces/project/util.js] *modified*
+export const util = "v2";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/util.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/util.ts
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-nested-subdirectory-removed-and-recreated.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-nested-subdirectory-removed-and-recreated.js
new file mode 100644
index 00000000000..8627df293ba
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-nested-subdirectory-removed-and-recreated.js
@@ -0,0 +1,97 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/src/lib/helper.ts] *new*
+export const helper = "v1";
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{
+ "compilerOptions": {},
+ "include": ["src/**/*.ts"]
+}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/src/lib/helper.js] *new*
+export const helper = "v1";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/src/lib/helper.ts
+Signatures::
+
+
+Edit [0]:: remove nested dir
+//// [/home/src/workspaces/project/src/lib/helper.ts] *deleted*
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+
+tsconfig.json::
+SemanticDiagnostics::
+Signatures::
+
+
+Diff:: incremental has prior state and does not report no-inputs error
+--- nonIncremental.output.txt
++++ incremental.output.txt
+@@ -1,4 +0,0 @@
+-[91merror[0m[90m TS18003: [0mNo inputs were found in config file '/home/src/workspaces/project/tsconfig.json'. Specified 'include' paths were '["src/**/*.ts"]' and 'exclude' paths were '[]'.
+-
+-Found 1 error.
+-
+
+Edit [1]:: recreate nested dir with new content
+//// [/home/src/workspaces/project/src/lib/helper.ts] *new*
+export const helper = "v2";
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/src/lib/helper.js] *modified*
+export const helper = "v2";
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/src/lib/helper.ts
+Signatures::
+(used version) /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+(computed .d.ts) /home/src/workspaces/project/src/lib/helper.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-in-existing-include-directory.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-in-existing-include-directory.js
new file mode 100644
index 00000000000..fb174d116d7
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-in-existing-include-directory.js
@@ -0,0 +1,71 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/src/a.ts] *new*
+export const a = 1;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{
+ "compilerOptions": {},
+ "include": ["src/**/*.ts"]
+}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/src/a.js] *new*
+export const a = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/src/a.ts
+Signatures::
+
+
+Edit [0]:: add new file to existing src directory
+//// [/home/src/workspaces/project/src/b.ts] *new*
+export const b = 2;
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/src/b.js] *new*
+export const b = 2;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/src/b.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/src/b.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-resolving-failed-import.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-resolving-failed-import.js
new file mode 100644
index 00000000000..c5115bfe0f5
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-resolving-failed-import.js
@@ -0,0 +1,79 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/a.ts] *new*
+import { b } from "./b";
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[96ma.ts[0m:[93m1[0m:[93m19[0m - [91merror[0m[90m TS2307: [0mCannot find module './b' or its corresponding type declarations.
+
+[7m1[0m import { b } from "./b";
+[7m [0m [91m ~~~~~[0m
+
+
+Found 1 error in a.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/a.js] *new*
+export {};
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/a.ts
+Signatures::
+
+
+Edit [0]:: create missing file
+//// [/home/src/workspaces/project/b.ts] *new*
+export const b = 1;
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/a.js] *rewrite with same content*
+//// [/home/src/workspaces/project/b.js] *new*
+export const b = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/b.ts
+*refresh* /home/src/workspaces/project/a.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/b.ts
+(computed .d.ts) /home/src/workspaces/project/a.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-added.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-added.js
new file mode 100644
index 00000000000..2c08f665024
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-added.js
@@ -0,0 +1,80 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+import { lib } from "mylib";
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[96mindex.ts[0m:[93m1[0m:[93m21[0m - [91merror[0m[90m TS2307: [0mCannot find module 'mylib' or its corresponding type declarations.
+
+[7m1[0m import { lib } from "mylib";
+[7m [0m [91m ~~~~~~~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: install package in node_modules
+//// [/home/src/workspaces/project/node_modules/mylib/index.d.ts] *new*
+export declare const lib: string;
+//// [/home/src/workspaces/project/node_modules/mylib/index.js] *new*
+exports.lib = "hello";
+//// [/home/src/workspaces/project/node_modules/mylib/package.json] *new*
+{"name": "mylib", "main": "index.js", "types": "index.d.ts"}
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/node_modules/mylib/index.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(used version) /home/src/workspaces/project/node_modules/mylib/index.d.ts
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-removed.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-removed.js
new file mode 100644
index 00000000000..744b4cfbcf5
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-removed.js
@@ -0,0 +1,82 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+import { lib } from "mylib";
+//// [/home/src/workspaces/project/node_modules/mylib/index.d.ts] *new*
+export declare const lib: string;
+//// [/home/src/workspaces/project/node_modules/mylib/index.js] *new*
+exports.lib = "hello";
+//// [/home/src/workspaces/project/node_modules/mylib/package.json] *new*
+{"name": "mylib", "main": "index.js", "types": "index.d.ts"}
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/node_modules/mylib/index.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: remove node_modules package
+//// [/home/src/workspaces/project/node_modules/mylib/index.d.ts] *deleted*
+//// [/home/src/workspaces/project/node_modules/mylib/index.js] *deleted*
+//// [/home/src/workspaces/project/node_modules/mylib/package.json] *deleted*
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[96mindex.ts[0m:[93m1[0m:[93m21[0m - [91merror[0m[90m TS2307: [0mCannot find module 'mylib' or its corresponding type declarations.
+
+[7m1[0m import { lib } from "mylib";
+[7m [0m [91m ~~~~~~~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-package-json-types-field-edited.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-package-json-types-field-edited.js
new file mode 100644
index 00000000000..9f8e5d02a37
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-package-json-types-field-edited.js
@@ -0,0 +1,75 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+import { lib } from "mylib";
+//// [/home/src/workspaces/project/node_modules/mylib/new.d.ts] *new*
+export declare const lib: string;
+//// [/home/src/workspaces/project/node_modules/mylib/old.d.ts] *new*
+export declare const lib: number;
+//// [/home/src/workspaces/project/node_modules/mylib/package.json] *new*
+{"name": "mylib", "types": "old.d.ts"}
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/node_modules/mylib/old.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: change package.json types field
+//// [/home/src/workspaces/project/node_modules/mylib/package.json] *modified*
+{"name": "mylib", "types": "new.d.ts"}
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/node_modules/mylib/new.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(used version) /home/src/workspaces/project/node_modules/mylib/new.d.ts
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-scoped-package-installed.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-scoped-package-installed.js
new file mode 100644
index 00000000000..9f17d930f1a
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-scoped-package-installed.js
@@ -0,0 +1,78 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+import { lib } from "@scope/mylib";
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[96mindex.ts[0m:[93m1[0m:[93m21[0m - [91merror[0m[90m TS2307: [0mCannot find module '@scope/mylib' or its corresponding type declarations.
+
+[7m1[0m import { lib } from "@scope/mylib";
+[7m [0m [91m ~~~~~~~~~~~~~~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: install scoped package
+//// [/home/src/workspaces/project/node_modules/@scope/mylib/index.d.ts] *new*
+export declare const lib: string;
+//// [/home/src/workspaces/project/node_modules/@scope/mylib/package.json] *new*
+{"name": "@scope/mylib", "types": "index.d.ts"}
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/node_modules/@scope/mylib/index.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(used version) /home/src/workspaces/project/node_modules/@scope/mylib/index.d.ts
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-file-rapidly-recreated.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-file-rapidly-recreated.js
new file mode 100644
index 00000000000..3cf8b77fd36
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-file-rapidly-recreated.js
@@ -0,0 +1,77 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/data.ts] *new*
+export const val = "original";
+//// [/home/src/workspaces/project/index.ts] *new*
+import { val } from "./data";
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/data.js] *new*
+export const val = "original";
+
+//// [/home/src/workspaces/project/index.js] *new*
+export {};
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/data.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: delete and immediately recreate with new content
+//// [/home/src/workspaces/project/data.ts] *modified*
+export const val = "recreated";
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/data.js] *modified*
+export const val = "recreated";
+
+//// [/home/src/workspaces/project/index.js] *rewrite with same content*
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/data.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/data.ts
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-deleted.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-deleted.js
new file mode 100644
index 00000000000..1d1ac5045a6
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-deleted.js
@@ -0,0 +1,210 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+const x = 1;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+"use strict";
+const x = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: delete tsconfig
+//// [/home/src/workspaces/project/tsconfig.json] *deleted*
+
+
+Output::
+[91merror[0m[90m TS5083: [0mCannot read file '/home/src/workspaces/project/tsconfig.json'.
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+
+
+
+Diff:: incremental reports config read error while clean build without tsconfig prints usage help
+--- nonIncremental.output.txt
++++ incremental.output.txt
+@@ -1,144 +1,1 @@
+-Version FakeTSVersion
+-tsc: The TypeScript Compiler - Version FakeTSVersion
+-
+-[1mCOMMON COMMANDS[22m
+-
+- [94mtsc[39m
+- Compiles the current project (tsconfig.json in the working directory.)
+-
+- [94mtsc app.ts util.ts[39m
+- Ignoring tsconfig.json, compiles the specified files with default compiler options.
+-
+- [94mtsc -b[39m
+- Build a composite project in the working directory.
+-
+- [94mtsc --init[39m
+- Creates a tsconfig.json with the recommended settings in the working directory.
+-
+- [94mtsc -p ./path/to/tsconfig.json[39m
+- Compiles the TypeScript project located at the specified path.
+-
+- [94mtsc --help --all[39m
+- An expanded version of this information, showing all possible compiler options
+-
+- [94mtsc --noEmit[39m
+- [94mtsc --target esnext[39m
+- Compiles the current project, with additional settings.
+-
+-[1mCOMMAND LINE FLAGS[22m
+-
+-[94m--help, -h[39m
+-Print this message.
+-
+-[94m--watch, -w[39m
+-Watch input files.
+-
+-[94m--all[39m
+-Show all compiler options.
+-
+-[94m--version, -v[39m
+-Print the compiler's version.
+-
+-[94m--init[39m
+-Initializes a TypeScript project and creates a tsconfig.json file.
+-
+-[94m--project, -p[39m
+-Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.
+-
+-[94m--showConfig[39m
+-Print the final configuration instead of building.
+-
+-[94m--ignoreConfig[39m
+-Ignore the tsconfig found and build with commandline options and files.
+-
+-[94m--build, -b[39m
+-Build one or more projects and their dependencies, if out of date
+-
+-[1mCOMMON COMPILER OPTIONS[22m
+-
+-[94m--pretty[39m
+-Enable color and formatting in TypeScript's output to make compiler errors easier to read.
+-type: boolean
+-default: true
+-
+-[94m--declaration, -d[39m
+-Generate .d.ts files from TypeScript and JavaScript files in your project.
+-type: boolean
+-default: `false`, unless `composite` is set
+-
+-[94m--declarationMap[39m
+-Create sourcemaps for d.ts files.
+-type: boolean
+-default: false
+-
+-[94m--emitDeclarationOnly[39m
+-Only output d.ts files and not JavaScript files.
+-type: boolean
+-default: false
+-
+-[94m--sourceMap[39m
+-Create source map files for emitted JavaScript files.
+-type: boolean
+-default: false
+-
+-[94m--noEmit[39m
+-Disable emitting files from a compilation.
+-type: boolean
+-default: false
+-
+-[94m--target, -t[39m
+-Set the JavaScript language version for emitted JavaScript and include compatible library declarations.
+-one of: es6/es2015, es2016, es2017, es2018, es2019, es2020, es2021, es2022, es2023, es2024, es2025, esnext
+-default: es2025
+-
+-[94m--module, -m[39m
+-Specify what module code is generated.
+-one of: commonjs, es6/es2015, es2020, es2022, esnext, node16, node18, node20, nodenext, preserve
+-default: undefined
+-
+-[94m--lib[39m
+-Specify a set of bundled library declaration files that describe the target runtime environment.
+-one or more: es5, es6/es2015, es7/es2016, es2017, es2018, es2019, es2020, es2021, es2022, es2023, es2024, es2025, esnext, dom, dom.iterable, dom.asynciterable, webworker, webworker.importscripts, webworker.iterable, webworker.asynciterable, scripthost, es2015.core, es2015.collection, es2015.generator, es2015.iterable, es2015.promise, es2015.proxy, es2015.reflect, es2015.symbol, es2015.symbol.wellknown, es2016.array.include, es2016.intl, es2017.arraybuffer, es2017.date, es2017.object, es2017.sharedmemory, es2017.string, es2017.intl, es2017.typedarrays, es2018.asyncgenerator, es2018.asynciterable/esnext.asynciterable, es2018.intl, es2018.promise, es2018.regexp, es2019.array, es2019.object, es2019.string, es2019.symbol/esnext.symbol, es2019.intl, es2020.bigint/esnext.bigint, es2020.date, es2020.promise, es2020.sharedmemory, es2020.string, es2020.symbol.wellknown, es2020.intl, es2020.number, es2021.promise, es2021.string, es2021.weakref/esnext.weakref, es2021.intl, es2022.array, es2022.error, es2022.intl, es2022.object, es2022.string, es2022.regexp, es2023.array, es2023.collection, es2023.intl, es2024.arraybuffer, es2024.collection, es2024.object/esnext.object, es2024.promise, es2024.regexp/esnext.regexp, es2024.sharedmemory, es2024.string/esnext.string, es2025.collection, es2025.float16/esnext.float16, es2025.intl, es2025.iterator/esnext.iterator, es2025.promise/esnext.promise, es2025.regexp, esnext.array, esnext.collection, esnext.date, esnext.decorators, esnext.disposable, esnext.error, esnext.intl, esnext.sharedmemory, esnext.temporal, esnext.typedarrays, decorators, decorators.legacy
+-default: undefined
+-
+-[94m--allowJs[39m
+-Allow JavaScript files to be a part of your program. Use the 'checkJs' option to get errors from these files.
+-type: boolean
+-default: `false`, unless `checkJs` is set
+-
+-[94m--checkJs[39m
+-Enable error reporting in type-checked JavaScript files.
+-type: boolean
+-default: false
+-
+-[94m--jsx[39m
+-Specify what JSX code is generated.
+-one of: preserve, react-native, react-jsx, react-jsxdev, react
+-default: undefined
+-
+-[94m--outFile[39m
+-Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output.
+-
+-[94m--outDir[39m
+-Specify an output folder for all emitted files.
+-
+-[94m--removeComments[39m
+-Disable emitting comments.
+-type: boolean
+-default: false
+-
+-[94m--strict[39m
+-Enable all strict type-checking options.
+-type: boolean
+-default: true
+-
+-[94m--types[39m
+-Specify type package names to be included without being referenced in a source file.
+-
+-[94m--esModuleInterop[39m
+-Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility.
+-type: boolean
+-default: true
+-
+-You can learn about all of the compiler options at https://aka.ms/tsc
+-
++[91merror[0m[90m TS5083: [0mCannot read file '/home/src/workspaces/project/tsconfig.json'.
\ No newline at end of file
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-with-extends-base-modified.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-with-extends-base-modified.js
new file mode 100644
index 00000000000..4abbf691989
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-with-extends-base-modified.js
@@ -0,0 +1,83 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/base.json] *new*
+{
+ "compilerOptions": { "strict": false }
+}
+//// [/home/src/workspaces/project/index.ts] *new*
+const x = null; const y: string = x;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{
+ "extends": "./base.json"
+}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+"use strict";
+const x = null;
+const y = x;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: modify base config to enable strict
+//// [/home/src/workspaces/project/base.json] *modified*
+{
+ "compilerOptions": { "strict": true }
+}
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[96mindex.ts[0m:[93m1[0m:[93m23[0m - [91merror[0m[90m TS2322: [0mType 'null' is not assignable to type 'string'.
+
+[7m1[0m const x = null; const y: string = x;
+[7m [0m [91m ~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-file-is-modified.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-file-is-modified.js
new file mode 100644
index 00000000000..ad0d4250854
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-file-is-modified.js
@@ -0,0 +1,71 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+const x: number = 1;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+"use strict";
+const x = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: modify file
+//// [/home/src/workspaces/project/index.ts] *modified*
+const x: number = 2;
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/index.js] *modified*
+"use strict";
+const x = 2;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/index.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-source-file-is-deleted.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-source-file-is-deleted.js
new file mode 100644
index 00000000000..9f72fb77745
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-source-file-is-deleted.js
@@ -0,0 +1,90 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/a.ts] *new*
+import { b } from "./b";
+//// [/home/src/workspaces/project/b.ts] *new*
+export const b = 1;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/a.js] *new*
+export {};
+
+//// [/home/src/workspaces/project/b.js] *new*
+export const b = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/b.ts
+*refresh* /home/src/workspaces/project/a.ts
+Signatures::
+
+
+Edit [0]:: delete imported file
+//// [/home/src/workspaces/project/b.ts] *deleted*
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[96ma.ts[0m:[93m1[0m:[93m19[0m - [91merror[0m[90m TS7016: [0mCould not find a declaration file for module './b'. '/home/src/workspaces/project/b.js' implicitly has an 'any' type.
+
+[7m1[0m import { b } from "./b";
+[7m [0m [91m ~~~~~[0m
+
+
+Found 1 error in a.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/workspaces/project/a.js] *rewrite with same content*
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/a.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/a.ts
+
+
+Diff:: incremental resolves to .js output from prior build (TS7016) while clean build cannot find module at all (TS2307)
+--- nonIncremental.output.txt
++++ incremental.output.txt
+@@ -1,4 +1,4 @@
+-[96ma.ts[0m:[93m1[0m:[93m19[0m - [91merror[0m[90m TS2307: [0mCannot find module './b' or its corresponding type declarations.
++[96ma.ts[0m:[93m1[0m:[93m19[0m - [91merror[0m[90m TS7016: [0mCould not find a declaration file for module './b'. '/home/src/workspaces/project/b.js' implicitly has an 'any' type.
+
+ [7m1[0m import { b } from "./b";
+ [7m [0m [91m ~~~~~[0m
\ No newline at end of file
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-include-pattern-adds-file.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-include-pattern-adds-file.js
new file mode 100644
index 00000000000..959ae56259e
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-include-pattern-adds-file.js
@@ -0,0 +1,77 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+const x = 1;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{
+ "compilerOptions": {},
+ "include": ["*.ts"]
+}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+"use strict";
+const x = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: widen include pattern to add src dir
+//// [/home/src/workspaces/project/src/extra.ts] *new*
+export const extra = 2;
+//// [/home/src/workspaces/project/tsconfig.json] *modified*
+{
+ "compilerOptions": {},
+ "include": ["*.ts", "src/**/*.ts"]
+}
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/workspaces/project/src/extra.js] *new*
+export const extra = 2;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/workspaces/project/src/extra.ts
+Signatures::
+(computed .d.ts) /home/src/workspaces/project/src/extra.ts
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-modified-to-change-strict.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-modified-to-change-strict.js
new file mode 100644
index 00000000000..0e5e0cce9ae
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-modified-to-change-strict.js
@@ -0,0 +1,81 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+const x = null; const y: string = x;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[96mindex.ts[0m:[93m1[0m:[93m23[0m - [91merror[0m[90m TS2322: [0mType 'null' is not assignable to type 'string'.
+
+[7m1[0m const x = null; const y: string = x;
+[7m [0m [91m ~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+"use strict";
+const x = null;
+const y = x;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: enable strict mode
+//// [/home/src/workspaces/project/tsconfig.json] *modified*
+{"compilerOptions": {"strict": true}}
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[96mindex.ts[0m:[93m1[0m:[93m23[0m - [91merror[0m[90m TS2322: [0mType 'null' is not assignable to type 'string'.
+
+[7m1[0m const x = null; const y: string = x;
+[7m [0m [91m ~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
+
+tsconfig.json::
+SemanticDiagnostics::
+Signatures::
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-touched-but-content-unchanged.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-touched-but-content-unchanged.js
new file mode 100644
index 00000000000..a9a3639536a
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-touched-but-content-unchanged.js
@@ -0,0 +1,63 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+const x = 1;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+"use strict";
+const x = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: touch tsconfig without changing content
+//// [/home/src/workspaces/project/tsconfig.json] *mTime changed*
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+
+tsconfig.json::
+SemanticDiagnostics::
+Signatures::
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-skips-build-when-no-files-change.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-skips-build-when-no-files-change.js
new file mode 100644
index 00000000000..d79f8a92954
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-skips-build-when-no-files-change.js
@@ -0,0 +1,60 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+const x: number = 1;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/index.js] *new*
+"use strict";
+const x = 1;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
+
+
+Edit [0]:: no change
+
+
+Output::
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/index.ts
+Signatures::
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-no-tsconfig.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-no-tsconfig.js
index 71e272910cb..f85233069c8 100644
--- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-no-tsconfig.js
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-no-tsconfig.js
@@ -7,8 +7,10 @@ Input::
tsgo index.ts --watch
ExitStatus:: Success
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
///
interface Boolean {}
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-and-incremental.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-and-incremental.js
index c32265cec30..1bbfe39551f 100644
--- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-and-incremental.js
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-and-incremental.js
@@ -9,8 +9,10 @@ Input::
tsgo --watch --incremental
ExitStatus:: Success
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
///
interface Boolean {}
diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-files-list-entry-deleted.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-files-list-entry-deleted.js
new file mode 100644
index 00000000000..622e5fb3ffc
--- /dev/null
+++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-files-list-entry-deleted.js
@@ -0,0 +1,71 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/a.ts] *new*
+export const a = 1;
+//// [/home/src/workspaces/project/b.ts] *new*
+export const b = 2;
+//// [/home/src/workspaces/project/tsconfig.json] *new*
+{
+ "compilerOptions": {},
+ "files": ["a.ts", "b.ts"]
+}
+
+tsgo --watch
+ExitStatus:: Success
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+//// [/home/src/workspaces/project/a.js] *new*
+export const a = 1;
+
+//// [/home/src/workspaces/project/b.js] *new*
+export const b = 2;
+
+
+tsconfig.json::
+SemanticDiagnostics::
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/a.ts
+*refresh* /home/src/workspaces/project/b.ts
+Signatures::
+
+
+Edit [0]:: delete file listed in files array
+//// [/home/src/workspaces/project/b.ts] *deleted*
+
+
+Output::
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
+
+tsconfig.json::
+SemanticDiagnostics::
+Signatures::
diff --git a/testdata/baselines/reference/tscWatch/noEmit/dts-errors-without-dts-enabled.js b/testdata/baselines/reference/tscWatch/noEmit/dts-errors-without-dts-enabled.js
index 38d0ffcb6d6..8c7009954f9 100644
--- a/testdata/baselines/reference/tscWatch/noEmit/dts-errors-without-dts-enabled.js
+++ b/testdata/baselines/reference/tscWatch/noEmit/dts-errors-without-dts-enabled.js
@@ -13,8 +13,10 @@ const a = class { private p = 10; };
tsgo -w
ExitStatus:: Success
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
///
interface Boolean {}
@@ -52,8 +54,10 @@ const a = "hello";
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -73,8 +77,10 @@ Edit [1]:: emit after fixing error
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
//// [/home/src/workspaces/project/a.js] *new*
"use strict";
const a = "hello";
@@ -96,8 +102,10 @@ Edit [2]:: no emit run after fixing error
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -110,8 +118,10 @@ const a = class { private p = 10; };
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -131,8 +141,10 @@ Edit [4]:: emit when error
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
//// [/home/src/workspaces/project/a.js] *modified*
"use strict";
const a = class {
@@ -156,8 +168,10 @@ Edit [5]:: no emit run when error
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
diff --git a/testdata/baselines/reference/tscWatch/noEmit/dts-errors.js b/testdata/baselines/reference/tscWatch/noEmit/dts-errors.js
index 18e4be0d7cb..135de179f59 100644
--- a/testdata/baselines/reference/tscWatch/noEmit/dts-errors.js
+++ b/testdata/baselines/reference/tscWatch/noEmit/dts-errors.js
@@ -14,7 +14,8 @@ const a = class { private p = 10; };
tsgo -w
ExitStatus:: Success
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS4094: [0mProperty 'p' of exported anonymous class type may not be private or protected.
[7m1[0m const a = class { private p = 10; };
@@ -27,7 +28,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
///
interface Boolean {}
@@ -65,8 +67,10 @@ const a = "hello";
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -86,8 +90,10 @@ Edit [1]:: emit after fixing error
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
//// [/home/src/workspaces/project/a.d.ts] *new*
declare const a = "hello";
@@ -112,8 +118,10 @@ Edit [2]:: no emit run after fixing error
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -126,7 +134,8 @@ const a = class { private p = 10; };
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS4094: [0mProperty 'p' of exported anonymous class type may not be private or protected.
[7m1[0m const a = class { private p = 10; };
@@ -139,7 +148,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -159,7 +169,8 @@ Edit [4]:: emit when error
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS4094: [0mProperty 'p' of exported anonymous class type may not be private or protected.
[7m1[0m const a = class { private p = 10; };
@@ -172,7 +183,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
//// [/home/src/workspaces/project/a.d.ts] *modified*
declare const a: {
new (): {
@@ -203,7 +215,8 @@ Edit [5]:: no emit run when error
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS4094: [0mProperty 'p' of exported anonymous class type may not be private or protected.
[7m1[0m const a = class { private p = 10; };
@@ -216,7 +229,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
diff --git a/testdata/baselines/reference/tscWatch/noEmit/semantic-errors.js b/testdata/baselines/reference/tscWatch/noEmit/semantic-errors.js
index cf568870a8c..dbee71768b2 100644
--- a/testdata/baselines/reference/tscWatch/noEmit/semantic-errors.js
+++ b/testdata/baselines/reference/tscWatch/noEmit/semantic-errors.js
@@ -13,7 +13,8 @@ const a: number = "hello"
tsgo -w
ExitStatus:: Success
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS2322: [0mType 'string' is not assignable to type 'number'.
[7m1[0m const a: number = "hello"
@@ -22,7 +23,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
///
interface Boolean {}
@@ -60,8 +62,10 @@ const a = "hello";
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -81,8 +85,10 @@ Edit [1]:: emit after fixing error
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
//// [/home/src/workspaces/project/a.js] *new*
"use strict";
const a = "hello";
@@ -104,8 +110,10 @@ Edit [2]:: no emit run after fixing error
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -118,7 +126,8 @@ const a: number = "hello"
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS2322: [0mType 'string' is not assignable to type 'number'.
[7m1[0m const a: number = "hello"
@@ -127,7 +136,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -147,7 +157,8 @@ Edit [4]:: emit when error
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS2322: [0mType 'string' is not assignable to type 'number'.
[7m1[0m const a: number = "hello"
@@ -156,7 +167,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
//// [/home/src/workspaces/project/a.js] *rewrite with same content*
tsconfig.json::
@@ -175,7 +187,8 @@ Edit [5]:: no emit run when error
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS2322: [0mType 'string' is not assignable to type 'number'.
[7m1[0m const a: number = "hello"
@@ -184,7 +197,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
diff --git a/testdata/baselines/reference/tscWatch/noEmit/syntax-errors.js b/testdata/baselines/reference/tscWatch/noEmit/syntax-errors.js
index 8f2b4abde33..b7d623008b5 100644
--- a/testdata/baselines/reference/tscWatch/noEmit/syntax-errors.js
+++ b/testdata/baselines/reference/tscWatch/noEmit/syntax-errors.js
@@ -13,7 +13,8 @@ const a = "hello
tsgo -w
ExitStatus:: Success
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] Starting compilation in watch mode...
+
[96ma.ts[0m:[93m1[0m:[93m17[0m - [91merror[0m[90m TS1002: [0mUnterminated string literal.
[7m1[0m const a = "hello
@@ -22,7 +23,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
///
interface Boolean {}
@@ -60,8 +62,10 @@ const a = "hello";
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -81,8 +85,10 @@ Edit [1]:: emit after fixing error
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
//// [/home/src/workspaces/project/a.js] *new*
"use strict";
const a = "hello";
@@ -104,8 +110,10 @@ Edit [2]:: no emit run after fixing error
Output::
-build starting at HH:MM:SS AM
-build finished in d.ddds
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
+[[90mHH:MM:SS AM[0m] Found 0 errors. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -118,7 +126,8 @@ const a = "hello
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
[96ma.ts[0m:[93m1[0m:[93m17[0m - [91merror[0m[90m TS1002: [0mUnterminated string literal.
[7m1[0m const a = "hello
@@ -127,7 +136,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::
@@ -145,7 +155,8 @@ Edit [4]:: emit when error
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
[96ma.ts[0m:[93m1[0m:[93m17[0m - [91merror[0m[90m TS1002: [0mUnterminated string literal.
[7m1[0m const a = "hello
@@ -154,7 +165,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
//// [/home/src/workspaces/project/a.js] *modified*
"use strict";
const a = "hello;
@@ -178,7 +190,8 @@ Edit [5]:: no emit run when error
Output::
-build starting at HH:MM:SS AM
+[2J[3J[H[[90mHH:MM:SS AM[0m] File change detected. Starting incremental compilation...
+
[96ma.ts[0m:[93m1[0m:[93m17[0m - [91merror[0m[90m TS1002: [0mUnterminated string literal.
[7m1[0m const a = "hello
@@ -187,7 +200,8 @@ build starting at HH:MM:SS AM
Found 1 error in a.ts[90m:1[0m
-build finished in d.ddds
+[[90mHH:MM:SS AM[0m] Found 1 error. Watching for file changes.
+
tsconfig.json::
SemanticDiagnostics::