package project_test import ( "context" "maps" "strings" "testing" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/bundled" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/glob" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/project" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/projecttestutil" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" "gotest.tools/v3/assert" ) func TestSession(t *testing.T) { t.Parallel() if !bundled.Embedded { t.Skip("bundled files are not embedded") } defaultFiles := map[string]any{ "/home/projects/TS/p1/tsconfig.json": `{ "compilerOptions": { "noLib": true, "module": "nodenext", "strict": true }, "include": ["src"] }`, "/home/projects/TS/p1/src/index.ts": `import { x } from "./x";`, "/home/projects/TS/p1/src/x.ts": `export const x = 1;`, "/home/projects/TS/p1/config.ts": `let x = 1, y = 2;`, } t.Run("DidOpenFile", func(t *testing.T) { t.Parallel() t.Run("create configured project", func(t *testing.T) { t.Parallel() session, _ := projecttestutil.Setup(defaultFiles) snapshot, release := session.Snapshot() defer release() assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) snapshot, release = session.Snapshot() defer release() assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1) configuredProject := snapshot.ProjectCollection.ConfiguredProject(tspath.Path("/home/projects/ts/p1/tsconfig.json")) assert.Assert(t, configuredProject != nil) // Get language service to access the program ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program := ls.GetProgram() assert.Assert(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil) assert.Equal(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "export const x = 1;") }) t.Run("create inferred project", func(t *testing.T) { t.Parallel() session, _ := projecttestutil.Setup(defaultFiles) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/config.ts", 1, defaultFiles["/home/projects/TS/p1/config.ts"].(string), lsproto.LanguageKindTypeScript) // Find tsconfig, load, notice config.ts is not included, create inferred project snapshot, release := session.Snapshot() defer release() assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 2) // Should have both configured project (for tsconfig.json) and inferred project configuredProject := snapshot.ProjectCollection.ConfiguredProject(tspath.Path("/home/projects/ts/p1/tsconfig.json")) inferredProject := snapshot.ProjectCollection.InferredProject() assert.Assert(t, configuredProject != nil) assert.Assert(t, inferredProject != nil) }) t.Run("inferred project for in-memory files", func(t *testing.T) { t.Parallel() session, _ := projecttestutil.Setup(defaultFiles) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/config.ts", 1, defaultFiles["/home/projects/TS/p1/config.ts"].(string), lsproto.LanguageKindTypeScript) session.DidOpenFile(context.Background(), "untitled:Untitled-1", 1, "x", lsproto.LanguageKindTypeScript) session.DidOpenFile(context.Background(), "untitled:Untitled-2", 1, "y", lsproto.LanguageKindTypeScript) snapshot, release := session.Snapshot() defer release() assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1) assert.Assert(t, snapshot.ProjectCollection.InferredProject() != nil) }) t.Run("inferred project JS file", func(t *testing.T) { t.Parallel() jsFiles := map[string]any{ "/home/projects/TS/p1/index.js": `import { x } from "./x";`, } session, _ := projecttestutil.Setup(jsFiles) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/index.js", 1, jsFiles["/home/projects/TS/p1/index.js"].(string), lsproto.LanguageKindJavaScript) snapshot, release := session.Snapshot() defer release() assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1) ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/index.js") assert.NilError(t, err) program := ls.GetProgram() assert.Assert(t, program.GetSourceFile("/home/projects/TS/p1/index.js") != nil) }) }) t.Run("DidChangeFile", func(t *testing.T) { t.Parallel() t.Run("update file and program", func(t *testing.T) { t.Parallel() session, _ := projecttestutil.Setup(defaultFiles) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript) lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts") assert.NilError(t, err) programBefore := lsBefore.GetProgram() session.DidChangeFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{ { Partial: ptrTo(lsproto.TextDocumentContentChangePartial{ Range: lsproto.Range{ Start: lsproto.Position{ Line: 0, Character: 17, }, End: lsproto.Position{ Line: 0, Character: 18, }, }, Text: "2", }), }, }) lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts") assert.NilError(t, err) programAfter := lsAfter.GetProgram() // Program should change due to the file content change assert.Check(t, programAfter != programBefore) assert.Equal(t, programAfter.GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "export const x = 2;") }) t.Run("unchanged source files are reused", func(t *testing.T) { t.Parallel() session, _ := projecttestutil.Setup(defaultFiles) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript) lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts") assert.NilError(t, err) programBefore := lsBefore.GetProgram() indexFileBefore := programBefore.GetSourceFile("/home/projects/TS/p1/src/index.ts") session.DidChangeFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{ { Partial: ptrTo(lsproto.TextDocumentContentChangePartial{ Range: lsproto.Range{ Start: lsproto.Position{ Line: 0, Character: 0, }, End: lsproto.Position{ Line: 0, Character: 0, }, }, Text: ";", }), }, }) lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts") assert.NilError(t, err) programAfter := lsAfter.GetProgram() // Unchanged file should be reused assert.Equal(t, programAfter.GetSourceFile("/home/projects/TS/p1/src/index.ts"), indexFileBefore) }) t.Run("change can pull in new files", func(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) files["/home/projects/TS/p1/y.ts"] = `export const y = 2;` session, _ := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) // Verify y.ts is not initially in the program lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) programBefore := lsBefore.GetProgram() assert.Check(t, programBefore.GetSourceFile("/home/projects/TS/p1/y.ts") == nil) session.DidChangeFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{ { Partial: ptrTo(lsproto.TextDocumentContentChangePartial{ Range: lsproto.Range{ Start: lsproto.Position{ Line: 0, Character: 0, }, End: lsproto.Position{ Line: 0, Character: 0, }, }, Text: `import { y } from "../y";\n`, }), }, }) lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) programAfter := lsAfter.GetProgram() // y.ts should now be included in the program assert.Assert(t, programAfter.GetSourceFile("/home/projects/TS/p1/y.ts") != nil) }) t.Run("single-file change followed by config change reloads program", func(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) files["/home/projects/TS/p1/tsconfig.json"] = `{ "compilerOptions": { "noLib": true, "module": "nodenext", "strict": true }, "include": ["src/index.ts"] }` session, utils := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) programBefore := lsBefore.GetProgram() assert.Equal(t, len(programBefore.GetSourceFiles()), 2) session.DidChangeFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{ { Partial: ptrTo(lsproto.TextDocumentContentChangePartial{ Range: lsproto.Range{ Start: lsproto.Position{ Line: 0, Character: 0, }, End: lsproto.Position{ Line: 0, Character: 0, }, }, Text: "\n", }), }, }) err = utils.FS().WriteFile("/home/projects/TS/p1/tsconfig.json", `{ "compilerOptions": { "noLib": true, "module": "nodenext", "strict": true }, "include": ["./**/*"] }`, false) assert.NilError(t, err) session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, Uri: "file:///home/projects/TS/p1/tsconfig.json", }, }) lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) programAfter := lsAfter.GetProgram() assert.Equal(t, len(programAfter.GetSourceFiles()), 3) }) }) t.Run("DidCloseFile", func(t *testing.T) { t.Parallel() t.Run("Configured projects", func(t *testing.T) { t.Parallel() t.Run("delete a file, close it, recreate it", func(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) session, utils := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, files["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) assert.NilError(t, utils.FS().Remove("/home/projects/TS/p1/src/x.ts")) session.DidCloseFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts") ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program := ls.GetProgram() assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil) err = utils.FS().WriteFile("/home/projects/TS/p1/src/x.ts", "", false) assert.NilError(t, err) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, "", lsproto.LanguageKindTypeScript) ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts") assert.NilError(t, err) program = ls.GetProgram() assert.Assert(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil) assert.Equal(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "") }) }) t.Run("Inferred projects", func(t *testing.T) { t.Parallel() t.Run("delete a file, close it, recreate it", func(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) delete(files, "/home/projects/TS/p1/tsconfig.json") session, utils := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, files["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) err := utils.FS().Remove("/home/projects/TS/p1/src/x.ts") assert.NilError(t, err) session.DidCloseFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts") ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program := ls.GetProgram() assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil) err = utils.FS().WriteFile("/home/projects/TS/p1/src/x.ts", "", false) assert.NilError(t, err) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, "", lsproto.LanguageKindTypeScript) ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts") assert.NilError(t, err) program = ls.GetProgram() assert.Assert(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil) assert.Equal(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "") }) }) }) t.Run("DidSaveFile", func(t *testing.T) { t.Parallel() t.Run("save event first", func(t *testing.T) { t.Parallel() session, _ := projecttestutil.Setup(defaultFiles) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) snapshot, release := session.Snapshot() defer release() assert.Equal(t, snapshot.ID(), uint64(1)) session.DidSaveFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts") session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, Uri: "file:///home/projects/TS/p1/src/index.ts", }, }) session.WaitForBackgroundTasks() snapshot, release = session.Snapshot() defer release() // We didn't need a snapshot change, but the session overlays should be updated. assert.Equal(t, snapshot.ID(), uint64(1)) // Open another file to force a snapshot update so we can see the changes. session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript) snapshot, release = session.Snapshot() defer release() assert.Equal(t, snapshot.GetFile("/home/projects/TS/p1/src/index.ts").MatchesDiskText(), true) }) t.Run("watch event first", func(t *testing.T) { t.Parallel() session, _ := projecttestutil.Setup(defaultFiles) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) snapshot, release := session.Snapshot() defer release() assert.Equal(t, snapshot.ID(), uint64(1)) session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, Uri: "file:///home/projects/TS/p1/src/index.ts", }, }) session.DidSaveFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts") session.WaitForBackgroundTasks() snapshot, release = session.Snapshot() defer release() // We didn't need a snapshot change, but the session overlays should be updated. assert.Equal(t, snapshot.ID(), uint64(1)) // Open another file to force a snapshot update so we can see the changes. session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript) snapshot, release = session.Snapshot() defer release() assert.Equal(t, snapshot.GetFile("/home/projects/TS/p1/src/index.ts").MatchesDiskText(), true) }) }) t.Run("Source file sharing", func(t *testing.T) { t.Parallel() t.Run("projects with similar options share source files", func(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) files["/home/projects/TS/p2/tsconfig.json"] = `{ "compilerOptions": { "noLib": true, "module": "nodenext", "strict": true, "noCheck": true } }` files["/home/projects/TS/p2/src/index.ts"] = `import { x } from "../../p1/src/x";` session, _ := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p2/src/index.ts", 1, files["/home/projects/TS/p2/src/index.ts"].(string), lsproto.LanguageKindTypeScript) snapshot, release := session.Snapshot() defer release() assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 2) ls1, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program1 := ls1.GetProgram() ls2, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p2/src/index.ts") assert.NilError(t, err) program2 := ls2.GetProgram() assert.Equal(t, program1.GetSourceFile("/home/projects/TS/p1/src/x.ts"), program2.GetSourceFile("/home/projects/TS/p1/src/x.ts"), ) }) t.Run("projects with different options do not share source files", func(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) files["/home/projects/TS/p2/tsconfig.json"] = `{ "compilerOptions": { "module": "nodenext", "jsx": "react" } }` files["/home/projects/TS/p2/src/index.ts"] = `import { x } from "../../p1/src/x";` session, _ := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p2/src/index.ts", 1, files["/home/projects/TS/p2/src/index.ts"].(string), lsproto.LanguageKindTypeScript) snapshot, release := session.Snapshot() defer release() assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 2) ls1, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program1 := ls1.GetProgram() ls2, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p2/src/index.ts") assert.NilError(t, err) program2 := ls2.GetProgram() x1 := program1.GetSourceFile("/home/projects/TS/p1/src/x.ts") x2 := program2.GetSourceFile("/home/projects/TS/p1/src/x.ts") assert.Assert(t, x1 != nil && x2 != nil) assert.Assert(t, x1 != x2) }) }) t.Run("DidChangeWatchedFiles", func(t *testing.T) { t.Parallel() t.Run("change open file", func(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) session, utils := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, files["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) programBefore := lsBefore.GetProgram() err = utils.FS().WriteFile("/home/projects/TS/p1/src/x.ts", `export const x = 2;`, false) assert.NilError(t, err) session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, Uri: "file:///home/projects/TS/p1/src/x.ts", }, }) lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) // Program should remain the same since the file is open and changes are handled through DidChangeTextDocument assert.Equal(t, programBefore, lsAfter.GetProgram()) }) t.Run("change closed program file", func(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) session, utils := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) programBefore := lsBefore.GetProgram() err = utils.FS().WriteFile("/home/projects/TS/p1/src/x.ts", `export const x = 2;`, false) assert.NilError(t, err) session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, Uri: "file:///home/projects/TS/p1/src/x.ts", }, }) lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) assert.Check(t, lsAfter.GetProgram() != programBefore) }) t.Run("change program file not in tsconfig root files", func(t *testing.T) { t.Parallel() for _, workspaceDir := range []string{"/", "/home/projects/TS/p1", "/somewhere/else/entirely"} { t.Run("workspaceDir="+strings.ReplaceAll(workspaceDir, "/", "_"), func(t *testing.T) { t.Parallel() files := map[string]any{ "/home/projects/TS/p1/tsconfig.json": `{ "compilerOptions": { "noLib": true, "module": "nodenext", "strict": true }, "files": ["src/index.ts"] }`, "/home/projects/TS/p1/src/index.ts": `import { x } from "../../x";`, "/home/projects/TS/x.ts": `export const x = 1;`, } session, utils := projecttestutil.SetupWithOptions(files, &project.SessionOptions{ CurrentDirectory: workspaceDir, DefaultLibraryPath: bundled.LibPath(), TypingsLocation: projecttestutil.TestTypingsLocation, PositionEncoding: lsproto.PositionEncodingKindUTF8, WatchEnabled: true, LoggingEnabled: true, }) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) lsBefore, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) programBefore := lsBefore.GetProgram() session.WaitForBackgroundTasks() var xWatched bool outer: for _, call := range utils.Client().WatchFilesCalls() { for _, watcher := range call.Watchers { if core.Must(glob.Parse(*watcher.GlobPattern.Pattern)).Match("/home/projects/TS/x.ts") { xWatched = true break outer } } } assert.Check(t, xWatched) err = utils.FS().WriteFile("/home/projects/TS/x.ts", `export const x = 2;`, false) assert.NilError(t, err) session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, Uri: "file:///home/projects/TS/x.ts", }, }) lsAfter, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) assert.Check(t, lsAfter.GetProgram() != programBefore) }) } }) t.Run("change config file", func(t *testing.T) { t.Parallel() files := map[string]any{ "/home/projects/TS/p1/tsconfig.json": `{ "compilerOptions": { "noLib": true, "strict": false } }`, "/home/projects/TS/p1/src/x.ts": `export declare const x: number | undefined;`, "/home/projects/TS/p1/src/index.ts": ` import { x } from "./x"; let y: number = x;`, } session, utils := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program := ls.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0) err = utils.FS().WriteFile("/home/projects/TS/p1/tsconfig.json", `{ "compilerOptions": { "noLib": false, "strict": true } }`, false) assert.NilError(t, err) session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, Uri: "file:///home/projects/TS/p1/tsconfig.json", }, }) ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program = ls.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1) }) t.Run("delete explicitly included file", func(t *testing.T) { t.Parallel() files := map[string]any{ "/home/projects/TS/p1/tsconfig.json": `{ "compilerOptions": { "noLib": true }, "files": ["src/index.ts", "src/x.ts"] }`, "/home/projects/TS/p1/src/x.ts": `export declare const x: number | undefined;`, "/home/projects/TS/p1/src/index.ts": `import { x } from "./x";`, } session, utils := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program := ls.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0) err = utils.FS().Remove("/home/projects/TS/p1/src/x.ts") assert.NilError(t, err) session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeDeleted, Uri: "file:///home/projects/TS/p1/src/x.ts", }, }) ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program = ls.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1) assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil) }) t.Run("delete wildcard included file", func(t *testing.T) { t.Parallel() files := map[string]any{ "/home/projects/TS/p1/tsconfig.json": `{ "compilerOptions": { "noLib": true }, "include": ["src"] }`, "/home/projects/TS/p1/src/index.ts": `let x = 2;`, "/home/projects/TS/p1/src/x.ts": `let y = x;`, } session, utils := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/x.ts", 1, files["/home/projects/TS/p1/src/x.ts"].(string), lsproto.LanguageKindTypeScript) ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts") assert.NilError(t, err) program := ls.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/x.ts"))), 0) err = utils.FS().Remove("/home/projects/TS/p1/src/index.ts") assert.NilError(t, err) session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeDeleted, Uri: "file:///home/projects/TS/p1/src/index.ts", }, }) ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/x.ts") assert.NilError(t, err) program = ls.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/x.ts"))), 1) }) t.Run("create explicitly included file", func(t *testing.T) { t.Parallel() files := map[string]any{ "/home/projects/TS/p1/tsconfig.json": `{ "compilerOptions": { "noLib": true }, "files": ["src/index.ts", "src/y.ts"] }`, "/home/projects/TS/p1/src/index.ts": `import { y } from "./y";`, } session, utils := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program := ls.GetProgram() // Initially should have an error because y.ts is missing assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1) // Add the missing file err = utils.FS().WriteFile("/home/projects/TS/p1/src/y.ts", `export const y = 1;`, false) assert.NilError(t, err) session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeCreated, Uri: "file:///home/projects/TS/p1/src/y.ts", }, }) // Error should be resolved ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program = ls.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0) assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/y.ts") != nil) }) t.Run("create failed lookup location", func(t *testing.T) { t.Parallel() files := map[string]any{ "/home/projects/TS/p1/tsconfig.json": `{ "compilerOptions": { "noLib": true }, "files": ["src/index.ts"] }`, "/home/projects/TS/p1/src/index.ts": `import { z } from "./z";`, } session, utils := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program := ls.GetProgram() // Initially should have an error because z.ts is missing assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1) // Add a new file through failed lookup watch err = utils.FS().WriteFile("/home/projects/TS/p1/src/z.ts", `export const z = 1;`, false) assert.NilError(t, err) session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeCreated, Uri: "file:///home/projects/TS/p1/src/z.ts", }, }) // Error should be resolved and the new file should be included in the program ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program = ls.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0) assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/z.ts") != nil) }) t.Run("create wildcard included file", func(t *testing.T) { t.Parallel() files := map[string]any{ "/home/projects/TS/p1/tsconfig.json": `{ "compilerOptions": { "noLib": true }, "include": ["src"] }`, "/home/projects/TS/p1/src/index.ts": `a;`, } session, utils := projecttestutil.Setup(files) session.DidOpenFile(context.Background(), "file:///home/projects/TS/p1/src/index.ts", 1, files["/home/projects/TS/p1/src/index.ts"].(string), lsproto.LanguageKindTypeScript) ls, err := session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program := ls.GetProgram() // Initially should have an error because declaration for 'a' is missing assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1) // Add a new file through wildcard watch err = utils.FS().WriteFile("/home/projects/TS/p1/src/a.ts", `const a = 1;`, false) assert.NilError(t, err) session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeCreated, Uri: "file:///home/projects/TS/p1/src/a.ts", }, }) // Error should be resolved and the new file should be included in the program ls, err = session.GetLanguageService(context.Background(), "file:///home/projects/TS/p1/src/index.ts") assert.NilError(t, err) program = ls.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0) assert.Check(t, program.GetSourceFile("/home/projects/TS/p1/src/a.ts") != nil) }) }) } func ptrTo[T any](v T) *T { return &v }