From a4cff856296e3778ac6c63788dad23e22f336b3b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 19 Jun 2025 12:33:19 -0700 Subject: [PATCH 1/2] Add support for workspace --- internal/ls/completions_test.go | 2 +- internal/lsp/server.go | 37 +++++++++++- internal/project/ata_test.go | 44 +++++++------- internal/project/defaultprojectfinder_test.go | 37 +++++++----- internal/project/projectlifetime_test.go | 30 +++++----- .../project/projectreferencesprogram_test.go | 25 ++++---- internal/project/service.go | 23 ++------ internal/project/service_test.go | 58 +++++++++---------- internal/project/workspace.go | 49 ++++++++++++++++ internal/project/workspace_test.go | 49 ++++++++++++++++ 10 files changed, 243 insertions(+), 111 deletions(-) create mode 100644 internal/project/workspace.go create mode 100644 internal/project/workspace_test.go diff --git a/internal/ls/completions_test.go b/internal/ls/completions_test.go index 7dbdeca8ab..ae46514c85 100644 --- a/internal/ls/completions_test.go +++ b/internal/ls/completions_test.go @@ -2172,7 +2172,7 @@ func assertIncludesItem(t *testing.T, actual *lsproto.CompletionList, expected * func createLanguageService(ctx context.Context, fileName string, files map[string]any) (*ls.LanguageService, func()) { projectService, _ := projecttestutil.Setup(files, nil) - projectService.OpenFile(fileName, files[fileName].(string), core.GetScriptKindFromFileName(fileName), "") + projectService.OpenFile(fileName, files[fileName].(string), core.GetScriptKindFromFileName(fileName)) project := projectService.Projects()[0] return project.GetLanguageServiceForRequest(ctx) } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index cc3017be5b..53c12950f6 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -141,6 +141,7 @@ type Server struct { initializeParams *lsproto.InitializeParams positionEncoding lsproto.PositionEncodingKind + workspace *project.Workspace watchEnabled bool watcherID atomic.Uint32 @@ -471,6 +472,8 @@ func (s *Server) handleRequestOrNotification(ctx context.Context, req *lsproto.R return s.handleDidClose(ctx, req) case *lsproto.DidChangeWatchedFilesParams: return s.handleDidChangeWatchedFiles(ctx, req) + case *lsproto.DidChangeWorkspaceFoldersParams: + return s.handleDidChangeWorkspaceFolders(ctx, req) case *lsproto.DocumentDiagnosticParams: return s.handleDocumentDiagnostic(ctx, req) case *lsproto.HoverParams: @@ -566,6 +569,14 @@ func (s *Server) handleInitialize(req *lsproto.RequestMessage) { FirstTriggerCharacter: "{", MoreTriggerCharacter: &[]string{"}", ";", "\n"}, }, + Workspace: &lsproto.WorkspaceOptions{ + WorkspaceFolders: &lsproto.WorkspaceFoldersServerCapabilities{ + Supported: ptrTo(true), + ChangeNotifications: ptrTo(lsproto.StringOrBoolean{ + Boolean: ptrTo(true), + }), + }, + }, }, }) } @@ -591,12 +602,20 @@ func (s *Server) handleInitialized(ctx context.Context, req *lsproto.RequestMess s.projectService.SetCompilerOptionsForInferredProjects(s.compilerOptionsForInferredProjects) } + if s.initializeParams.RootUri.Value != "" { + s.projectService.Workspace.SetRoot(ls.DocumentURIToFileName(s.initializeParams.RootUri.Value)) + } + if s.initializeParams.WorkspaceFolders != nil { + for _, folder := range s.initializeParams.WorkspaceFolders.Value { + s.projectService.Workspace.AddFolder(ls.DocumentURIToFileName(lsproto.DocumentUri(folder.Uri))) + } + } return nil } func (s *Server) handleDidOpen(ctx context.Context, req *lsproto.RequestMessage) error { params := req.Params.(*lsproto.DidOpenTextDocumentParams) - s.projectService.OpenFile(ls.DocumentURIToFileName(params.TextDocument.Uri), params.TextDocument.Text, ls.LanguageKindToScriptKind(params.TextDocument.LanguageId), "") + s.projectService.OpenFile(ls.DocumentURIToFileName(params.TextDocument.Uri), params.TextDocument.Text, ls.LanguageKindToScriptKind(params.TextDocument.LanguageId)) return nil } @@ -622,6 +641,19 @@ func (s *Server) handleDidChangeWatchedFiles(ctx context.Context, req *lsproto.R return s.projectService.OnWatchedFilesChanged(ctx, params.Changes) } +func (s *Server) handleDidChangeWorkspaceFolders(ctx context.Context, req *lsproto.RequestMessage) error { + params := req.Params.(*lsproto.DidChangeWorkspaceFoldersParams) + if params.Event != nil { + for _, folder := range params.Event.Added { + s.workspace.AddFolder(ls.DocumentURIToFileName(lsproto.DocumentUri(folder.Uri))) + } + for _, folder := range params.Event.Removed { + s.workspace.RemoveFolder(ls.DocumentURIToFileName(lsproto.DocumentUri(folder.Uri))) + } + } + return nil +} + func (s *Server) handleDocumentDiagnostic(ctx context.Context, req *lsproto.RequestMessage) error { params := req.Params.(*lsproto.DocumentDiagnosticParams) project := s.projectService.EnsureDefaultProjectForURI(params.TextDocument.Uri) @@ -827,7 +859,8 @@ func isBlockingMethod(method lsproto.Method) bool { lsproto.MethodTextDocumentDidChange, lsproto.MethodTextDocumentDidSave, lsproto.MethodTextDocumentDidClose, - lsproto.MethodWorkspaceDidChangeWatchedFiles: + lsproto.MethodWorkspaceDidChangeWatchedFiles, + lsproto.MethodWorkspaceDidChangeWorkspaceFolders: return true } return false diff --git a/internal/project/ata_test.go b/internal/project/ata_test.go index b0ce457c55..15300b89f7 100644 --- a/internal/project/ata_test.go +++ b/internal/project/ata_test.go @@ -34,7 +34,7 @@ func TestAta(t *testing.T) { TypesRegistry: []string{"config"}, }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) assert.Equal(t, len(service.Projects()), 1) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") assert.Equal(t, p.Kind(), project.KindConfigured) @@ -70,7 +70,7 @@ func TestAta(t *testing.T) { }, }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) assert.Equal(t, len(service.Projects()), 1) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") assert.Equal(t, p.Kind(), project.KindConfigured) @@ -108,7 +108,7 @@ func TestAta(t *testing.T) { }, }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) assert.Equal(t, len(service.Projects()), 1) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") assert.Equal(t, p.Kind(), project.KindInferred) @@ -142,7 +142,7 @@ func TestAta(t *testing.T) { TypesRegistry: []string{"jquery"}, }, }) - service.OpenFile("/user/username/projects/project/jquery.js", files["/user/username/projects/project/jquery.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/jquery.js", files["/user/username/projects/project/jquery.js"].(string), core.ScriptKindJS) assert.Equal(t, len(service.Projects()), 1) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/jquery.js") assert.Equal(t, p.Kind(), project.KindConfigured) @@ -170,7 +170,7 @@ func TestAta(t *testing.T) { TypesRegistry: []string{"node"}, }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) assert.Equal(t, len(service.Projects()), 1) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") assert.Equal(t, p.Kind(), project.KindConfigured) @@ -210,8 +210,8 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project1/app.js", files["/user/username/projects/project1/app.js"].(string), core.ScriptKindJS, "") - service.OpenFile("/user/username/projects/project2/app.js", files["/user/username/projects/project2/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project1/app.js", files["/user/username/projects/project1/app.js"].(string), core.ScriptKindJS) + service.OpenFile("/user/username/projects/project2/app.js", files["/user/username/projects/project2/app.js"].(string), core.ScriptKindJS) _, p1 := service.EnsureDefaultProjectForFile("/user/username/projects/project1/app.js") _, p2 := service.EnsureDefaultProjectForFile("/user/username/projects/project2/app.js") var installStatuses []project.TypingsInstallerStatus @@ -271,8 +271,8 @@ func TestAta(t *testing.T) { } } - service.OpenFile("/user/username/projects/project1/app.js", files["/user/username/projects/project1/app.js"].(string), core.ScriptKindJS, "") - service.OpenFile("/user/username/projects/project2/app.js", files["/user/username/projects/project2/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project1/app.js", files["/user/username/projects/project1/app.js"].(string), core.ScriptKindJS) + service.OpenFile("/user/username/projects/project2/app.js", files["/user/username/projects/project2/app.js"].(string), core.ScriptKindJS) _, p1 := service.EnsureDefaultProjectForFile("/user/username/projects/project1/app.js") _, p2 := service.EnsureDefaultProjectForFile("/user/username/projects/project2/app.js") // Order is determinate since second install will run only after completing first one @@ -310,7 +310,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -352,7 +352,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -394,7 +394,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -436,7 +436,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -463,7 +463,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -495,7 +495,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -522,7 +522,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -571,7 +571,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -602,7 +602,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -651,7 +651,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -697,7 +697,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -745,7 +745,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus @@ -791,7 +791,7 @@ func TestAta(t *testing.T) { }, }) - service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/user/username/projects/project/app.js", files["/user/username/projects/project/app.js"].(string), core.ScriptKindJS) _, p := service.EnsureDefaultProjectForFile("/user/username/projects/project/app.js") // Order is determinate since second install will run only after completing first one status := <-host.ServiceOptions.InstallStatus diff --git a/internal/project/defaultprojectfinder_test.go b/internal/project/defaultprojectfinder_test.go index 0473ccdc8f..a214778544 100644 --- a/internal/project/defaultprojectfinder_test.go +++ b/internal/project/defaultprojectfinder_test.go @@ -24,14 +24,15 @@ func TestDefaultProjectFinder(t *testing.T) { t.Parallel() files := filesForSolutionConfigFile([]string{"./tsconfig-src.json"}, "", nil) service, _ := projecttestutil.Setup(files, nil) - service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS, "") + service.Workspace.SetRoot("/user/username/workspaces/dummy") + service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) srcProject := service.ConfiguredProject(tspath.Path("/user/username/projects/myproject/tsconfig-src.json")) assert.Assert(t, srcProject != nil) _, project := service.EnsureDefaultProjectForFile("/user/username/projects/myproject/src/main.ts") assert.Equal(t, project, srcProject) service.CloseFile("/user/username/projects/myproject/src/main.ts") - service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS, "/user/username/workspaces/dummy") + service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.InferredProject(tspath.Path("/user/username/workspaces/dummy")) != nil) configFileExists(t, service, tspath.Path("/user/username/projects/myproject/tsconfig.json"), false) @@ -44,14 +45,15 @@ func TestDefaultProjectFinder(t *testing.T) { applyIndirectProjectFiles(files, 1, "") applyIndirectProjectFiles(files, 2, "") service, _ := projecttestutil.Setup(files, nil) - service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS, "") + service.Workspace.SetRoot("/user/username/workspaces/dummy") + service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) srcProject := service.ConfiguredProject(tspath.Path("/user/username/projects/myproject/tsconfig-src.json")) assert.Assert(t, srcProject != nil) _, project := service.EnsureDefaultProjectForFile("/user/username/projects/myproject/src/main.ts") assert.Equal(t, project, srcProject) service.CloseFile("/user/username/projects/myproject/src/main.ts") - service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS, "/user/username/workspaces/dummy") + service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.InferredProject(tspath.Path("/user/username/workspaces/dummy")) != nil) configFileExists(t, service, tspath.Path("/user/username/projects/myproject/tsconfig.json"), false) @@ -64,14 +66,15 @@ func TestDefaultProjectFinder(t *testing.T) { t.Parallel() files := filesForSolutionConfigFile([]string{"./tsconfig-src.json"}, `"disableReferencedProjectLoad": true`, nil) service, _ := projecttestutil.Setup(files, nil) - service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS, "") + service.Workspace.SetRoot("/user/username/workspaces/dummy") + service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.ConfiguredProject(tspath.Path("/user/username/projects/myproject/tsconfig-src.json")) == nil) // Should not create referenced project _, proj := service.EnsureDefaultProjectForFile("/user/username/projects/myproject/src/main.ts") assert.Equal(t, proj.Kind(), project.KindInferred) service.CloseFile("/user/username/projects/myproject/src/main.ts") - service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS, "/user/username/workspaces/dummy") + service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.InferredProject(tspath.Path("/user/username/workspaces/dummy")) != nil) configFileExists(t, service, tspath.Path("/user/username/projects/myproject/tsconfig.json"), false) @@ -83,14 +86,15 @@ func TestDefaultProjectFinder(t *testing.T) { files := filesForSolutionConfigFile([]string{"./tsconfig-indirect1.json"}, "", nil) applyIndirectProjectFiles(files, 1, `"disableReferencedProjectLoad": true`) service, _ := projecttestutil.Setup(files, nil) - service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS, "") + service.Workspace.SetRoot("/user/username/workspaces/dummy") + service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.ConfiguredProject(tspath.Path("/user/username/projects/myproject/tsconfig-src.json")) == nil) // Inferred project because no default is found _, proj := service.EnsureDefaultProjectForFile("/user/username/projects/myproject/src/main.ts") assert.Equal(t, proj.Kind(), project.KindInferred) service.CloseFile("/user/username/projects/myproject/src/main.ts") - service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS, "/user/username/workspaces/dummy") + service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.InferredProject(tspath.Path("/user/username/workspaces/dummy")) != nil) configFileExists(t, service, tspath.Path("/user/username/projects/myproject/tsconfig.json"), false) @@ -104,7 +108,8 @@ func TestDefaultProjectFinder(t *testing.T) { applyIndirectProjectFiles(files, 1, `"disableReferencedProjectLoad": true`) applyIndirectProjectFiles(files, 2, "") service, _ := projecttestutil.Setup(files, nil) - service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS, "") + service.Workspace.SetRoot("/user/username/workspaces/dummy") + service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) srcProject := service.ConfiguredProject(tspath.Path("/user/username/projects/myproject/tsconfig-src.json")) assert.Assert(t, srcProject != nil) @@ -112,7 +117,7 @@ func TestDefaultProjectFinder(t *testing.T) { _, proj := service.EnsureDefaultProjectForFile("/user/username/projects/myproject/src/main.ts") assert.Equal(t, proj, srcProject) service.CloseFile("/user/username/projects/myproject/src/main.ts") - service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS, "/user/username/workspaces/dummy") + service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.InferredProject(tspath.Path("/user/username/workspaces/dummy")) != nil) configFileExists(t, service, tspath.Path("/user/username/projects/myproject/tsconfig.json"), false) @@ -130,7 +135,8 @@ func TestDefaultProjectFinder(t *testing.T) { export function bar() {} ` service, _ := projecttestutil.Setup(files, nil) - service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS, "") + service.Workspace.SetRoot("/user/username/workspaces/dummy") + service.OpenFile("/user/username/projects/myproject/src/main.ts", files["/user/username/projects/myproject/src/main.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 2) srcProject := service.ConfiguredProject(tspath.Path("/user/username/projects/myproject/tsconfig-src.json")) assert.Assert(t, srcProject != nil) @@ -138,7 +144,7 @@ func TestDefaultProjectFinder(t *testing.T) { _, project := service.EnsureDefaultProjectForFile("/user/username/projects/myproject/src/main.ts") assert.Equal(t, project, srcProject) service.CloseFile("/user/username/projects/myproject/src/main.ts") - service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS, "/user/username/workspaces/dummy") + service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.InferredProject(tspath.Path("/user/username/workspaces/dummy")) != nil) configFileExists(t, service, tspath.Path("/user/username/projects/myproject/tsconfig.json"), false) @@ -190,7 +196,8 @@ func TestDefaultProjectFinder(t *testing.T) { }`, } service, _ := projecttestutil.Setup(files, nil) - service.OpenFile("/home/src/projects/project/app/Component-demos.ts", files["/home/src/projects/project/app/Component-demos.ts"].(string), core.ScriptKindTS, "") + service.Workspace.SetRoot("/user/username/workspaces/dummy") + service.OpenFile("/home/src/projects/project/app/Component-demos.ts", files["/home/src/projects/project/app/Component-demos.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) demoProject := service.ConfiguredProject(tspath.Path("/home/src/projects/project/demos/tsconfig.json")) assert.Assert(t, demoProject != nil) @@ -200,7 +207,7 @@ func TestDefaultProjectFinder(t *testing.T) { _, project := service.EnsureDefaultProjectForFile("/home/src/projects/project/app/Component-demos.ts") assert.Equal(t, project, demoProject) service.CloseFile("/home/src/projects/project/app/Component-demos.ts") - service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS, "/user/username/workspaces/dummy") + service.OpenFile("/user/username/workspaces/dummy/dummy.ts", "const x = 1;", core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.InferredProject(tspath.Path("/user/username/workspaces/dummy")) != nil) configFileExists(t, service, tspath.Path("/home/src/projects/project/app/tsconfig.json"), false) @@ -234,7 +241,7 @@ func TestDefaultProjectFinder(t *testing.T) { }`, } service, _ := projecttestutil.Setup(files, nil) - service.OpenFile("/home/src/projects/project/src/index.d.ts", files["/home/src/projects/project/src/index.d.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/src/projects/project/src/index.d.ts", files["/home/src/projects/project/src/index.d.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 2) assert.Assert(t, service.ConfiguredProject(tspath.Path("/home/src/projects/project/tsconfig.json")) != nil) _, proj := service.EnsureDefaultProjectForFile("/home/src/projects/project/src/index.d.ts") diff --git a/internal/project/projectlifetime_test.go b/internal/project/projectlifetime_test.go index 4abe4aeae6..2d5ac129b7 100644 --- a/internal/project/projectlifetime_test.go +++ b/internal/project/projectlifetime_test.go @@ -54,8 +54,8 @@ func TestProjectLifetime(t *testing.T) { } service, host := projecttestutil.Setup(files, nil) assert.Equal(t, len(service.Projects()), 0) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") - service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) + service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 2) assert.Assert(t, service.ConfiguredProject(serviceToPath(service, "/home/projects/TS/p1/tsconfig.json")) != nil) assert.Assert(t, service.ConfiguredProject(serviceToPath(service, "/home/projects/TS/p2/tsconfig.json")) != nil) @@ -64,7 +64,7 @@ func TestProjectLifetime(t *testing.T) { configFileExists(t, service, serviceToPath(service, "/home/projects/TS/p2/tsconfig.json"), true) service.CloseFile("/home/projects/TS/p1/src/index.ts") - service.OpenFile("/home/projects/TS/p3/src/index.ts", files["/home/projects/TS/p3/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p3/src/index.ts", files["/home/projects/TS/p3/src/index.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 2) assert.Assert(t, service.ConfiguredProject(serviceToPath(service, "/home/projects/TS/p1/tsconfig.json")) == nil) assert.Assert(t, service.ConfiguredProject(serviceToPath(service, "/home/projects/TS/p2/tsconfig.json")) != nil) @@ -79,7 +79,7 @@ func TestProjectLifetime(t *testing.T) { service.CloseFile("/home/projects/TS/p2/src/index.ts") service.CloseFile("/home/projects/TS/p3/src/index.ts") - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) assert.Assert(t, service.ConfiguredProject(serviceToPath(service, "/home/projects/TS/p1/tsconfig.json")) != nil) assert.Assert(t, service.ConfiguredProject(serviceToPath(service, "/home/projects/TS/p2/tsconfig.json")) == nil) assert.Assert(t, service.ConfiguredProject(serviceToPath(service, "/home/projects/TS/p3/tsconfig.json")) == nil) @@ -108,15 +108,18 @@ func TestProjectLifetime(t *testing.T) { "/home/projects/TS/p3/config.ts": `let x = 1, y = 2;`, } service, _ := projecttestutil.Setup(files, nil) + service.Workspace.AddFolder("/home/projects/TS/p1") + service.Workspace.AddFolder("/home/projects/TS/p2") + service.Workspace.AddFolder("/home/projects/TS/p3") assert.Equal(t, len(service.Projects()), 0) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "/home/projects/TS/p1") - service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS, "/home/projects/TS/p2") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) + service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 2) assert.Assert(t, service.InferredProject(serviceToPath(service, "/home/projects/TS/p1")) != nil) assert.Assert(t, service.InferredProject(serviceToPath(service, "/home/projects/TS/p2")) != nil) service.CloseFile("/home/projects/TS/p1/src/index.ts") - service.OpenFile("/home/projects/TS/p3/src/index.ts", files["/home/projects/TS/p3/src/index.ts"].(string), core.ScriptKindTS, "/home/projects/TS/p3") + service.OpenFile("/home/projects/TS/p3/src/index.ts", files["/home/projects/TS/p3/src/index.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 2) assert.Assert(t, service.InferredProject(serviceToPath(service, "/home/projects/TS/p1")) == nil) assert.Assert(t, service.InferredProject(serviceToPath(service, "/home/projects/TS/p2")) != nil) @@ -125,7 +128,7 @@ func TestProjectLifetime(t *testing.T) { service.CloseFile("/home/projects/TS/p2/src/index.ts") service.CloseFile("/home/projects/TS/p3/src/index.ts") - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "/home/projects/TS/p1") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) assert.Assert(t, service.InferredProject(serviceToPath(service, "/home/projects/TS/p1")) != nil) assert.Assert(t, service.InferredProject(serviceToPath(service, "/home/projects/TS/p2")) == nil) assert.Assert(t, service.InferredProject(serviceToPath(service, "/home/projects/TS/p3")) == nil) @@ -148,26 +151,27 @@ func TestProjectLifetime(t *testing.T) { } service, _ := projecttestutil.Setup(files, nil) assert.Equal(t, len(service.Projects()), 0) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") - service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) + service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.InferredProject(tspath.Path("")) != nil) service.CloseFile("/home/projects/TS/p1/src/index.ts") - service.OpenFile("/home/projects/TS/p3/src/index.ts", files["/home/projects/TS/p3/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p3/src/index.ts", files["/home/projects/TS/p3/src/index.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.InferredProject(tspath.Path("")) != nil) assert.Assert(t, service.DocumentStore().GetScriptInfoByPath(serviceToPath(service, "/home/projects/TS/p1/src/index.ts")) == nil) service.CloseFile("/home/projects/TS/p2/src/index.ts") service.CloseFile("/home/projects/TS/p3/src/index.ts") - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) assert.Assert(t, service.InferredProject(tspath.Path("")) != nil) assert.Assert(t, service.DocumentStore().GetScriptInfoByPath(serviceToPath(service, "/home/projects/TS/p2/src/index.ts")) == nil) assert.Assert(t, service.DocumentStore().GetScriptInfoByPath(serviceToPath(service, "/home/projects/TS/p3/src/index.ts")) == nil) service.CloseFile("/home/projects/TS/p1/src/index.ts") - service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS, "/home/projects/TS/p2") + service.Workspace.AddFolder("/home/projects/TS/p2") + service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) assert.Assert(t, service.InferredProject(tspath.Path("")) == nil) assert.Assert(t, service.DocumentStore().GetScriptInfoByPath(serviceToPath(service, "/home/projects/TS/p1/src/index.ts")) == nil) diff --git a/internal/project/projectreferencesprogram_test.go b/internal/project/projectreferencesprogram_test.go index 6478cab201..d34c3669f8 100644 --- a/internal/project/projectreferencesprogram_test.go +++ b/internal/project/projectreferencesprogram_test.go @@ -25,8 +25,9 @@ func TestProjectReferencesProgram(t *testing.T) { t.Parallel() files := filesForReferencedProjectProgram(false) service, _ := projecttestutil.Setup(files, nil) + service.Workspace.SetRoot("/user/username/projects/myproject") assert.Equal(t, len(service.Projects()), 0) - service.OpenFile("/user/username/projects/myproject/main/main.ts", files["/user/username/projects/myproject/main/main.ts"].(string), core.ScriptKindTS, "/user/username/projects/myproject") + service.OpenFile("/user/username/projects/myproject/main/main.ts", files["/user/username/projects/myproject/main/main.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) p := service.Projects()[0] assert.Equal(t, p.Kind(), project.KindConfigured) @@ -51,8 +52,9 @@ func TestProjectReferencesProgram(t *testing.T) { export declare function fn5(): void; ` service, _ := projecttestutil.Setup(files, nil) + service.Workspace.SetRoot("/user/username/projects/myproject") assert.Equal(t, len(service.Projects()), 0) - service.OpenFile("/user/username/projects/myproject/main/main.ts", files["/user/username/projects/myproject/main/main.ts"].(string), core.ScriptKindTS, "/user/username/projects/myproject") + service.OpenFile("/user/username/projects/myproject/main/main.ts", files["/user/username/projects/myproject/main/main.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) p := service.Projects()[0] assert.Equal(t, p.Kind(), project.KindConfigured) @@ -71,7 +73,7 @@ func TestProjectReferencesProgram(t *testing.T) { files, aTest, bFoo, bBar := filesForSymlinkReferences(false, "") service, _ := projecttestutil.Setup(files, nil) assert.Equal(t, len(service.Projects()), 0) - service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS, "") + service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) p := service.Projects()[0] assert.Equal(t, p.Kind(), project.KindConfigured) @@ -90,7 +92,7 @@ func TestProjectReferencesProgram(t *testing.T) { files, aTest, bFoo, bBar := filesForSymlinkReferences(true, "") service, _ := projecttestutil.Setup(files, nil) assert.Equal(t, len(service.Projects()), 0) - service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS, "") + service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) p := service.Projects()[0] assert.Equal(t, p.Kind(), project.KindConfigured) @@ -109,7 +111,7 @@ func TestProjectReferencesProgram(t *testing.T) { files, aTest, bFoo, bBar := filesForSymlinkReferences(false, "@issue/") service, _ := projecttestutil.Setup(files, nil) assert.Equal(t, len(service.Projects()), 0) - service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS, "") + service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) p := service.Projects()[0] assert.Equal(t, p.Kind(), project.KindConfigured) @@ -128,7 +130,7 @@ func TestProjectReferencesProgram(t *testing.T) { files, aTest, bFoo, bBar := filesForSymlinkReferences(true, "@issue/") service, _ := projecttestutil.Setup(files, nil) assert.Equal(t, len(service.Projects()), 0) - service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS, "") + service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) p := service.Projects()[0] assert.Equal(t, p.Kind(), project.KindConfigured) @@ -147,7 +149,7 @@ func TestProjectReferencesProgram(t *testing.T) { files, aTest, bFoo, bBar := filesForSymlinkReferencesInSubfolder(false, "") service, _ := projecttestutil.Setup(files, nil) assert.Equal(t, len(service.Projects()), 0) - service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS, "") + service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) p := service.Projects()[0] assert.Equal(t, p.Kind(), project.KindConfigured) @@ -166,7 +168,7 @@ func TestProjectReferencesProgram(t *testing.T) { files, aTest, bFoo, bBar := filesForSymlinkReferencesInSubfolder(true, "") service, _ := projecttestutil.Setup(files, nil) assert.Equal(t, len(service.Projects()), 0) - service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS, "") + service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) p := service.Projects()[0] assert.Equal(t, p.Kind(), project.KindConfigured) @@ -185,7 +187,7 @@ func TestProjectReferencesProgram(t *testing.T) { files, aTest, bFoo, bBar := filesForSymlinkReferencesInSubfolder(false, "@issue/") service, _ := projecttestutil.Setup(files, nil) assert.Equal(t, len(service.Projects()), 0) - service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS, "") + service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) p := service.Projects()[0] assert.Equal(t, p.Kind(), project.KindConfigured) @@ -204,7 +206,7 @@ func TestProjectReferencesProgram(t *testing.T) { files, aTest, bFoo, bBar := filesForSymlinkReferencesInSubfolder(true, "@issue/") service, _ := projecttestutil.Setup(files, nil) assert.Equal(t, len(service.Projects()), 0) - service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS, "") + service.OpenFile(aTest, files[aTest].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) p := service.Projects()[0] assert.Equal(t, p.Kind(), project.KindConfigured) @@ -222,7 +224,8 @@ func TestProjectReferencesProgram(t *testing.T) { t.Parallel() files := filesForReferencedProjectProgram(false) service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/user/username/projects/myproject/main/main.ts", files["/user/username/projects/myproject/main/main.ts"].(string), core.ScriptKindTS, "/user/username/projects/myproject") + service.Workspace.SetRoot("/user/username/projects/myproject") + service.OpenFile("/user/username/projects/myproject/main/main.ts", files["/user/username/projects/myproject/main/main.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) project := service.Projects()[0] programBefore := project.GetProgram() diff --git a/internal/project/service.go b/internal/project/service.go index b4a5249cd1..b4c855ebfa 100644 --- a/internal/project/service.go +++ b/internal/project/service.go @@ -41,6 +41,7 @@ type Service struct { options ServiceOptions comparePathsOptions tspath.ComparePathsOptions converters *ls.Converters + Workspace Workspace projectsMu sync.RWMutex configuredProjects map[tspath.Path]*Project @@ -84,6 +85,7 @@ func NewService(host ServiceHost, options ServiceOptions) *Service { openFiles: make(map[tspath.Path]string), configFileForOpenFiles: make(map[tspath.Path]string), } + service.Workspace.comparePathsOptions = service.comparePathsOptions service.defaultProjectFinder = &defaultProjectFinder{ service: service, configFileForOpenFiles: make(map[tspath.Path]string), @@ -209,11 +211,12 @@ func (s *Service) isOpenFile(info *ScriptInfo) bool { return ok } -func (s *Service) OpenFile(fileName string, fileContent string, scriptKind core.ScriptKind, projectRootPath string) { +func (s *Service) OpenFile(fileName string, fileContent string, scriptKind core.ScriptKind) { path := s.toPath(fileName) existing := s.documentStore.GetScriptInfoByPath(path) + // Find project root path info := s.documentStore.getOrCreateScriptInfoWorker(fileName, path, scriptKind, true /*openedByClient*/, fileContent, true /*deferredDeleteOk*/, s.FS()) - s.openFiles[info.path] = projectRootPath + s.openFiles[info.path] = s.Workspace.GetProjectRootPath(fileName) if existing == nil && info != nil && !info.isDynamic { // Invoke wild card directory watcher to ensure that the file presence is reflected s.configFileRegistry.tryInvokeWildCardDirectories(fileName, info.path) @@ -614,22 +617,6 @@ func (s *Service) getInferredProjectForProjectRootPath(info *ScriptInfo, project } return nil } - - if !info.isDynamic { - var bestMatch *Project - for _, project := range s.inferredProjects { - if project.rootPath != "" && - tspath.ContainsPath(string(project.rootPath), string(info.path), s.comparePathsOptions) && - (bestMatch == nil || len(bestMatch.rootPath) <= len(project.rootPath)) { - bestMatch = project - } - } - - if bestMatch != nil { - return bestMatch - } - } - // unrooted inferred project if no best match found if unrootedProject, ok := s.inferredProjects[""]; ok { return unrootedProject diff --git a/internal/project/service_test.go b/internal/project/service_test.go index 3e39e14096..5a3573b329 100644 --- a/internal/project/service_test.go +++ b/internal/project/service_test.go @@ -39,7 +39,7 @@ func TestService(t *testing.T) { t.Parallel() service, _ := projecttestutil.Setup(defaultFiles, nil) assert.Equal(t, len(service.Projects()), 0) - service.OpenFile("/home/projects/TS/p1/src/index.ts", defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 1) p := service.Projects()[0] assert.Equal(t, p.Kind(), project.KindConfigured) @@ -51,7 +51,7 @@ func TestService(t *testing.T) { t.Run("create inferred project", func(t *testing.T) { t.Parallel() service, _ := projecttestutil.Setup(defaultFiles, nil) - service.OpenFile("/home/projects/TS/p1/config.ts", defaultFiles["/home/projects/TS/p1/config.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/config.ts", defaultFiles["/home/projects/TS/p1/config.ts"].(string), core.ScriptKindTS) // Find tsconfig, load, notice config.ts is not included, create inferred project assert.Equal(t, len(service.Projects()), 2) _, proj := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/config.ts") @@ -61,9 +61,9 @@ func TestService(t *testing.T) { t.Run("inferred project for in-memory files", func(t *testing.T) { t.Parallel() service, _ := projecttestutil.Setup(defaultFiles, nil) - service.OpenFile("/home/projects/TS/p1/config.ts", defaultFiles["/home/projects/TS/p1/config.ts"].(string), core.ScriptKindTS, "") - service.OpenFile("^/untitled/ts-nul-authority/Untitled-1", "x", core.ScriptKindTS, "") - service.OpenFile("^/untitled/ts-nul-authority/Untitled-2", "y", core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/config.ts", defaultFiles["/home/projects/TS/p1/config.ts"].(string), core.ScriptKindTS) + service.OpenFile("^/untitled/ts-nul-authority/Untitled-1", "x", core.ScriptKindTS) + service.OpenFile("^/untitled/ts-nul-authority/Untitled-2", "y", core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 2) _, p1 := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/config.ts") _, p2 := service.EnsureDefaultProjectForFile("^/untitled/ts-nul-authority/Untitled-1") @@ -78,7 +78,7 @@ func TestService(t *testing.T) { "/home/projects/TS/p1/index.js": `import { x } from "./x";`, } service, _ := projecttestutil.Setup(jsFiles, nil) - service.OpenFile("/home/projects/TS/p1/index.js", jsFiles["/home/projects/TS/p1/index.js"].(string), core.ScriptKindJS, "") + service.OpenFile("/home/projects/TS/p1/index.js", jsFiles["/home/projects/TS/p1/index.js"].(string), core.ScriptKindJS) assert.Equal(t, len(service.Projects()), 1) project := service.Projects()[0] assert.Assert(t, project.GetProgram().GetSourceFile("/home/projects/TS/p1/index.js") != nil) @@ -90,7 +90,7 @@ func TestService(t *testing.T) { t.Run("update script info eagerly and program lazily", func(t *testing.T) { t.Parallel() service, _ := projecttestutil.Setup(defaultFiles, nil) - service.OpenFile("/home/projects/TS/p1/src/x.ts", defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/x.ts", defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS) info, proj := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/x.ts") programBefore := proj.GetProgram() err := service.ChangeFile( @@ -128,7 +128,7 @@ func TestService(t *testing.T) { t.Run("unchanged source files are reused", func(t *testing.T) { t.Parallel() service, _ := projecttestutil.Setup(defaultFiles, nil) - service.OpenFile("/home/projects/TS/p1/src/x.ts", defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/x.ts", defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS) _, proj := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/x.ts") programBefore := proj.GetProgram() indexFileBefore := programBefore.GetSourceFile("/home/projects/TS/p1/src/index.ts") @@ -166,7 +166,7 @@ func TestService(t *testing.T) { files := maps.Clone(defaultFiles) files["/home/projects/TS/p1/y.ts"] = `export const y = 2;` service, _ := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) assert.Check(t, service.DocumentStore().GetScriptInfoByPath(serviceToPath(service, "/home/projects/TS/p1/y.ts")) == nil) // Avoid using initial file set after this point files = nil //nolint:ineffassign @@ -212,7 +212,7 @@ func TestService(t *testing.T) { "include": ["src/index.ts"] }` service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) _, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") programBefore := project.GetProgram() assert.Equal(t, len(programBefore.GetSourceFiles()), 2) @@ -277,8 +277,8 @@ func TestService(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "") - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS) + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) assert.Equal(t, service.DocumentStore().SourceFileCount(), 2) // Avoid using initial file set after this point files = nil //nolint:ineffassign @@ -293,7 +293,7 @@ func TestService(t *testing.T) { err := host.FS().WriteFile("/home/projects/TS/p1/src/x.ts", "", false) assert.NilError(t, err) - service.OpenFile("/home/projects/TS/p1/src/x.ts", "", core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/x.ts", "", core.ScriptKindTS) assert.Equal(t, service.DocumentStore().GetScriptInfoByPath(serviceToPath(service, "/home/projects/TS/p1/src/x.ts")).Text(), "") assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil) assert.Equal(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "") @@ -307,8 +307,8 @@ func TestService(t *testing.T) { files := maps.Clone(defaultFiles) delete(files, "/home/projects/TS/p1/tsconfig.json") service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "") - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS) + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) // Avoid using initial file set after this point files = nil //nolint:ineffassign @@ -322,7 +322,7 @@ func TestService(t *testing.T) { err = host.FS().WriteFile("/home/projects/TS/p1/src/x.ts", "", false) assert.NilError(t, err) - service.OpenFile("/home/projects/TS/p1/src/x.ts", "", core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/x.ts", "", core.ScriptKindTS) assert.Equal(t, service.DocumentStore().GetScriptInfoByPath(serviceToPath(service, "/home/projects/TS/p1/src/x.ts")).Text(), "") assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil) assert.Equal(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "") @@ -345,8 +345,8 @@ func TestService(t *testing.T) { }` files["/home/projects/TS/p2/src/index.ts"] = `import { x } from "../../p1/src/x";` service, _ := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") - service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) + service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 2) // Avoid using initial file set after this point files = nil //nolint:ineffassign @@ -370,8 +370,8 @@ func TestService(t *testing.T) { }` files["/home/projects/TS/p2/src/index.ts"] = `import { x } from "../../p1/src/x";` service, _ := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") - service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) + service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS) assert.Equal(t, len(service.Projects()), 2) // Avoid using initial file set after this point files = nil //nolint:ineffassign @@ -391,8 +391,8 @@ func TestService(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "") - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS) + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) _, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") programBefore := project.GetProgram() // Avoid using initial file set after this point @@ -415,7 +415,7 @@ func TestService(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) _, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") programBefore := project.GetProgram() // Avoid using initial file set after this point @@ -450,7 +450,7 @@ func TestService(t *testing.T) { } service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) _, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") program := project.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0) @@ -487,7 +487,7 @@ func TestService(t *testing.T) { "/home/projects/TS/p1/src/index.ts": `import { x } from "./x";`, } service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) _, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") program := project.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0) @@ -520,7 +520,7 @@ func TestService(t *testing.T) { "/home/projects/TS/p1/src/x.ts": `let y = x;`, } service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS) _, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/x.ts") program := project.GetProgram() assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/x.ts"))), 0) @@ -551,7 +551,7 @@ func TestService(t *testing.T) { "/home/projects/TS/p1/src/index.ts": `import { y } from "./y";`, } service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) _, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") program := project.GetProgram() @@ -609,7 +609,7 @@ func TestService(t *testing.T) { "/home/projects/TS/p1/src/index.ts": `import { z } from "./z";`, } service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) _, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") program := project.GetProgram() @@ -650,7 +650,7 @@ func TestService(t *testing.T) { "/home/projects/TS/p1/src/index.ts": `a;`, } service, host := projecttestutil.Setup(files, nil) - service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS) _, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") program := project.GetProgram() diff --git a/internal/project/workspace.go b/internal/project/workspace.go new file mode 100644 index 0000000000..c45aee45f2 --- /dev/null +++ b/internal/project/workspace.go @@ -0,0 +1,49 @@ +package project + +import ( + "slices" + + "github.com/microsoft/typescript-go/internal/tspath" +) + +type Workspace struct { + root string + folders []string + comparePathsOptions tspath.ComparePathsOptions +} + +func (w *Workspace) GetProjectRootPath(fileName string) string { + // If dynamic then use root otherwise try to find the best match in folders + if !isDynamicFileName(fileName) { + var bestMatch string + for _, folder := range w.folders { + if tspath.ContainsPath(folder, fileName, w.comparePathsOptions) && + (bestMatch == "" || len(bestMatch) <= len(folder)) { + bestMatch = folder + } + } + + if bestMatch != "" { + return bestMatch + } + + if w.root != "" && tspath.ContainsPath(w.root, fileName, w.comparePathsOptions) { + return w.root + } + + return "" + } + return w.root +} + +func (w *Workspace) SetRoot(root string) { + w.root = root +} + +func (w *Workspace) AddFolder(folder string) { + w.folders = append(w.folders, folder) +} + +func (w *Workspace) RemoveFolder(folder string) { + w.folders = slices.Delete(w.folders, slices.Index(w.folders, folder), slices.Index(w.folders, folder)+1) +} diff --git a/internal/project/workspace_test.go b/internal/project/workspace_test.go new file mode 100644 index 0000000000..b784d9858b --- /dev/null +++ b/internal/project/workspace_test.go @@ -0,0 +1,49 @@ +package project_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/project" + "gotest.tools/v3/assert" +) + +func TestWorkspace(t *testing.T) { + t.Parallel() + + t.Run("GetProjectRootPath", func(t *testing.T) { + t.Parallel() + var ws project.Workspace + ws.SetRoot("/Coding") + ws.AddFolder("/Coding/One") + ws.AddFolder("/Coding/Two") + ws.AddFolder("/Coding/Two/Nested") + assert.Equal(t, ws.GetProjectRootPath("/Coding/One/file/path.txt"), "/Coding/One") + assert.Equal(t, ws.GetProjectRootPath("/Coding/Two/file/path.txt"), "/Coding/Two") + assert.Equal(t, ws.GetProjectRootPath("/Coding/Two/Nested/file"), "/Coding/Two/Nested") + assert.Equal(t, ws.GetProjectRootPath("/Coding/Two/Nested/f"), "/Coding/Two/Nested") + assert.Equal(t, ws.GetProjectRootPath("/Coding/One"), "/Coding/One") + assert.Equal(t, ws.GetProjectRootPath("/Coding/Two"), "/Coding/Two") + assert.Equal(t, ws.GetProjectRootPath("/Coding/Two/Nested"), "/Coding/Two/Nested") + assert.Equal(t, ws.GetProjectRootPath("^/untitled/ts-nul-authority/Untitled-1"), "/Coding") + assert.Equal(t, ws.GetProjectRootPath("/SomethingElse/file.ts"), "") + }) + + t.Run("Removing from folders", func(t *testing.T) { + t.Parallel() + var ws project.Workspace + ws.SetRoot("/Coding") + ws.AddFolder("/Coding/One") + ws.AddFolder("/Coding/Two") + ws.AddFolder("/Coding/Two/Nested") + ws.RemoveFolder("/Coding/Two") + assert.Equal(t, ws.GetProjectRootPath("/Coding/One/file/path.txt"), "/Coding/One") + assert.Equal(t, ws.GetProjectRootPath("/Coding/Two/file/path.txt"), "/Coding") + assert.Equal(t, ws.GetProjectRootPath("/Coding/Two/Nested/file"), "/Coding/Two/Nested") + assert.Equal(t, ws.GetProjectRootPath("/Coding/Two/Nested/f"), "/Coding/Two/Nested") + assert.Equal(t, ws.GetProjectRootPath("/Coding/One"), "/Coding/One") + assert.Equal(t, ws.GetProjectRootPath("/Coding/Two"), "/Coding") + assert.Equal(t, ws.GetProjectRootPath("/Coding/Two/Nested"), "/Coding/Two/Nested") + assert.Equal(t, ws.GetProjectRootPath("^/untitled/ts-nul-authority/Untitled-1"), "/Coding") + assert.Equal(t, ws.GetProjectRootPath("/SomethingElse/file.ts"), "") + }) +} From a3a4a2f223644ddf3c9d1563eecbe7ec37da0c6a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 19 Jun 2025 16:25:33 -0700 Subject: [PATCH 2/2] Inferred project test --- internal/project/service_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/internal/project/service_test.go b/internal/project/service_test.go index 5a3573b329..1d950697a0 100644 --- a/internal/project/service_test.go +++ b/internal/project/service_test.go @@ -10,6 +10,7 @@ import ( "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/project" "github.com/microsoft/typescript-go/internal/testutil/projecttestutil" + "github.com/microsoft/typescript-go/internal/tspath" "gotest.tools/v3/assert" ) @@ -83,6 +84,34 @@ func TestService(t *testing.T) { project := service.Projects()[0] assert.Assert(t, project.GetProgram().GetSourceFile("/home/projects/TS/p1/index.js") != nil) }) + + t.Run("inferred project per project root", func(t *testing.T) { + t.Parallel() + files := map[string]any{ + "/user/username/projects/project/a/file1.ts": `let x = 1;`, + "/user/username/projects/project/a/file2.ts": `let y = 1;`, + "/user/username/projects/project/b/file2.ts": `let x = 3;`, + "/user/username/projects/project/c/file3.ts": `let z = 4;`, + } + service, _ := projecttestutil.Setup(files, nil) + service.Workspace.AddFolder("/user/username/projects/project/a") + service.Workspace.AddFolder("/user/username/projects/project/b") + service.OpenFile("/user/username/projects/project/a/file1.ts", files["/user/username/projects/project/a/file1.ts"].(string), core.ScriptKindTS) + service.OpenFile("/user/username/projects/project/a/file2.ts", files["/user/username/projects/project/a/file2.ts"].(string), core.ScriptKindTS) + service.OpenFile("/user/username/projects/project/b/file2.ts", files["/user/username/projects/project/b/file2.ts"].(string), core.ScriptKindTS) + service.OpenFile("/user/username/projects/project/c/file3.ts", files["/user/username/projects/project/c/file3.ts"].(string), core.ScriptKindTS) + assert.Equal(t, len(service.Projects()), 3) + projecta := service.InferredProject(tspath.Path("/user/username/projects/project/a")) + assert.Assert(t, projecta != nil) + assert.Assert(t, projecta.GetProgram().GetSourceFile("/user/username/projects/project/a/file1.ts") != nil) + assert.Assert(t, projecta.GetProgram().GetSourceFile("/user/username/projects/project/a/file2.ts") != nil) + projectb := service.InferredProject(tspath.Path("/user/username/projects/project/b")) + assert.Assert(t, projectb != nil) + assert.Assert(t, projectb.GetProgram().GetSourceFile("/user/username/projects/project/b/file2.ts") != nil) + projectc := service.InferredProject(tspath.Path("")) + assert.Assert(t, projectc != nil) + assert.Assert(t, projectc.GetProgram().GetSourceFile("/user/username/projects/project/c/file3.ts") != nil) + }) }) t.Run("ChangeFile", func(t *testing.T) {