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 +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +index.ts:1:22 - error TS7016: Could not find a declaration file for module 'untyped-lib'. '/home/src/workspaces/project/node_modules/untyped-lib/index.js' implicitly has an 'any' type. + +1 import * as lib from "untyped-lib"; +   ~~~~~~~~~~~~~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +src/utils/format.ts:1:54 - error TS2339: Property 'trim' does not exist on type 'string'. + +1 export function format(s: string): string { return s.trim(); } +   ~~~~ + + +Found 1 error in src/utils/format.ts:1 + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +index.ts:1:24 - error TS7016: Could not find a declaration file for module './helper'. '/home/src/workspaces/project/helper.js' implicitly has an 'any' type. + +1 import { helper } from "./helper"; +   ~~~~~~~~~~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] 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 @@ +-index.ts:1:24 - error TS2307: Cannot find module './helper' or its corresponding type declarations. ++index.ts:1:24 - error TS7016: Could not find a declaration file for module './helper'. '/home/src/workspaces/project/helper.js' implicitly has an 'any' type. + + 1 import { helper } from "./helper"; +    ~~~~~~~~~~ + +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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +index.ts:1:22 - error TS7016: Could not find a declaration file for module './lib/util'. '/home/src/workspaces/project/lib/util.js' implicitly has an 'any' type. + +1 import { util } from "./lib/util"; +   ~~~~~~~~~~~~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] 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 @@ +-index.ts:1:22 - error TS2307: Cannot find module './lib/util' or its corresponding type declarations. ++index.ts:1:22 - error TS7016: Could not find a declaration file for module './lib/util'. '/home/src/workspaces/project/lib/util.js' implicitly has an 'any' type. + + 1 import { util } from "./lib/util"; +    ~~~~~~~~~~~~ \ 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +index.ts:1:22 - error TS2307: Cannot find module './lib/util' or its corresponding type declarations. + +1 import { util } from "./lib/util"; +   ~~~~~~~~~~~~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +index.ts:1:22 - error TS7016: Could not find a declaration file for module './util'. '/home/src/workspaces/project/util.js' implicitly has an 'any' type. + +1 import { util } from "./util"; +   ~~~~~~~~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] 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 @@ +-index.ts:1:22 - error TS2307: Cannot find module './util' or its corresponding type declarations. ++index.ts:1:22 - error TS7016: Could not find a declaration file for module './util'. '/home/src/workspaces/project/util.js' implicitly has an 'any' type. + + 1 import { util } from "./util"; +    ~~~~~~~~ + +Edit [1]:: recreate util module with new content +//// [/home/src/workspaces/project/util.ts] *new* +export const util = "v2"; + + +Output:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 @@ +-error TS18003: No 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +a.ts:1:19 - error TS2307: Cannot find module './b' or its corresponding type declarations. + +1 import { b } from "./b"; +   ~~~~~ + + +Found 1 error in a.ts:1 + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +index.ts:1:21 - error TS2307: Cannot find module 'mylib' or its corresponding type declarations. + +1 import { lib } from "mylib"; +   ~~~~~~~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +index.ts:1:21 - error TS2307: Cannot find module 'mylib' or its corresponding type declarations. + +1 import { lib } from "mylib"; +   ~~~~~~~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +index.ts:1:21 - error TS2307: Cannot find module '@scope/mylib' or its corresponding type declarations. + +1 import { lib } from "@scope/mylib"; +   ~~~~~~~~~~~~~~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +error TS5083: Cannot read file '/home/src/workspaces/project/tsconfig.json'. +[HH:MM:SS AM] 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 +- +-COMMON COMMANDS +- +- tsc +- Compiles the current project (tsconfig.json in the working directory.) +- +- tsc app.ts util.ts +- Ignoring tsconfig.json, compiles the specified files with default compiler options. +- +- tsc -b +- Build a composite project in the working directory. +- +- tsc --init +- Creates a tsconfig.json with the recommended settings in the working directory. +- +- tsc -p ./path/to/tsconfig.json +- Compiles the TypeScript project located at the specified path. +- +- tsc --help --all +- An expanded version of this information, showing all possible compiler options +- +- tsc --noEmit +- tsc --target esnext +- Compiles the current project, with additional settings. +- +-COMMAND LINE FLAGS +- +---help, -h +-Print this message. +- +---watch, -w +-Watch input files. +- +---all +-Show all compiler options. +- +---version, -v +-Print the compiler's version. +- +---init +-Initializes a TypeScript project and creates a tsconfig.json file. +- +---project, -p +-Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'. +- +---showConfig +-Print the final configuration instead of building. +- +---ignoreConfig +-Ignore the tsconfig found and build with commandline options and files. +- +---build, -b +-Build one or more projects and their dependencies, if out of date +- +-COMMON COMPILER OPTIONS +- +---pretty +-Enable color and formatting in TypeScript's output to make compiler errors easier to read. +-type: boolean +-default: true +- +---declaration, -d +-Generate .d.ts files from TypeScript and JavaScript files in your project. +-type: boolean +-default: `false`, unless `composite` is set +- +---declarationMap +-Create sourcemaps for d.ts files. +-type: boolean +-default: false +- +---emitDeclarationOnly +-Only output d.ts files and not JavaScript files. +-type: boolean +-default: false +- +---sourceMap +-Create source map files for emitted JavaScript files. +-type: boolean +-default: false +- +---noEmit +-Disable emitting files from a compilation. +-type: boolean +-default: false +- +---target, -t +-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 +- +---module, -m +-Specify what module code is generated. +-one of: commonjs, es6/es2015, es2020, es2022, esnext, node16, node18, node20, nodenext, preserve +-default: undefined +- +---lib +-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 +- +---allowJs +-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 +- +---checkJs +-Enable error reporting in type-checked JavaScript files. +-type: boolean +-default: false +- +---jsx +-Specify what JSX code is generated. +-one of: preserve, react-native, react-jsx, react-jsxdev, react +-default: undefined +- +---outFile +-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. +- +---outDir +-Specify an output folder for all emitted files. +- +---removeComments +-Disable emitting comments. +-type: boolean +-default: false +- +---strict +-Enable all strict type-checking options. +-type: boolean +-default: true +- +---types +-Specify type package names to be included without being referenced in a source file. +- +---esModuleInterop +-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 +- ++error TS5083: Cannot 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +index.ts:1:23 - error TS2322: Type 'null' is not assignable to type 'string'. + +1 const x = null; const y: string = x; +   ~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +a.ts:1:19 - error TS7016: Could not find a declaration file for module './b'. '/home/src/workspaces/project/b.js' implicitly has an 'any' type. + +1 import { b } from "./b"; +   ~~~~~ + + +Found 1 error in a.ts:1 + +[HH:MM:SS AM] 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 @@ +-a.ts:1:19 - error TS2307: Cannot find module './b' or its corresponding type declarations. ++a.ts:1:19 - error TS7016: Could not find a declaration file for module './b'. '/home/src/workspaces/project/b.js' implicitly has an 'any' type. + + 1 import { b } from "./b"; +    ~~~~~ \ 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +index.ts:1:23 - error TS2322: Type 'null' is not assignable to type 'string'. + +1 const x = null; const y: string = x; +   ~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +index.ts:1:23 - error TS2322: Type 'null' is not assignable to type 'string'. + +1 const x = null; const y: string = x; +   ~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] Starting compilation in watch mode... + a.ts:1:7 - error TS4094: Property 'p' of exported anonymous class type may not be private or protected. 1 const a = class { private p = 10; }; @@ -27,7 +28,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + a.ts:1:7 - error TS4094: Property 'p' of exported anonymous class type may not be private or protected. 1 const a = class { private p = 10; }; @@ -139,7 +148,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + a.ts:1:7 - error TS4094: Property 'p' of exported anonymous class type may not be private or protected. 1 const a = class { private p = 10; }; @@ -172,7 +183,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + a.ts:1:7 - error TS4094: Property 'p' of exported anonymous class type may not be private or protected. 1 const a = class { private p = 10; }; @@ -216,7 +229,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] 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 +[HH:MM:SS AM] Starting compilation in watch mode... + a.ts:1:7 - error TS2322: Type 'string' is not assignable to type 'number'. 1 const a: number = "hello" @@ -22,7 +23,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + a.ts:1:7 - error TS2322: Type 'string' is not assignable to type 'number'. 1 const a: number = "hello" @@ -127,7 +136,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + a.ts:1:7 - error TS2322: Type 'string' is not assignable to type 'number'. 1 const a: number = "hello" @@ -156,7 +167,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + a.ts:1:7 - error TS2322: Type 'string' is not assignable to type 'number'. 1 const a: number = "hello" @@ -184,7 +197,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] 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 +[HH:MM:SS AM] Starting compilation in watch mode... + a.ts:1:17 - error TS1002: Unterminated string literal. 1 const a = "hello @@ -22,7 +23,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + a.ts:1:17 - error TS1002: Unterminated string literal. 1 const a = "hello @@ -127,7 +136,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + a.ts:1:17 - error TS1002: Unterminated string literal. 1 const a = "hello @@ -154,7 +165,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] 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 +[HH:MM:SS AM] File change detected. Starting incremental compilation... + a.ts:1:17 - error TS1002: Unterminated string literal. 1 const a = "hello @@ -187,7 +200,8 @@ build starting at HH:MM:SS AM Found 1 error in a.ts:1 -build finished in d.ddds +[HH:MM:SS AM] Found 1 error. Watching for file changes. + tsconfig.json:: SemanticDiagnostics::