From 976c1562ddbc17e7bc185ca827aeffefced7a1b6 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 30 May 2025 11:44:00 -0700 Subject: [PATCH 1/9] Cache FS in LS --- internal/project/project.go | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/internal/project/project.go b/internal/project/project.go index 75be8ee20f..3450ae5b13 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -17,6 +17,7 @@ import ( "github.com/microsoft/typescript-go/internal/tsoptions" "github.com/microsoft/typescript-go/internal/tspath" "github.com/microsoft/typescript-go/internal/vfs" + "github.com/microsoft/typescript-go/internal/vfs/cachedvfs" ) //go:generate go tool golang.org/x/tools/cmd/stringer -type=Kind -output=project_stringer_generated.go @@ -87,7 +88,7 @@ type ProjectHost interface { var _ compiler.CompilerHost = (*Project)(nil) type Project struct { - host ProjectHost + host *projectHostWithCachedFS name string kind Kind @@ -141,9 +142,11 @@ func NewInferredProject(compilerOptions *core.CompilerOptions, currentDirectory } func NewProject(name string, kind Kind, currentDirectory string, host ProjectHost) *Project { + cachedHost := newProjectHostWithCachedFS(host) + host.Log(fmt.Sprintf("Creating %sProject: %s, currentDirectory: %s", kind.String(), name, currentDirectory)) project := &Project{ - host: host, + host: cachedHost, name: name, kind: kind, currentDirectory: currentDirectory, @@ -151,11 +154,11 @@ func NewProject(name string, kind Kind, currentDirectory string, host ProjectHos } project.comparePathsOptions = tspath.ComparePathsOptions{ CurrentDirectory: currentDirectory, - UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(), + UseCaseSensitiveFileNames: project.host.FS().UseCaseSensitiveFileNames(), } - client := host.Client() - if host.IsWatchEnabled() && client != nil { - globMapper := createGlobMapper(host) + client := project.host.Client() + if project.host.IsWatchEnabled() && client != nil { + globMapper := createGlobMapper(project.host) project.failedLookupsWatch = newWatchedFiles(client, lsproto.WatchKindCreate, globMapper) project.affectingLocationsWatch = newWatchedFiles(client, lsproto.WatchKindChange|lsproto.WatchKindCreate|lsproto.WatchKindDelete, globMapper) } @@ -163,6 +166,22 @@ func NewProject(name string, kind Kind, currentDirectory string, host ProjectHos return project } +type projectHostWithCachedFS struct { + ProjectHost + fs *cachedvfs.FS +} + +func newProjectHostWithCachedFS(host ProjectHost) *projectHostWithCachedFS { + return &projectHostWithCachedFS{ + ProjectHost: host, + fs: cachedvfs.From(host.FS()), + } +} + +func (p *projectHostWithCachedFS) FS() vfs.FS { + return p.fs +} + // FS implements compiler.CompilerHost. func (p *Project) FS() vfs.FS { return p.host.FS() @@ -406,6 +425,7 @@ func (p *Project) updateGraph() bool { start := time.Now() p.log("Starting updateGraph: Project: " + p.name) + p.host.fs.ClearCache() var writeFileNames bool oldProgram := p.program p.initialLoadPending = false From 163289ca5e1598f8c4b79c097b7310f1c7c92627 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 30 May 2025 14:05:13 -0700 Subject: [PATCH 2/9] Don't replace FS out from under host --- internal/project/service_test.go | 67 +++++++------------ .../projecttestutil/projecttestutil.go | 4 -- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/internal/project/service_test.go b/internal/project/service_test.go index c4533383cc..1702fb7460 100644 --- a/internal/project/service_test.go +++ b/internal/project/service_test.go @@ -209,7 +209,7 @@ func TestService(t *testing.T) { }, "include": ["src/index.ts"] }` - service, host := projecttestutil.Setup(files) + service, _ := projecttestutil.Setup(files) 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() @@ -250,7 +250,6 @@ func TestService(t *testing.T) { }, "include": ["./**/*"] }` - host.ReplaceFS(files) err = service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, @@ -270,14 +269,13 @@ func TestService(t *testing.T) { t.Parallel() t.Run("delete a file, close it, recreate it", func(t *testing.T) { t.Parallel() - service, host := projecttestutil.Setup(defaultFiles) - service.OpenFile("/home/projects/TS/p1/src/x.ts", defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "") + files := maps.Clone(defaultFiles) + service, _ := projecttestutil.Setup(files) + 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", defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") assert.Equal(t, service.SourceFileCount(), 2) - files := maps.Clone(defaultFiles) delete(files, "/home/projects/TS/p1/src/x.ts") - host.ReplaceFS(files) service.CloseFile("/home/projects/TS/p1/src/x.ts") assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts") == nil) @@ -285,7 +283,6 @@ func TestService(t *testing.T) { assert.Equal(t, service.SourceFileCount(), 1) files["/home/projects/TS/p1/src/x.ts"] = `` - host.ReplaceFS(files) service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "") assert.Equal(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts").Text(), "") assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil) @@ -299,19 +296,17 @@ func TestService(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) delete(files, "/home/projects/TS/p1/tsconfig.json") - service, host := projecttestutil.Setup(files) - 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/index.ts", defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + service, _ := projecttestutil.Setup(files) + 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, "") delete(files, "/home/projects/TS/p1/src/x.ts") - host.ReplaceFS(files) service.CloseFile("/home/projects/TS/p1/src/x.ts") assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts") == nil) assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil) files["/home/projects/TS/p1/src/x.ts"] = `` - host.ReplaceFS(files) service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "") assert.Equal(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts").Text(), "") assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil) @@ -375,15 +370,14 @@ func TestService(t *testing.T) { t.Run("change open file", func(t *testing.T) { t.Parallel() - service, host := projecttestutil.Setup(defaultFiles) - 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/index.ts", defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + files := maps.Clone(defaultFiles) + service, _ := projecttestutil.Setup(files) + 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() - files := maps.Clone(defaultFiles) files["/home/projects/TS/p1/src/x.ts"] = `export const x = 2;` - host.ReplaceFS(files) assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, @@ -396,14 +390,13 @@ func TestService(t *testing.T) { t.Run("change closed program file", func(t *testing.T) { t.Parallel() - service, host := projecttestutil.Setup(defaultFiles) - service.OpenFile("/home/projects/TS/p1/src/index.ts", defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") + files := maps.Clone(defaultFiles) + service, _ := projecttestutil.Setup(files) + 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() - files := maps.Clone(defaultFiles) files["/home/projects/TS/p1/src/x.ts"] = `export const x = 2;` - host.ReplaceFS(files) assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, @@ -429,20 +422,18 @@ func TestService(t *testing.T) { let y: number = x;`, } - service, host := projecttestutil.Setup(files) + service, _ := projecttestutil.Setup(files) 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) - filesCopy := maps.Clone(files) - filesCopy["/home/projects/TS/p1/tsconfig.json"] = `{ + files["/home/projects/TS/p1/tsconfig.json"] = `{ "compilerOptions": { "noLib": false, "strict": true } }` - host.ReplaceFS(filesCopy) assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, @@ -466,15 +457,13 @@ func TestService(t *testing.T) { "/home/projects/TS/p1/src/x.ts": `export declare const x: number | undefined;`, "/home/projects/TS/p1/src/index.ts": `import { x } from "./x";`, } - service, host := projecttestutil.Setup(files) + service, _ := projecttestutil.Setup(files) 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) - filesCopy := maps.Clone(files) - delete(filesCopy, "/home/projects/TS/p1/src/x.ts") - host.ReplaceFS(filesCopy) + delete(files, "/home/projects/TS/p1/src/x.ts") assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeDeleted, @@ -499,15 +488,13 @@ func TestService(t *testing.T) { "/home/projects/TS/p1/src/index.ts": `let x = 2;`, "/home/projects/TS/p1/src/x.ts": `let y = x;`, } - service, host := projecttestutil.Setup(files) + service, _ := projecttestutil.Setup(files) 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) - filesCopy := maps.Clone(files) - delete(filesCopy, "/home/projects/TS/p1/src/index.ts") - host.ReplaceFS(filesCopy) + delete(files, "/home/projects/TS/p1/src/index.ts") assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeDeleted, @@ -561,9 +548,7 @@ func TestService(t *testing.T) { }) // Add the missing file - filesCopy := maps.Clone(files) - filesCopy["/home/projects/TS/p1/src/y.ts"] = `export const y = 1;` - host.ReplaceFS(filesCopy) + files["/home/projects/TS/p1/src/y.ts"] = `export const y = 1;` assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeCreated, @@ -602,9 +587,7 @@ func TestService(t *testing.T) { })) // Add a new file through failed lookup watch - filesCopy := maps.Clone(files) - filesCopy["/home/projects/TS/p1/src/z.ts"] = `export const z = 1;` - host.ReplaceFS(filesCopy) + files["/home/projects/TS/p1/src/z.ts"] = `export const z = 1;` assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeCreated, @@ -629,7 +612,7 @@ func TestService(t *testing.T) { }`, "/home/projects/TS/p1/src/index.ts": `a;`, } - service, host := projecttestutil.Setup(files) + service, _ := projecttestutil.Setup(files) 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() @@ -638,9 +621,7 @@ func TestService(t *testing.T) { 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 - filesCopy := maps.Clone(files) - filesCopy["/home/projects/TS/p1/src/a.ts"] = `const a = 1;` - host.ReplaceFS(filesCopy) + files["/home/projects/TS/p1/src/a.ts"] = `const a = 1;` assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeCreated, diff --git a/internal/testutil/projecttestutil/projecttestutil.go b/internal/testutil/projecttestutil/projecttestutil.go index fc6f9f9cb0..bbbea7b534 100644 --- a/internal/testutil/projecttestutil/projecttestutil.go +++ b/internal/testutil/projecttestutil/projecttestutil.go @@ -57,10 +57,6 @@ func (p *ProjectServiceHost) Client() project.Client { return p.ClientMock } -func (p *ProjectServiceHost) ReplaceFS(files map[string]any) { - p.fs = bundled.WrapFS(vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)) -} - var _ project.ServiceHost = (*ProjectServiceHost)(nil) func Setup(files map[string]any) (*project.Service, *ProjectServiceHost) { From a68ac7447b46300a37454c6e66d0900960233f5a Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 30 May 2025 16:38:04 -0700 Subject: [PATCH 3/9] Fix files map use --- internal/project/service_test.go | 86 ++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/internal/project/service_test.go b/internal/project/service_test.go index 1702fb7460..cfd358a015 100644 --- a/internal/project/service_test.go +++ b/internal/project/service_test.go @@ -168,6 +168,7 @@ func TestService(t *testing.T) { service, _ := projecttestutil.Setup(files) service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "") assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/y.ts") == nil) + files = nil // Avoid using initial file set after this point err := service.ChangeFile( lsproto.VersionedTextDocumentIdentifier{ @@ -209,11 +210,12 @@ func TestService(t *testing.T) { }, "include": ["src/index.ts"] }` - service, _ := projecttestutil.Setup(files) + service, host := projecttestutil.Setup(files) 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) + files = nil // Avoid using initial file set after this point err := service.ChangeFile( lsproto.VersionedTextDocumentIdentifier{ @@ -242,14 +244,16 @@ func TestService(t *testing.T) { ) assert.NilError(t, err) - files["/home/projects/TS/p1/tsconfig.json"] = `{ + err = host.FS().WriteFile("/home/projects/TS/p1/tsconfig.json", `{ "compilerOptions": { "noLib": true, "module": "nodenext", "strict": true, }, "include": ["./**/*"] - }` + }`, false) + assert.NilError(t, err) + err = service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, @@ -270,20 +274,23 @@ func TestService(t *testing.T) { t.Run("delete a file, close it, recreate it", func(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) - service, _ := projecttestutil.Setup(files) + service, host := projecttestutil.Setup(files) 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", defaultFiles["/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.Equal(t, service.SourceFileCount(), 2) + files = nil // Avoid using initial file set after this point - delete(files, "/home/projects/TS/p1/src/x.ts") + assert.NilError(t, host.FS().Remove("/home/projects/TS/p1/src/x.ts")) service.CloseFile("/home/projects/TS/p1/src/x.ts") assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts") == nil) assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil) assert.Equal(t, service.SourceFileCount(), 1) - files["/home/projects/TS/p1/src/x.ts"] = `` - service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "") + 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, "") assert.Equal(t, service.GetScriptInfo("/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(), "") @@ -296,18 +303,22 @@ func TestService(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) delete(files, "/home/projects/TS/p1/tsconfig.json") - service, _ := projecttestutil.Setup(files) + service, host := projecttestutil.Setup(files) 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, "") + files = nil // Avoid using initial file set after this point - delete(files, "/home/projects/TS/p1/src/x.ts") + err := host.FS().Remove("/home/projects/TS/p1/src/x.ts") + assert.NilError(t, err) service.CloseFile("/home/projects/TS/p1/src/x.ts") assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts") == nil) assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil) - files["/home/projects/TS/p1/src/x.ts"] = `` - service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "") + 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, "") assert.Equal(t, service.GetScriptInfo("/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(), "") @@ -333,6 +344,7 @@ func TestService(t *testing.T) { 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) + files = nil // Avoid using initial file set after this point _, p1 := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") _, p2 := service.EnsureDefaultProjectForFile("/home/projects/TS/p2/src/index.ts") assert.Equal( @@ -356,6 +368,7 @@ func TestService(t *testing.T) { 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) + files = nil // Avoid using initial file set after this point _, p1 := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") _, p2 := service.EnsureDefaultProjectForFile("/home/projects/TS/p2/src/index.ts") x1 := p1.GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") @@ -371,13 +384,16 @@ func TestService(t *testing.T) { t.Run("change open file", func(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) - service, _ := projecttestutil.Setup(files) + service, host := projecttestutil.Setup(files) 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() + files = nil // Avoid using initial file set after this point + + err := host.FS().WriteFile("/home/projects/TS/p1/src/x.ts", `export const x = 2;`, false) + assert.NilError(t, err) - files["/home/projects/TS/p1/src/x.ts"] = `export const x = 2;` assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, @@ -391,12 +407,15 @@ func TestService(t *testing.T) { t.Run("change closed program file", func(t *testing.T) { t.Parallel() files := maps.Clone(defaultFiles) - service, _ := projecttestutil.Setup(files) + service, host := projecttestutil.Setup(files) 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() + files = nil // Avoid using initial file set after this point + + err := host.FS().WriteFile("/home/projects/TS/p1/src/x.ts", `export const x = 2;`, false) + assert.NilError(t, err) - files["/home/projects/TS/p1/src/x.ts"] = `export const x = 2;` assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, @@ -422,18 +441,20 @@ func TestService(t *testing.T) { let y: number = x;`, } - service, _ := projecttestutil.Setup(files) + service, host := projecttestutil.Setup(files) 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) - files["/home/projects/TS/p1/tsconfig.json"] = `{ + err := host.FS().WriteFile("/home/projects/TS/p1/tsconfig.json", `{ "compilerOptions": { "noLib": false, "strict": true } - }` + }`, false) + assert.NilError(t, err) + assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeChanged, @@ -457,13 +478,15 @@ func TestService(t *testing.T) { "/home/projects/TS/p1/src/x.ts": `export declare const x: number | undefined;`, "/home/projects/TS/p1/src/index.ts": `import { x } from "./x";`, } - service, _ := projecttestutil.Setup(files) + service, host := projecttestutil.Setup(files) 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) - delete(files, "/home/projects/TS/p1/src/x.ts") + err := host.FS().Remove("/home/projects/TS/p1/src/x.ts") + assert.NilError(t, err) + assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeDeleted, @@ -488,13 +511,15 @@ func TestService(t *testing.T) { "/home/projects/TS/p1/src/index.ts": `let x = 2;`, "/home/projects/TS/p1/src/x.ts": `let y = x;`, } - service, _ := projecttestutil.Setup(files) + service, host := projecttestutil.Setup(files) 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) - delete(files, "/home/projects/TS/p1/src/index.ts") + err := host.FS().Remove("/home/projects/TS/p1/src/index.ts") + assert.NilError(t, err) + assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeDeleted, @@ -548,7 +573,9 @@ func TestService(t *testing.T) { }) // Add the missing file - files["/home/projects/TS/p1/src/y.ts"] = `export const y = 1;` + err := host.FS().WriteFile("/home/projects/TS/p1/src/y.ts", `export const y = 1;`, false) + assert.NilError(t, err) + assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeCreated, @@ -587,7 +614,9 @@ func TestService(t *testing.T) { })) // Add a new file through failed lookup watch - files["/home/projects/TS/p1/src/z.ts"] = `export const z = 1;` + err := host.FS().WriteFile("/home/projects/TS/p1/src/z.ts", `export const z = 1;`, false) + assert.NilError(t, err) + assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeCreated, @@ -612,7 +641,7 @@ func TestService(t *testing.T) { }`, "/home/projects/TS/p1/src/index.ts": `a;`, } - service, _ := projecttestutil.Setup(files) + service, host := projecttestutil.Setup(files) 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() @@ -621,7 +650,10 @@ func TestService(t *testing.T) { 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 - files["/home/projects/TS/p1/src/a.ts"] = `const a = 1;` + + err := host.FS().WriteFile("/home/projects/TS/p1/src/a.ts", `const a = 1;`, false) + assert.NilError(t, err) + assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{ { Type: lsproto.FileChangeTypeCreated, From 79459016eb11d3230ebf4f5980c4b8da974a66d4 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 2 Jun 2025 13:35:42 -0700 Subject: [PATCH 4/9] Hack ATA to not use the cached host --- internal/project/ata.go | 2 +- internal/project/project.go | 9 +++++++-- internal/project/service.go | 4 ++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/internal/project/ata.go b/internal/project/ata.go index a2daf41d0a..06d1278193 100644 --- a/internal/project/ata.go +++ b/internal/project/ata.go @@ -261,7 +261,7 @@ func (ti *TypingsInstaller) invokeRoutineToInstallTypings( if success { p.Logf("ATA:: Installed typings %v", packageNames) var installedTypingFiles []string - resolver := module.NewResolver(p, &core.CompilerOptions{ModuleResolution: core.ModuleResolutionKindNodeNext}, "", "") + resolver := module.NewResolver(p.uncachedHost, &core.CompilerOptions{ModuleResolution: core.ModuleResolutionKindNodeNext}, "", "") for _, packageName := range request.filteredTypings { typingFile := ti.typingToFileName(resolver, packageName) if typingFile == "" { diff --git a/internal/project/project.go b/internal/project/project.go index 2486ce80ff..0bf58e45f5 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -15,6 +15,7 @@ import ( "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/ls" "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/module" "github.com/microsoft/typescript-go/internal/tsoptions" "github.com/microsoft/typescript-go/internal/tspath" "github.com/microsoft/typescript-go/internal/vfs" @@ -73,6 +74,7 @@ const ( type ProjectHost interface { tsoptions.ParseConfigHost + module.ResolutionHost NewLine() string DefaultLibraryPath() string TypingsInstaller() *TypingsInstaller @@ -121,7 +123,9 @@ func typeAcquisitionChanged(opt1 *core.TypeAcquisition, opt2 *core.TypeAcquisiti var _ compiler.CompilerHost = (*Project)(nil) type Project struct { - host *projectHostWithCachedFS + // TODO: remove this hack + uncachedHost ProjectHost + host *projectHostWithCachedFS name string kind Kind @@ -190,6 +194,7 @@ func NewProject(name string, kind Kind, currentDirectory string, host ProjectHos host.Log(fmt.Sprintf("Creating %sProject: %s, currentDirectory: %s", kind.String(), name, currentDirectory)) project := &Project{ + uncachedHost: host, host: cachedHost, name: name, kind: kind, @@ -281,7 +286,7 @@ func (p *Project) NewLine() string { // Trace implements compiler.CompilerHost. func (p *Project) Trace(msg string) { - p.Log(msg) + p.host.Log(msg) } // GetDefaultLibraryPath implements compiler.CompilerHost. diff --git a/internal/project/service.go b/internal/project/service.go index 3ec6f02f94..c8190faab0 100644 --- a/internal/project/service.go +++ b/internal/project/service.go @@ -108,6 +108,10 @@ func (s *Service) Log(msg string) { s.options.Logger.Info(msg) } +func (s *Service) Trace(msg string) { + s.Log(msg) +} + func (s *Service) HasLevel(level LogLevel) bool { return s.options.Logger.HasLevel(level) } From 2f2be30756d44381579ba61576c41d2debee2cc8 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 2 Jun 2025 13:55:01 -0700 Subject: [PATCH 5/9] fix other impl --- internal/api/api.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/api/api.go b/internal/api/api.go index d03df66cd6..95e38fda6e 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -115,6 +115,11 @@ func (api *API) Log(s string) { api.options.Logger.Info(s) } +// Log implements ProjectHost. +func (api *API) Trace(s string) { + api.options.Logger.Info(s) +} + // NewLine implements ProjectHost. func (api *API) NewLine() string { return api.host.NewLine() From 71d61543ec863c6f890292256ed240d0fcd06b68 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 2 Jun 2025 14:30:21 -0700 Subject: [PATCH 6/9] Silence lint --- internal/project/service_test.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/internal/project/service_test.go b/internal/project/service_test.go index 9bd83c5f14..350d10ab5c 100644 --- a/internal/project/service_test.go +++ b/internal/project/service_test.go @@ -168,7 +168,8 @@ func TestService(t *testing.T) { 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, "") assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/y.ts") == nil) - files = nil // Avoid using initial file set after this point + // Avoid using initial file set after this point + files = nil //nolint:ineffassign err := service.ChangeFile( lsproto.VersionedTextDocumentIdentifier{ @@ -215,7 +216,8 @@ func TestService(t *testing.T) { _, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") programBefore := project.GetProgram() assert.Equal(t, len(programBefore.GetSourceFiles()), 2) - files = nil // Avoid using initial file set after this point + // Avoid using initial file set after this point + files = nil //nolint:ineffassign err := service.ChangeFile( lsproto.VersionedTextDocumentIdentifier{ @@ -278,7 +280,8 @@ func TestService(t *testing.T) { 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.SourceFileCount(), 2) - files = nil // Avoid using initial file set after this point + // Avoid using initial file set after this point + files = nil //nolint:ineffassign assert.NilError(t, host.FS().Remove("/home/projects/TS/p1/src/x.ts")) @@ -306,7 +309,8 @@ func TestService(t *testing.T) { 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, "") - files = nil // Avoid using initial file set after this point + // Avoid using initial file set after this point + files = nil //nolint:ineffassign err := host.FS().Remove("/home/projects/TS/p1/src/x.ts") assert.NilError(t, err) @@ -344,7 +348,8 @@ func TestService(t *testing.T) { 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) - files = nil // Avoid using initial file set after this point + // Avoid using initial file set after this point + files = nil //nolint:ineffassign _, p1 := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") _, p2 := service.EnsureDefaultProjectForFile("/home/projects/TS/p2/src/index.ts") assert.Equal( @@ -368,7 +373,8 @@ func TestService(t *testing.T) { 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) - files = nil // Avoid using initial file set after this point + // Avoid using initial file set after this point + files = nil //nolint:ineffassign _, p1 := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts") _, p2 := service.EnsureDefaultProjectForFile("/home/projects/TS/p2/src/index.ts") x1 := p1.GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") @@ -389,7 +395,8 @@ func TestService(t *testing.T) { 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() - files = nil // Avoid using initial file set after this point + // Avoid using initial file set after this point + files = nil //nolint:ineffassign err := host.FS().WriteFile("/home/projects/TS/p1/src/x.ts", `export const x = 2;`, false) assert.NilError(t, err) @@ -411,7 +418,8 @@ func TestService(t *testing.T) { 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() - files = nil // Avoid using initial file set after this point + // Avoid using initial file set after this point + files = nil //nolint:ineffassign err := host.FS().WriteFile("/home/projects/TS/p1/src/x.ts", `export const x = 2;`, false) assert.NilError(t, err) From 2eff7716a2c1fe48b22502bc18c9995590b09652 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 3 Jun 2025 13:30:48 -0700 Subject: [PATCH 7/9] Defer it --- internal/project/project.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/project/project.go b/internal/project/project.go index 0bf58e45f5..ae45aea8e6 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -484,9 +484,10 @@ func (p *Project) updateGraph() bool { return false } + defer p.host.fs.ClearCache() + start := time.Now() p.Log("Starting updateGraph: Project: " + p.name) - p.host.fs.ClearCache() var writeFileNames bool oldProgram := p.program p.initialLoadPending = false From fa6df8971cdb1e79df0d74a40c100e48aa8d5157 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 3 Jun 2025 15:03:56 -0700 Subject: [PATCH 8/9] Only cache during updateGraph --- internal/project/ata.go | 2 +- internal/project/project.go | 12 ++--- internal/vfs/cachedvfs/cachedvfs.go | 84 +++++++++++++++++++++++------ 3 files changed, 74 insertions(+), 24 deletions(-) diff --git a/internal/project/ata.go b/internal/project/ata.go index 4a1ded8925..31dd9b1465 100644 --- a/internal/project/ata.go +++ b/internal/project/ata.go @@ -243,7 +243,7 @@ func (ti *TypingsInstaller) invokeRoutineToInstallTypings( if success { p.Logf("ATA:: Installed typings %v", packageNames) var installedTypingFiles []string - resolver := module.NewResolver(p.uncachedHost, &core.CompilerOptions{ModuleResolution: core.ModuleResolutionKindNodeNext}, "", "") + resolver := module.NewResolver(p, &core.CompilerOptions{ModuleResolution: core.ModuleResolutionKindNodeNext}, "", "") for _, packageName := range request.filteredTypings { typingFile := ti.typingToFileName(resolver, packageName) if typingFile == "" { diff --git a/internal/project/project.go b/internal/project/project.go index ae45aea8e6..e2aec93810 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -123,9 +123,7 @@ func typeAcquisitionChanged(opt1 *core.TypeAcquisition, opt2 *core.TypeAcquisiti var _ compiler.CompilerHost = (*Project)(nil) type Project struct { - // TODO: remove this hack - uncachedHost ProjectHost - host *projectHostWithCachedFS + host *projectHostWithCachedFS name string kind Kind @@ -194,7 +192,6 @@ func NewProject(name string, kind Kind, currentDirectory string, host ProjectHos host.Log(fmt.Sprintf("Creating %sProject: %s, currentDirectory: %s", kind.String(), name, currentDirectory)) project := &Project{ - uncachedHost: host, host: cachedHost, name: name, kind: kind, @@ -223,10 +220,12 @@ type projectHostWithCachedFS struct { } func newProjectHostWithCachedFS(host ProjectHost) *projectHostWithCachedFS { - return &projectHostWithCachedFS{ + newHost := &projectHostWithCachedFS{ ProjectHost: host, fs: cachedvfs.From(host.FS()), } + newHost.fs.DisableAndClearCache() + return newHost } func (p *projectHostWithCachedFS) FS() vfs.FS { @@ -484,7 +483,8 @@ func (p *Project) updateGraph() bool { return false } - defer p.host.fs.ClearCache() + p.host.fs.Enable() + defer p.host.fs.DisableAndClearCache() start := time.Now() p.Log("Starting updateGraph: Project: " + p.name) diff --git a/internal/vfs/cachedvfs/cachedvfs.go b/internal/vfs/cachedvfs/cachedvfs.go index 132c67bfaf..0e284fe043 100644 --- a/internal/vfs/cachedvfs/cachedvfs.go +++ b/internal/vfs/cachedvfs/cachedvfs.go @@ -1,12 +1,15 @@ package cachedvfs import ( + "sync/atomic" + "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/vfs" ) type FS struct { - fs vfs.FS + fs vfs.FS + enabled atomic.Bool directoryExistsCache collections.SyncMap[string, bool] fileExistsCache collections.SyncMap[string, bool] @@ -18,7 +21,19 @@ type FS struct { var _ vfs.FS = (*FS)(nil) func From(fs vfs.FS) *FS { - return &FS{fs: fs} + fsys := &FS{fs: fs} + fsys.enabled.Store(true) + return fsys +} + +func (fsys *FS) DisableAndClearCache() { + if fsys.enabled.CompareAndSwap(true, false) { + fsys.ClearCache() + } +} + +func (fsys *FS) Enable() { + fsys.enabled.Store(true) } func (fsys *FS) ClearCache() { @@ -30,29 +45,50 @@ func (fsys *FS) ClearCache() { } func (fsys *FS) DirectoryExists(path string) bool { - if ret, ok := fsys.directoryExistsCache.Load(path); ok { - return ret + if fsys.enabled.Load() { + if ret, ok := fsys.directoryExistsCache.Load(path); ok { + return ret + } } + ret := fsys.fs.DirectoryExists(path) - fsys.directoryExistsCache.Store(path, ret) + + if fsys.enabled.Load() { + fsys.directoryExistsCache.Store(path, ret) + } + return ret } func (fsys *FS) FileExists(path string) bool { - if ret, ok := fsys.fileExistsCache.Load(path); ok { - return ret + if fsys.enabled.Load() { + if ret, ok := fsys.fileExistsCache.Load(path); ok { + return ret + } } + ret := fsys.fs.FileExists(path) - fsys.fileExistsCache.Store(path, ret) + + if fsys.enabled.Load() { + fsys.fileExistsCache.Store(path, ret) + } + return ret } func (fsys *FS) GetAccessibleEntries(path string) vfs.Entries { - if ret, ok := fsys.getAccessibleEntriesCache.Load(path); ok { - return ret + if fsys.enabled.Load() { + if ret, ok := fsys.getAccessibleEntriesCache.Load(path); ok { + return ret + } } + ret := fsys.fs.GetAccessibleEntries(path) - fsys.getAccessibleEntriesCache.Store(path, ret) + + if fsys.enabled.Load() { + fsys.getAccessibleEntriesCache.Store(path, ret) + } + return ret } @@ -61,11 +97,18 @@ func (fsys *FS) ReadFile(path string) (contents string, ok bool) { } func (fsys *FS) Realpath(path string) string { - if ret, ok := fsys.realpathCache.Load(path); ok { - return ret + if fsys.enabled.Load() { + if ret, ok := fsys.realpathCache.Load(path); ok { + return ret + } } + ret := fsys.fs.Realpath(path) - fsys.realpathCache.Store(path, ret) + + if fsys.enabled.Load() { + fsys.realpathCache.Store(path, ret) + } + return ret } @@ -74,11 +117,18 @@ func (fsys *FS) Remove(path string) error { } func (fsys *FS) Stat(path string) vfs.FileInfo { - if ret, ok := fsys.statCache.Load(path); ok { - return ret.(vfs.FileInfo) + if fsys.enabled.Load() { + if ret, ok := fsys.statCache.Load(path); ok { + return ret.(vfs.FileInfo) + } } + ret := fsys.fs.Stat(path) - fsys.statCache.Store(path, ret) + + if fsys.enabled.Load() { + fsys.statCache.Store(path, ret) + } + return ret } From ffcef9f428a5a00e000f925b5863d1c8b3f052b4 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:00:04 -0700 Subject: [PATCH 9/9] Tests --- internal/vfs/cachedvfs/cachedvfs_test.go | 140 +++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/internal/vfs/cachedvfs/cachedvfs_test.go b/internal/vfs/cachedvfs/cachedvfs_test.go index f74a42c870..a63b4b359f 100644 --- a/internal/vfs/cachedvfs/cachedvfs_test.go +++ b/internal/vfs/cachedvfs/cachedvfs_test.go @@ -34,6 +34,20 @@ func TestDirectoryExists(t *testing.T) { cached.DirectoryExists("/other/path") assert.Equal(t, 3, len(underlying.DirectoryExistsCalls())) + + cached.DisableAndClearCache() + cached.DirectoryExists("/some/path") + assert.Equal(t, 4, len(underlying.DirectoryExistsCalls())) + + cached.DirectoryExists("/some/path") + assert.Equal(t, 5, len(underlying.DirectoryExistsCalls())) + + cached.Enable() + cached.DirectoryExists("/some/path") + assert.Equal(t, 6, len(underlying.DirectoryExistsCalls())) + + cached.DirectoryExists("/some/path") + assert.Equal(t, 6, len(underlying.DirectoryExistsCalls())) } func TestFileExists(t *testing.T) { @@ -54,6 +68,20 @@ func TestFileExists(t *testing.T) { cached.FileExists("/other/path/file.txt") assert.Equal(t, 3, len(underlying.FileExistsCalls())) + + cached.DisableAndClearCache() + cached.FileExists("/some/path/file.txt") + assert.Equal(t, 4, len(underlying.FileExistsCalls())) + + cached.FileExists("/some/path/file.txt") + assert.Equal(t, 5, len(underlying.FileExistsCalls())) + + cached.Enable() + cached.FileExists("/some/path/file.txt") + assert.Equal(t, 6, len(underlying.FileExistsCalls())) + + cached.FileExists("/some/path/file.txt") + assert.Equal(t, 6, len(underlying.FileExistsCalls())) } func TestGetAccessibleEntries(t *testing.T) { @@ -74,6 +102,20 @@ func TestGetAccessibleEntries(t *testing.T) { cached.GetAccessibleEntries("/other/path") assert.Equal(t, 3, len(underlying.GetAccessibleEntriesCalls())) + + cached.DisableAndClearCache() + cached.GetAccessibleEntries("/some/path") + assert.Equal(t, 4, len(underlying.GetAccessibleEntriesCalls())) + + cached.GetAccessibleEntries("/some/path") + assert.Equal(t, 5, len(underlying.GetAccessibleEntriesCalls())) + + cached.Enable() + cached.GetAccessibleEntries("/some/path") + assert.Equal(t, 6, len(underlying.GetAccessibleEntriesCalls())) + + cached.GetAccessibleEntries("/some/path") + assert.Equal(t, 6, len(underlying.GetAccessibleEntriesCalls())) } func TestRealpath(t *testing.T) { @@ -94,6 +136,20 @@ func TestRealpath(t *testing.T) { cached.Realpath("/other/path") assert.Equal(t, 3, len(underlying.RealpathCalls())) + + cached.DisableAndClearCache() + cached.Realpath("/some/path") + assert.Equal(t, 4, len(underlying.RealpathCalls())) + + cached.Realpath("/some/path") + assert.Equal(t, 5, len(underlying.RealpathCalls())) + + cached.Enable() + cached.Realpath("/some/path") + assert.Equal(t, 6, len(underlying.RealpathCalls())) + + cached.Realpath("/some/path") + assert.Equal(t, 6, len(underlying.RealpathCalls())) } func TestStat(t *testing.T) { @@ -114,6 +170,20 @@ func TestStat(t *testing.T) { cached.Stat("/other/path") assert.Equal(t, 3, len(underlying.StatCalls())) + + cached.DisableAndClearCache() + cached.Stat("/some/path") + assert.Equal(t, 4, len(underlying.StatCalls())) + + cached.Stat("/some/path") + assert.Equal(t, 5, len(underlying.StatCalls())) + + cached.Enable() + cached.Stat("/some/path") + assert.Equal(t, 6, len(underlying.StatCalls())) + + cached.Stat("/some/path") + assert.Equal(t, 6, len(underlying.StatCalls())) } func TestReadFile(t *testing.T) { @@ -131,6 +201,20 @@ func TestReadFile(t *testing.T) { cached.ClearCache() cached.ReadFile("/some/path/file.txt") assert.Equal(t, 3, len(underlying.ReadFileCalls())) + + cached.DisableAndClearCache() + cached.ReadFile("/some/path/file.txt") + assert.Equal(t, 4, len(underlying.ReadFileCalls())) + + cached.ReadFile("/some/path/file.txt") + assert.Equal(t, 5, len(underlying.ReadFileCalls())) + + cached.Enable() + cached.ReadFile("/some/path/file.txt") + assert.Equal(t, 6, len(underlying.ReadFileCalls())) + + cached.ReadFile("/some/path/file.txt") + assert.Equal(t, 7, len(underlying.ReadFileCalls())) } func TestUseCaseSensitiveFileNames(t *testing.T) { @@ -148,6 +232,20 @@ func TestUseCaseSensitiveFileNames(t *testing.T) { cached.ClearCache() cached.UseCaseSensitiveFileNames() assert.Equal(t, 3, len(underlying.UseCaseSensitiveFileNamesCalls())) + + cached.DisableAndClearCache() + cached.UseCaseSensitiveFileNames() + assert.Equal(t, 4, len(underlying.UseCaseSensitiveFileNamesCalls())) + + cached.UseCaseSensitiveFileNames() + assert.Equal(t, 5, len(underlying.UseCaseSensitiveFileNamesCalls())) + + cached.Enable() + cached.UseCaseSensitiveFileNames() + assert.Equal(t, 6, len(underlying.UseCaseSensitiveFileNamesCalls())) + + cached.UseCaseSensitiveFileNames() + assert.Equal(t, 7, len(underlying.UseCaseSensitiveFileNamesCalls())) } func TestWalkDir(t *testing.T) { @@ -169,6 +267,20 @@ func TestWalkDir(t *testing.T) { cached.ClearCache() _ = cached.WalkDir("/some/path", walkFn) assert.Equal(t, 3, len(underlying.WalkDirCalls())) + + cached.DisableAndClearCache() + _ = cached.WalkDir("/some/path", walkFn) + assert.Equal(t, 4, len(underlying.WalkDirCalls())) + + _ = cached.WalkDir("/some/path", walkFn) + assert.Equal(t, 5, len(underlying.WalkDirCalls())) + + cached.Enable() + _ = cached.WalkDir("/some/path", walkFn) + assert.Equal(t, 6, len(underlying.WalkDirCalls())) + + _ = cached.WalkDir("/some/path", walkFn) + assert.Equal(t, 7, len(underlying.WalkDirCalls())) } func TestRemove(t *testing.T) { @@ -186,6 +298,20 @@ func TestRemove(t *testing.T) { cached.ClearCache() _ = cached.Remove("/some/path/file.txt") assert.Equal(t, 3, len(underlying.RemoveCalls())) + + cached.DisableAndClearCache() + _ = cached.Remove("/some/path/file.txt") + assert.Equal(t, 4, len(underlying.RemoveCalls())) + + _ = cached.Remove("/some/path/file.txt") + assert.Equal(t, 5, len(underlying.RemoveCalls())) + + cached.Enable() + _ = cached.Remove("/some/path/file.txt") + assert.Equal(t, 6, len(underlying.RemoveCalls())) + + _ = cached.Remove("/some/path/file.txt") + assert.Equal(t, 7, len(underlying.RemoveCalls())) } func TestWriteFile(t *testing.T) { @@ -208,4 +334,18 @@ func TestWriteFile(t *testing.T) { assert.Equal(t, "/some/path/file.txt", call.Path) assert.Equal(t, "third content", call.Data) assert.Equal(t, false, call.WriteByteOrderMark) + + cached.DisableAndClearCache() + _ = cached.WriteFile("/some/path/file.txt", "fourth content", false) + assert.Equal(t, 4, len(underlying.WriteFileCalls())) + + _ = cached.WriteFile("/some/path/file.txt", "fifth content", true) + assert.Equal(t, 5, len(underlying.WriteFileCalls())) + + cached.Enable() + _ = cached.WriteFile("/some/path/file.txt", "sixth content", false) + assert.Equal(t, 6, len(underlying.WriteFileCalls())) + + _ = cached.WriteFile("/some/path/file.txt", "seventh content", true) + assert.Equal(t, 7, len(underlying.WriteFileCalls())) }