Skip to content

Commit 9bc5e3f

Browse files
authored
Cache FS in LS (#996)
1 parent bba6379 commit 9bc5e3f

File tree

7 files changed

+319
-77
lines changed

7 files changed

+319
-77
lines changed

internal/api/api.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ func (api *API) Log(s string) {
115115
api.options.Logger.Info(s)
116116
}
117117

118+
// Log implements ProjectHost.
119+
func (api *API) Trace(s string) {
120+
api.options.Logger.Info(s)
121+
}
122+
118123
// NewLine implements ProjectHost.
119124
func (api *API) NewLine() string {
120125
return api.host.NewLine()

internal/project/project.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import (
1515
"github.com/microsoft/typescript-go/internal/core"
1616
"github.com/microsoft/typescript-go/internal/ls"
1717
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
18+
"github.com/microsoft/typescript-go/internal/module"
1819
"github.com/microsoft/typescript-go/internal/tsoptions"
1920
"github.com/microsoft/typescript-go/internal/tspath"
2021
"github.com/microsoft/typescript-go/internal/vfs"
22+
"github.com/microsoft/typescript-go/internal/vfs/cachedvfs"
2123
)
2224

2325
//go:generate go tool golang.org/x/tools/cmd/stringer -type=Kind -output=project_stringer_generated.go
@@ -72,6 +74,7 @@ const (
7274

7375
type ProjectHost interface {
7476
tsoptions.ParseConfigHost
77+
module.ResolutionHost
7578
NewLine() string
7679
DefaultLibraryPath() string
7780
TypingsInstaller() *TypingsInstaller
@@ -120,7 +123,7 @@ func typeAcquisitionChanged(opt1 *core.TypeAcquisition, opt2 *core.TypeAcquisiti
120123
var _ compiler.CompilerHost = (*Project)(nil)
121124

122125
type Project struct {
123-
host ProjectHost
126+
host *projectHostWithCachedFS
124127

125128
name string
126129
kind Kind
@@ -187,9 +190,11 @@ func NewInferredProject(compilerOptions *core.CompilerOptions, currentDirectory
187190
}
188191

189192
func NewProject(name string, kind Kind, currentDirectory string, host ProjectHost) *Project {
193+
cachedHost := newProjectHostWithCachedFS(host)
194+
190195
host.Log(fmt.Sprintf("Creating %sProject: %s, currentDirectory: %s", kind.String(), name, currentDirectory))
191196
project := &Project{
192-
host: host,
197+
host: cachedHost,
193198
name: name,
194199
kind: kind,
195200
currentDirectory: currentDirectory,
@@ -198,11 +203,11 @@ func NewProject(name string, kind Kind, currentDirectory string, host ProjectHos
198203
}
199204
project.comparePathsOptions = tspath.ComparePathsOptions{
200205
CurrentDirectory: currentDirectory,
201-
UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
206+
UseCaseSensitiveFileNames: project.host.FS().UseCaseSensitiveFileNames(),
202207
}
203-
client := host.Client()
204-
if host.IsWatchEnabled() && client != nil {
205-
globMapper := createResolutionLookupGlobMapper(host)
208+
client := project.host.Client()
209+
if project.host.IsWatchEnabled() && client != nil {
210+
globMapper := createResolutionLookupGlobMapper(project.host)
206211
project.failedLookupsWatch = newWatchedFiles(project, lsproto.WatchKindCreate, globMapper, "failed lookup")
207212
project.affectingLocationsWatch = newWatchedFiles(project, lsproto.WatchKindChange|lsproto.WatchKindCreate|lsproto.WatchKindDelete, globMapper, "affecting location")
208213
project.typingsFilesWatch = newWatchedFiles(project, lsproto.WatchKindChange|lsproto.WatchKindCreate|lsproto.WatchKindDelete, globMapperForTypingsInstaller, "typings installer files")
@@ -212,6 +217,24 @@ func NewProject(name string, kind Kind, currentDirectory string, host ProjectHos
212217
return project
213218
}
214219

220+
type projectHostWithCachedFS struct {
221+
ProjectHost
222+
fs *cachedvfs.FS
223+
}
224+
225+
func newProjectHostWithCachedFS(host ProjectHost) *projectHostWithCachedFS {
226+
newHost := &projectHostWithCachedFS{
227+
ProjectHost: host,
228+
fs: cachedvfs.From(host.FS()),
229+
}
230+
newHost.fs.DisableAndClearCache()
231+
return newHost
232+
}
233+
234+
func (p *projectHostWithCachedFS) FS() vfs.FS {
235+
return p.fs
236+
}
237+
215238
// FS implements compiler.CompilerHost.
216239
func (p *Project) FS() vfs.FS {
217240
return p.host.FS()
@@ -265,7 +288,7 @@ func (p *Project) NewLine() string {
265288

266289
// Trace implements compiler.CompilerHost.
267290
func (p *Project) Trace(msg string) {
268-
p.Log(msg)
291+
p.host.Log(msg)
269292
}
270293

271294
// GetDefaultLibraryPath implements compiler.CompilerHost.
@@ -480,6 +503,9 @@ func (p *Project) updateGraph() (*compiler.Program, bool) {
480503
return p.program, false
481504
}
482505

506+
p.host.fs.Enable()
507+
defer p.host.fs.DisableAndClearCache()
508+
483509
start := time.Now()
484510
p.Log("Starting updateGraph: Project: " + p.name)
485511
oldProgram := p.program

internal/project/service.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ func (s *Service) Log(msg string) {
106106
s.options.Logger.Info(msg)
107107
}
108108

109+
func (s *Service) Trace(msg string) {
110+
s.Log(msg)
111+
}
112+
109113
func (s *Service) HasLevel(level LogLevel) bool {
110114
return s.options.Logger.HasLevel(level)
111115
}

internal/project/service_test.go

Lines changed: 70 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ func TestService(t *testing.T) {
168168
service, _ := projecttestutil.Setup(files, nil)
169169
service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "")
170170
assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/y.ts") == nil)
171+
// Avoid using initial file set after this point
172+
files = nil //nolint:ineffassign
171173

172174
err := service.ChangeFile(
173175
lsproto.VersionedTextDocumentIdentifier{
@@ -214,6 +216,8 @@ func TestService(t *testing.T) {
214216
_, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts")
215217
programBefore := project.GetProgram()
216218
assert.Equal(t, len(programBefore.GetSourceFiles()), 2)
219+
// Avoid using initial file set after this point
220+
files = nil //nolint:ineffassign
217221

218222
err := service.ChangeFile(
219223
lsproto.VersionedTextDocumentIdentifier{
@@ -242,15 +246,16 @@ func TestService(t *testing.T) {
242246
)
243247
assert.NilError(t, err)
244248

245-
files["/home/projects/TS/p1/tsconfig.json"] = `{
249+
err = host.FS().WriteFile("/home/projects/TS/p1/tsconfig.json", `{
246250
"compilerOptions": {
247251
"noLib": true,
248252
"module": "nodenext",
249253
"strict": true,
250254
},
251255
"include": ["./**/*"]
252-
}`
253-
host.ReplaceFS(files)
256+
}`, false)
257+
assert.NilError(t, err)
258+
254259
err = service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{
255260
{
256261
Type: lsproto.FileChangeTypeChanged,
@@ -270,23 +275,25 @@ func TestService(t *testing.T) {
270275
t.Parallel()
271276
t.Run("delete a file, close it, recreate it", func(t *testing.T) {
272277
t.Parallel()
273-
service, host := projecttestutil.Setup(defaultFiles, nil)
274-
service.OpenFile("/home/projects/TS/p1/src/x.ts", defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "")
275-
service.OpenFile("/home/projects/TS/p1/src/index.ts", defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "")
278+
files := maps.Clone(defaultFiles)
279+
service, host := projecttestutil.Setup(files, nil)
280+
service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "")
281+
service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "")
276282
assert.Equal(t, service.SourceFileCount(), 2)
283+
// Avoid using initial file set after this point
284+
files = nil //nolint:ineffassign
277285

278-
files := maps.Clone(defaultFiles)
279-
delete(files, "/home/projects/TS/p1/src/x.ts")
280-
host.ReplaceFS(files)
286+
assert.NilError(t, host.FS().Remove("/home/projects/TS/p1/src/x.ts"))
281287

282288
service.CloseFile("/home/projects/TS/p1/src/x.ts")
283289
assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts") == nil)
284290
assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil)
285291
assert.Equal(t, service.SourceFileCount(), 1)
286292

287-
files["/home/projects/TS/p1/src/x.ts"] = ``
288-
host.ReplaceFS(files)
289-
service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "")
293+
err := host.FS().WriteFile("/home/projects/TS/p1/src/x.ts", "", false)
294+
assert.NilError(t, err)
295+
296+
service.OpenFile("/home/projects/TS/p1/src/x.ts", "", core.ScriptKindTS, "")
290297
assert.Equal(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts").Text(), "")
291298
assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil)
292299
assert.Equal(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "")
@@ -300,19 +307,22 @@ func TestService(t *testing.T) {
300307
files := maps.Clone(defaultFiles)
301308
delete(files, "/home/projects/TS/p1/tsconfig.json")
302309
service, host := projecttestutil.Setup(files, nil)
303-
service.OpenFile("/home/projects/TS/p1/src/x.ts", defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "")
304-
service.OpenFile("/home/projects/TS/p1/src/index.ts", defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "")
310+
service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "")
311+
service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "")
312+
// Avoid using initial file set after this point
313+
files = nil //nolint:ineffassign
305314

306-
delete(files, "/home/projects/TS/p1/src/x.ts")
307-
host.ReplaceFS(files)
315+
err := host.FS().Remove("/home/projects/TS/p1/src/x.ts")
316+
assert.NilError(t, err)
308317

309318
service.CloseFile("/home/projects/TS/p1/src/x.ts")
310319
assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts") == nil)
311320
assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil)
312321

313-
files["/home/projects/TS/p1/src/x.ts"] = ``
314-
host.ReplaceFS(files)
315-
service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "")
322+
err = host.FS().WriteFile("/home/projects/TS/p1/src/x.ts", "", false)
323+
assert.NilError(t, err)
324+
325+
service.OpenFile("/home/projects/TS/p1/src/x.ts", "", core.ScriptKindTS, "")
316326
assert.Equal(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts").Text(), "")
317327
assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil)
318328
assert.Equal(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts").Text(), "")
@@ -338,6 +348,8 @@ func TestService(t *testing.T) {
338348
service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "")
339349
service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS, "")
340350
assert.Equal(t, len(service.Projects()), 2)
351+
// Avoid using initial file set after this point
352+
files = nil //nolint:ineffassign
341353
_, p1 := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts")
342354
_, p2 := service.EnsureDefaultProjectForFile("/home/projects/TS/p2/src/index.ts")
343355
assert.Equal(
@@ -361,6 +373,8 @@ func TestService(t *testing.T) {
361373
service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "")
362374
service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS, "")
363375
assert.Equal(t, len(service.Projects()), 2)
376+
// Avoid using initial file set after this point
377+
files = nil //nolint:ineffassign
364378
_, p1 := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts")
365379
_, p2 := service.EnsureDefaultProjectForFile("/home/projects/TS/p2/src/index.ts")
366380
x1 := p1.GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts")
@@ -375,15 +389,18 @@ func TestService(t *testing.T) {
375389

376390
t.Run("change open file", func(t *testing.T) {
377391
t.Parallel()
378-
service, host := projecttestutil.Setup(defaultFiles, nil)
379-
service.OpenFile("/home/projects/TS/p1/src/x.ts", defaultFiles["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "")
380-
service.OpenFile("/home/projects/TS/p1/src/index.ts", defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "")
392+
files := maps.Clone(defaultFiles)
393+
service, host := projecttestutil.Setup(files, nil)
394+
service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"].(string), core.ScriptKindTS, "")
395+
service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "")
381396
_, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts")
382397
programBefore := project.GetProgram()
398+
// Avoid using initial file set after this point
399+
files = nil //nolint:ineffassign
400+
401+
err := host.FS().WriteFile("/home/projects/TS/p1/src/x.ts", `export const x = 2;`, false)
402+
assert.NilError(t, err)
383403

384-
files := maps.Clone(defaultFiles)
385-
files["/home/projects/TS/p1/src/x.ts"] = `export const x = 2;`
386-
host.ReplaceFS(files)
387404
assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{
388405
{
389406
Type: lsproto.FileChangeTypeChanged,
@@ -396,14 +413,17 @@ func TestService(t *testing.T) {
396413

397414
t.Run("change closed program file", func(t *testing.T) {
398415
t.Parallel()
399-
service, host := projecttestutil.Setup(defaultFiles, nil)
400-
service.OpenFile("/home/projects/TS/p1/src/index.ts", defaultFiles["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "")
416+
files := maps.Clone(defaultFiles)
417+
service, host := projecttestutil.Setup(files, nil)
418+
service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"].(string), core.ScriptKindTS, "")
401419
_, project := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/index.ts")
402420
programBefore := project.GetProgram()
421+
// Avoid using initial file set after this point
422+
files = nil //nolint:ineffassign
423+
424+
err := host.FS().WriteFile("/home/projects/TS/p1/src/x.ts", `export const x = 2;`, false)
425+
assert.NilError(t, err)
403426

404-
files := maps.Clone(defaultFiles)
405-
files["/home/projects/TS/p1/src/x.ts"] = `export const x = 2;`
406-
host.ReplaceFS(files)
407427
assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{
408428
{
409429
Type: lsproto.FileChangeTypeChanged,
@@ -435,14 +455,14 @@ func TestService(t *testing.T) {
435455
program := project.GetProgram()
436456
assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0)
437457

438-
filesCopy := maps.Clone(files)
439-
filesCopy["/home/projects/TS/p1/tsconfig.json"] = `{
458+
err := host.FS().WriteFile("/home/projects/TS/p1/tsconfig.json", `{
440459
"compilerOptions": {
441460
"noLib": false,
442461
"strict": true
443462
}
444-
}`
445-
host.ReplaceFS(filesCopy)
463+
}`, false)
464+
assert.NilError(t, err)
465+
446466
assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{
447467
{
448468
Type: lsproto.FileChangeTypeChanged,
@@ -472,9 +492,9 @@ func TestService(t *testing.T) {
472492
program := project.GetProgram()
473493
assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 0)
474494

475-
filesCopy := maps.Clone(files)
476-
delete(filesCopy, "/home/projects/TS/p1/src/x.ts")
477-
host.ReplaceFS(filesCopy)
495+
err := host.FS().Remove("/home/projects/TS/p1/src/x.ts")
496+
assert.NilError(t, err)
497+
478498
assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{
479499
{
480500
Type: lsproto.FileChangeTypeDeleted,
@@ -505,9 +525,9 @@ func TestService(t *testing.T) {
505525
program := project.GetProgram()
506526
assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/x.ts"))), 0)
507527

508-
filesCopy := maps.Clone(files)
509-
delete(filesCopy, "/home/projects/TS/p1/src/index.ts")
510-
host.ReplaceFS(filesCopy)
528+
err := host.FS().Remove("/home/projects/TS/p1/src/index.ts")
529+
assert.NilError(t, err)
530+
511531
assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{
512532
{
513533
Type: lsproto.FileChangeTypeDeleted,
@@ -561,9 +581,9 @@ func TestService(t *testing.T) {
561581
})
562582

563583
// Add the missing file
564-
filesCopy := maps.Clone(files)
565-
filesCopy["/home/projects/TS/p1/src/y.ts"] = `export const y = 1;`
566-
host.ReplaceFS(filesCopy)
584+
err := host.FS().WriteFile("/home/projects/TS/p1/src/y.ts", `export const y = 1;`, false)
585+
assert.NilError(t, err)
586+
567587
assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{
568588
{
569589
Type: lsproto.FileChangeTypeCreated,
@@ -602,9 +622,9 @@ func TestService(t *testing.T) {
602622
}))
603623

604624
// Add a new file through failed lookup watch
605-
filesCopy := maps.Clone(files)
606-
filesCopy["/home/projects/TS/p1/src/z.ts"] = `export const z = 1;`
607-
host.ReplaceFS(filesCopy)
625+
err := host.FS().WriteFile("/home/projects/TS/p1/src/z.ts", `export const z = 1;`, false)
626+
assert.NilError(t, err)
627+
608628
assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{
609629
{
610630
Type: lsproto.FileChangeTypeCreated,
@@ -638,9 +658,10 @@ func TestService(t *testing.T) {
638658
assert.Equal(t, len(program.GetSemanticDiagnostics(projecttestutil.WithRequestID(t.Context()), program.GetSourceFile("/home/projects/TS/p1/src/index.ts"))), 1)
639659

640660
// Add a new file through wildcard watch
641-
filesCopy := maps.Clone(files)
642-
filesCopy["/home/projects/TS/p1/src/a.ts"] = `const a = 1;`
643-
host.ReplaceFS(filesCopy)
661+
662+
err := host.FS().WriteFile("/home/projects/TS/p1/src/a.ts", `const a = 1;`, false)
663+
assert.NilError(t, err)
664+
644665
assert.NilError(t, service.OnWatchedFilesChanged(t.Context(), []*lsproto.FileEvent{
645666
{
646667
Type: lsproto.FileChangeTypeCreated,

internal/testutil/projecttestutil/projecttestutil.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,6 @@ func (p *ProjectServiceHost) Client() project.Client {
8383
return p.ClientMock
8484
}
8585

86-
func (p *ProjectServiceHost) ReplaceFS(files map[string]any) {
87-
p.fs = bundled.WrapFS(vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/))
88-
}
89-
9086
var _ project.ServiceHost = (*ProjectServiceHost)(nil)
9187

9288
func Setup(files map[string]any, testOptions *TestTypingsInstaller) (*project.Service, *ProjectServiceHost) {

0 commit comments

Comments
 (0)