Skip to content

Commit 6b117d8

Browse files
authored
Simplify and consolidate LSP watcher registrations (#1733)
1 parent 5a50e1b commit 6b117d8

17 files changed

+771
-654
lines changed

internal/collections/ordered_map.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,11 @@ func DiffOrderedMaps[K comparable, V comparable](m1 *OrderedMap[K, V], m2 *Order
300300
}
301301

302302
func DiffOrderedMapsFunc[K comparable, V any](m1 *OrderedMap[K, V], m2 *OrderedMap[K, V], equalValues func(a, b V) bool, onAdded func(key K, value V), onRemoved func(key K, value V), onModified func(key K, oldValue V, newValue V)) {
303+
for k, v2 := range m2.Entries() {
304+
if _, ok := m1.Get(k); !ok {
305+
onAdded(k, v2)
306+
}
307+
}
303308
for k, v1 := range m1.Entries() {
304309
if v2, ok := m2.Get(k); ok {
305310
if !equalValues(v1, v2) {
@@ -309,10 +314,4 @@ func DiffOrderedMapsFunc[K comparable, V any](m1 *OrderedMap[K, V], m2 *OrderedM
309314
onRemoved(k, v1)
310315
}
311316
}
312-
313-
for k, v2 := range m2.Entries() {
314-
if _, ok := m1.Get(k); !ok {
315-
onAdded(k, v2)
316-
}
317-
}
318317
}

internal/core/core.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,11 @@ func DiffMaps[K comparable, V comparable](m1 map[K]V, m2 map[K]V, onAdded func(K
606606
}
607607

608608
func DiffMapsFunc[K comparable, V any](m1 map[K]V, m2 map[K]V, equalValues func(V, V) bool, onAdded func(K, V), onRemoved func(K, V), onChanged func(K, V, V)) {
609+
for k, v2 := range m2 {
610+
if _, ok := m1[k]; !ok {
611+
onAdded(k, v2)
612+
}
613+
}
609614
for k, v1 := range m1 {
610615
if v2, ok := m2[k]; ok {
611616
if !equalValues(v1, v2) {
@@ -615,12 +620,6 @@ func DiffMapsFunc[K comparable, V any](m1 map[K]V, m2 map[K]V, equalValues func(
615620
onRemoved(k, v1)
616621
}
617622
}
618-
619-
for k, v2 := range m2 {
620-
if _, ok := m1[k]; !ok {
621-
onAdded(k, v2)
622-
}
623-
}
624623
}
625624

626625
// CopyMapInto is maps.Copy, unless dst is nil, in which case it clones and returns src.

internal/lsp/server.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/microsoft/typescript-go/internal/project"
2424
"github.com/microsoft/typescript-go/internal/project/ata"
2525
"github.com/microsoft/typescript-go/internal/project/logging"
26+
"github.com/microsoft/typescript-go/internal/tspath"
2627
"github.com/microsoft/typescript-go/internal/vfs"
2728
"golang.org/x/sync/errgroup"
2829
"golang.org/x/text/language"
@@ -651,9 +652,27 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali
651652
s.watchEnabled = true
652653
}
653654

655+
cwd := s.cwd
656+
if s.initializeParams.Capabilities != nil &&
657+
s.initializeParams.Capabilities.Workspace != nil &&
658+
s.initializeParams.Capabilities.Workspace.WorkspaceFolders != nil &&
659+
ptrIsTrue(s.initializeParams.Capabilities.Workspace.WorkspaceFolders) &&
660+
s.initializeParams.WorkspaceFolders != nil &&
661+
s.initializeParams.WorkspaceFolders.WorkspaceFolders != nil &&
662+
len(*s.initializeParams.WorkspaceFolders.WorkspaceFolders) == 1 {
663+
cwd = lsproto.DocumentUri((*s.initializeParams.WorkspaceFolders.WorkspaceFolders)[0].Uri).FileName()
664+
} else if s.initializeParams.RootUri.DocumentUri != nil {
665+
cwd = s.initializeParams.RootUri.DocumentUri.FileName()
666+
} else if s.initializeParams.RootPath != nil && s.initializeParams.RootPath.String != nil {
667+
cwd = *s.initializeParams.RootPath.String
668+
}
669+
if !tspath.PathIsAbsolute(cwd) {
670+
cwd = s.cwd
671+
}
672+
654673
s.session = project.NewSession(&project.SessionInit{
655674
Options: &project.SessionOptions{
656-
CurrentDirectory: s.cwd,
675+
CurrentDirectory: cwd,
657676
DefaultLibraryPath: s.defaultLibraryPath,
658677
TypingsLocation: s.typingsLocation,
659678
PositionEncoding: s.positionEncoding,

internal/module/resolver.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1733,7 +1733,11 @@ func (r *resolutionState) readPackageJsonPeerDependencies(packageJsonInfo *packa
17331733
r.tracer.write(diagnostics.X_package_json_has_a_peerDependencies_field.Message())
17341734
}
17351735
packageDirectory := r.realPath(packageJsonInfo.PackageDirectory)
1736-
nodeModules := packageDirectory[:strings.LastIndex(packageDirectory, "/node_modules")+len("/node_modules")] + "/"
1736+
nodeModulesIndex := strings.LastIndex(packageDirectory, "/node_modules")
1737+
if nodeModulesIndex == -1 {
1738+
return ""
1739+
}
1740+
nodeModules := packageDirectory[:nodeModulesIndex+len("/node_modules")] + "/"
17371741
builder := strings.Builder{}
17381742
for name := range peerDependencies.Value {
17391743
peerPackageJson := r.getPackageJsonInfo(nodeModules+name /*onlyRecordFailures*/, false)

internal/project/configfileregistry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ type configFileEntry struct {
4141
// when this is set, no other fields will be used.
4242
retainingConfigs map[tspath.Path]struct{}
4343
// rootFilesWatch is a watch for the root files of this config file.
44-
rootFilesWatch *WatchedFiles[[]string]
44+
rootFilesWatch *WatchedFiles[patternsAndIgnored]
4545
}
4646

4747
func newConfigFileEntry(fileName string) *configFileEntry {

internal/project/configfileregistrybuilder.go

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -165,21 +165,61 @@ func (c *configFileRegistryBuilder) updateRootFilesWatch(fileName string, entry
165165
return
166166
}
167167

168-
wildcardGlobs := entry.commandLine.WildcardDirectories()
169-
rootFileGlobs := make([]string, 0, len(wildcardGlobs)+1+len(entry.commandLine.ExtendedSourceFiles()))
170-
rootFileGlobs = append(rootFileGlobs, fileName)
171-
for _, extendedConfig := range entry.commandLine.ExtendedSourceFiles() {
172-
rootFileGlobs = append(rootFileGlobs, extendedConfig)
168+
var ignored map[string]struct{}
169+
var globs []string
170+
var externalDirectories []string
171+
var includeWorkspace bool
172+
var includeTsconfigDir bool
173+
tsconfigDir := tspath.GetDirectoryPath(fileName)
174+
wildcardDirectories := entry.commandLine.WildcardDirectories()
175+
comparePathsOptions := tspath.ComparePathsOptions{
176+
CurrentDirectory: c.sessionOptions.CurrentDirectory,
177+
UseCaseSensitiveFileNames: c.FS().UseCaseSensitiveFileNames(),
173178
}
174-
for dir, recursive := range wildcardGlobs {
175-
rootFileGlobs = append(rootFileGlobs, fmt.Sprintf("%s/%s", tspath.NormalizePath(dir), core.IfElse(recursive, recursiveFileGlobPattern, fileGlobPattern)))
179+
for dir := range wildcardDirectories {
180+
if tspath.ContainsPath(c.sessionOptions.CurrentDirectory, dir, comparePathsOptions) {
181+
includeWorkspace = true
182+
} else if tspath.ContainsPath(tsconfigDir, dir, comparePathsOptions) {
183+
includeTsconfigDir = true
184+
} else {
185+
externalDirectories = append(externalDirectories, dir)
186+
}
176187
}
177188
for _, fileName := range entry.commandLine.LiteralFileNames() {
178-
rootFileGlobs = append(rootFileGlobs, fileName)
189+
if tspath.ContainsPath(c.sessionOptions.CurrentDirectory, fileName, comparePathsOptions) {
190+
includeWorkspace = true
191+
} else if tspath.ContainsPath(tsconfigDir, fileName, comparePathsOptions) {
192+
includeTsconfigDir = true
193+
} else {
194+
externalDirectories = append(externalDirectories, tspath.GetDirectoryPath(fileName))
195+
}
179196
}
180197

181-
slices.Sort(rootFileGlobs)
182-
entry.rootFilesWatch = entry.rootFilesWatch.Clone(rootFileGlobs)
198+
if includeWorkspace {
199+
globs = append(globs, getRecursiveGlobPattern(c.sessionOptions.CurrentDirectory))
200+
}
201+
if includeTsconfigDir {
202+
globs = append(globs, getRecursiveGlobPattern(tsconfigDir))
203+
}
204+
for _, fileName := range entry.commandLine.ExtendedSourceFiles() {
205+
if includeWorkspace && tspath.ContainsPath(c.sessionOptions.CurrentDirectory, fileName, comparePathsOptions) {
206+
continue
207+
}
208+
globs = append(globs, fileName)
209+
}
210+
if len(externalDirectories) > 0 {
211+
commonParents, ignoredExternalDirs := tspath.GetCommonParents(externalDirectories, minWatchLocationDepth, getPathComponentsForWatching, comparePathsOptions)
212+
for _, parent := range commonParents {
213+
globs = append(globs, getRecursiveGlobPattern(parent))
214+
}
215+
ignored = ignoredExternalDirs
216+
}
217+
218+
slices.Sort(globs)
219+
entry.rootFilesWatch = entry.rootFilesWatch.Clone(patternsAndIgnored{
220+
patterns: globs,
221+
ignored: ignored,
222+
})
183223
}
184224

185225
// acquireConfigForProject loads a config file entry from the cache, or parses it if not already
@@ -347,11 +387,8 @@ func (c *configFileRegistryBuilder) DidChangeFiles(summary FileChangeSummary, lo
347387
}
348388
logger.Logf("Checking if any of %d created files match root files for config %s", len(createdFiles), entry.Key())
349389
for _, fileName := range createdFiles {
350-
parsedGlobs := config.rootFilesWatch.ParsedGlobs()
351-
for _, g := range parsedGlobs {
352-
if g.Match(fileName) {
353-
return true
354-
}
390+
if config.commandLine.PossiblyMatchesFileName(fileName) {
391+
return true
355392
}
356393
}
357394
return false

internal/project/project.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ type Project struct {
6969
// The ID of the snapshot that created the program stored in this project.
7070
ProgramLastUpdate uint64
7171

72+
programFilesWatch *WatchedFiles[patternsAndIgnored]
7273
failedLookupsWatch *WatchedFiles[map[tspath.Path]string]
7374
affectingLocationsWatch *WatchedFiles[map[tspath.Path]string]
74-
typingsFilesWatch *WatchedFiles[map[tspath.Path]string]
75-
typingsDirectoryWatch *WatchedFiles[map[tspath.Path]string]
75+
typingsWatch *WatchedFiles[patternsAndIgnored]
7676

7777
checkerPool *checkerPool
7878

@@ -146,26 +146,26 @@ func NewProject(
146146

147147
project.configFilePath = tspath.ToPath(configFileName, currentDirectory, builder.fs.fs.UseCaseSensitiveFileNames())
148148
if builder.sessionOptions.WatchEnabled {
149+
project.programFilesWatch = NewWatchedFiles(
150+
"non-root program files for "+configFileName,
151+
lsproto.WatchKindCreate|lsproto.WatchKindChange|lsproto.WatchKindDelete,
152+
core.Identity,
153+
)
149154
project.failedLookupsWatch = NewWatchedFiles(
150155
"failed lookups for "+configFileName,
151156
lsproto.WatchKindCreate,
152-
createResolutionLookupGlobMapper(project.currentDirectory, builder.fs.fs.UseCaseSensitiveFileNames()),
157+
createResolutionLookupGlobMapper(builder.sessionOptions.CurrentDirectory, builder.sessionOptions.DefaultLibraryPath, project.currentDirectory, builder.fs.fs.UseCaseSensitiveFileNames()),
153158
)
154159
project.affectingLocationsWatch = NewWatchedFiles(
155160
"affecting locations for "+configFileName,
156161
lsproto.WatchKindCreate|lsproto.WatchKindChange|lsproto.WatchKindDelete,
157-
createResolutionLookupGlobMapper(project.currentDirectory, builder.fs.fs.UseCaseSensitiveFileNames()),
162+
createResolutionLookupGlobMapper(builder.sessionOptions.CurrentDirectory, builder.sessionOptions.DefaultLibraryPath, project.currentDirectory, builder.fs.fs.UseCaseSensitiveFileNames()),
158163
)
159164
if builder.sessionOptions.TypingsLocation != "" {
160-
project.typingsFilesWatch = NewWatchedFiles(
165+
project.typingsWatch = NewWatchedFiles(
161166
"typings installer files",
162167
lsproto.WatchKindCreate|lsproto.WatchKindChange|lsproto.WatchKindDelete,
163-
globMapperForTypingsInstaller,
164-
)
165-
project.typingsDirectoryWatch = NewWatchedFiles(
166-
"typings installer directories",
167-
lsproto.WatchKindCreate|lsproto.WatchKindDelete,
168-
globMapperForTypingsInstaller,
168+
core.Identity,
169169
)
170170
}
171171
}
@@ -221,10 +221,10 @@ func (p *Project) Clone() *Project {
221221
ProgramUpdateKind: ProgramUpdateKindNone,
222222
ProgramLastUpdate: p.ProgramLastUpdate,
223223

224+
programFilesWatch: p.programFilesWatch,
224225
failedLookupsWatch: p.failedLookupsWatch,
225226
affectingLocationsWatch: p.affectingLocationsWatch,
226-
typingsFilesWatch: p.typingsFilesWatch,
227-
typingsDirectoryWatch: p.typingsDirectoryWatch,
227+
typingsWatch: p.typingsWatch,
228228

229229
checkerPool: p.checkerPool,
230230

@@ -327,14 +327,19 @@ func (p *Project) CreateProgram() CreateProgramResult {
327327
}
328328
}
329329

330-
func (p *Project) CloneWatchers() (failedLookupsWatch *WatchedFiles[map[tspath.Path]string], affectingLocationsWatch *WatchedFiles[map[tspath.Path]string]) {
330+
func (p *Project) CloneWatchers(workspaceDir string, libDir string) (programFilesWatch *WatchedFiles[patternsAndIgnored], failedLookupsWatch *WatchedFiles[map[tspath.Path]string], affectingLocationsWatch *WatchedFiles[map[tspath.Path]string]) {
331331
failedLookups := make(map[tspath.Path]string)
332332
affectingLocations := make(map[tspath.Path]string)
333+
programFiles := getNonRootFileGlobs(workspaceDir, libDir, p.Program.GetSourceFiles(), p.CommandLine.FileNamesByPath(), tspath.ComparePathsOptions{
334+
UseCaseSensitiveFileNames: p.host.FS().UseCaseSensitiveFileNames(),
335+
CurrentDirectory: p.currentDirectory,
336+
})
333337
extractLookups(p.toPath, failedLookups, affectingLocations, p.Program.GetResolvedModules())
334338
extractLookups(p.toPath, failedLookups, affectingLocations, p.Program.GetResolvedTypeReferenceDirectives())
339+
programFilesWatch = p.programFilesWatch.Clone(programFiles)
335340
failedLookupsWatch = p.failedLookupsWatch.Clone(failedLookups)
336341
affectingLocationsWatch = p.affectingLocationsWatch.Clone(affectingLocations)
337-
return failedLookupsWatch, affectingLocationsWatch
342+
return programFilesWatch, failedLookupsWatch, affectingLocationsWatch
338343
}
339344

340345
func (p *Project) log(msg string) {

internal/project/projectcollectionbuilder.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -343,14 +343,14 @@ func (b *projectCollectionBuilder) DidUpdateATAState(ataChanges map[tspath.Path]
343343
// the set of typings files is actually different.
344344
p.installedTypingsInfo = ataChange.TypingsInfo
345345
p.typingsFiles = ataChange.TypingsFiles
346-
fileWatchGlobs, directoryWatchGlobs := getTypingsLocationsGlobs(
346+
typingsWatchGlobs := getTypingsLocationsGlobs(
347347
ataChange.TypingsFilesToWatch,
348348
b.sessionOptions.TypingsLocation,
349+
b.sessionOptions.CurrentDirectory,
349350
p.currentDirectory,
350351
b.fs.fs.UseCaseSensitiveFileNames(),
351352
)
352-
p.typingsFilesWatch = p.typingsFilesWatch.Clone(fileWatchGlobs)
353-
p.typingsDirectoryWatch = p.typingsDirectoryWatch.Clone(directoryWatchGlobs)
353+
p.typingsWatch = p.typingsWatch.Clone(typingsWatchGlobs)
354354
p.dirty = true
355355
p.dirtyFilePath = ""
356356
},
@@ -535,7 +535,7 @@ func (b *projectCollectionBuilder) findOrCreateDefaultConfiguredProjectWorker(
535535
// For composite projects, we can get an early negative result.
536536
// !!! what about declaration files in node_modules? wouldn't it be better to
537537
// check project inclusion if the project is already loaded?
538-
if !config.MatchesFileName(fileName) {
538+
if _, ok := config.FileNamesByPath()[path]; !ok {
539539
node.logger.Log("Project does not contain file (by composite config inclusion)")
540540
return false, false
541541
}
@@ -793,7 +793,8 @@ func (b *projectCollectionBuilder) updateProgram(entry dirty.Value[*Project], lo
793793
if result.UpdateKind == ProgramUpdateKindNewFiles {
794794
filesChanged = true
795795
if b.sessionOptions.WatchEnabled {
796-
failedLookupsWatch, affectingLocationsWatch := project.CloneWatchers()
796+
programFilesWatch, failedLookupsWatch, affectingLocationsWatch := project.CloneWatchers(b.sessionOptions.CurrentDirectory, b.sessionOptions.DefaultLibraryPath)
797+
project.programFilesWatch = programFilesWatch
797798
project.failedLookupsWatch = failedLookupsWatch
798799
project.affectingLocationsWatch = affectingLocationsWatch
799800
}

internal/project/projectlifetime_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func TestProjectLifetime(t *testing.T) {
7070
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 2)
7171
assert.Assert(t, snapshot.ProjectCollection.ConfiguredProject(tspath.Path("/home/projects/ts/p1/tsconfig.json")) != nil)
7272
assert.Assert(t, snapshot.ProjectCollection.ConfiguredProject(tspath.Path("/home/projects/ts/p2/tsconfig.json")) != nil)
73-
assert.Equal(t, len(utils.Client().WatchFilesCalls()), 2)
73+
assert.Equal(t, len(utils.Client().WatchFilesCalls()), 1)
7474
assert.Assert(t, snapshot.ConfigFileRegistry.GetConfig(tspath.Path("/home/projects/ts/p1/tsconfig.json")) != nil)
7575
assert.Assert(t, snapshot.ConfigFileRegistry.GetConfig(tspath.Path("/home/projects/ts/p2/tsconfig.json")) != nil)
7676

@@ -89,8 +89,8 @@ func TestProjectLifetime(t *testing.T) {
8989
assert.Assert(t, snapshot.ConfigFileRegistry.GetConfig(tspath.Path("/home/projects/ts/p1/tsconfig.json")) == nil)
9090
assert.Assert(t, snapshot.ConfigFileRegistry.GetConfig(tspath.Path("/home/projects/ts/p2/tsconfig.json")) != nil)
9191
assert.Assert(t, snapshot.ConfigFileRegistry.GetConfig(tspath.Path("/home/projects/ts/p3/tsconfig.json")) != nil)
92-
assert.Equal(t, len(utils.Client().WatchFilesCalls()), 3)
93-
assert.Equal(t, len(utils.Client().UnwatchFilesCalls()), 1)
92+
assert.Equal(t, len(utils.Client().WatchFilesCalls()), 1)
93+
assert.Equal(t, len(utils.Client().UnwatchFilesCalls()), 0)
9494

9595
// Close p2 and p3 files, open p1 file again
9696
session.DidCloseFile(context.Background(), uri2)
@@ -105,8 +105,8 @@ func TestProjectLifetime(t *testing.T) {
105105
assert.Assert(t, snapshot.ConfigFileRegistry.GetConfig(tspath.Path("/home/projects/ts/p1/tsconfig.json")) != nil)
106106
assert.Assert(t, snapshot.ConfigFileRegistry.GetConfig(tspath.Path("/home/projects/ts/p2/tsconfig.json")) == nil)
107107
assert.Assert(t, snapshot.ConfigFileRegistry.GetConfig(tspath.Path("/home/projects/ts/p3/tsconfig.json")) == nil)
108-
assert.Equal(t, len(utils.Client().WatchFilesCalls()), 4)
109-
assert.Equal(t, len(utils.Client().UnwatchFilesCalls()), 3)
108+
assert.Equal(t, len(utils.Client().WatchFilesCalls()), 1)
109+
assert.Equal(t, len(utils.Client().UnwatchFilesCalls()), 0)
110110
})
111111

112112
t.Run("unrooted inferred projects", func(t *testing.T) {

0 commit comments

Comments
 (0)