Skip to content

Commit 3f60807

Browse files
committed
Trigger update on failed/affecting locations watch
1 parent 1feadc4 commit 3f60807

File tree

3 files changed

+156
-39
lines changed

3 files changed

+156
-39
lines changed

internal/project/project.go

+94-35
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package project
22

33
import (
44
"fmt"
5+
"maps"
56
"strings"
67
"sync"
78

@@ -19,10 +20,7 @@ import (
1920
)
2021

2122
//go:generate go tool golang.org/x/tools/cmd/stringer -type=Kind -output=project_stringer_generated.go
22-
const (
23-
fileGlobPattern = "*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"
24-
recursiveFileGlobPattern = "**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"
25-
)
23+
const hr = "-----------------------------------------------"
2624

2725
var projectNamer = &namer{}
2826

@@ -81,15 +79,21 @@ type Project struct {
8179
languageService *ls.LanguageService
8280
program *compiler.Program
8381

84-
watchedGlobs []string
85-
watcherID WatcherHandle
82+
// Watchers
83+
rootFilesWatch *watchedFiles[[]string]
84+
failedLookupsWatch *watchedFiles[map[tspath.Path]string]
85+
affectingLocationsWatch *watchedFiles[map[tspath.Path]string]
8686
}
8787

8888
func NewConfiguredProject(configFileName string, configFilePath tspath.Path, host ProjectHost) *Project {
8989
project := NewProject(configFileName, KindConfigured, tspath.GetDirectoryPath(configFileName), host)
9090
project.configFileName = configFileName
9191
project.configFilePath = configFilePath
9292
project.initialLoadPending = true
93+
client := host.Client()
94+
if client != nil {
95+
project.rootFilesWatch = newWatchedFiles(client, lsproto.WatchKindChange|lsproto.WatchKindCreate|lsproto.WatchKindDelete, core.Identity)
96+
}
9397
return project
9498
}
9599

@@ -113,6 +117,15 @@ func NewProject(name string, kind Kind, currentDirectory string, host ProjectHos
113117
CurrentDirectory: currentDirectory,
114118
UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
115119
}
120+
client := host.Client()
121+
if client != nil {
122+
project.failedLookupsWatch = newWatchedFiles(client, lsproto.WatchKindCreate, func(data map[tspath.Path]string) []string {
123+
return slices.Sorted(maps.Values(data))
124+
})
125+
project.affectingLocationsWatch = newWatchedFiles(client, lsproto.WatchKindChange|lsproto.WatchKindCreate|lsproto.WatchKindDelete, func(data map[tspath.Path]string) []string {
126+
return slices.Sorted(maps.Values(data))
127+
})
128+
}
116129
project.languageService = ls.NewLanguageService(project)
117130
project.markAsDirty()
118131
return project
@@ -222,7 +235,7 @@ func (p *Project) LanguageService() *ls.LanguageService {
222235
return p.languageService
223236
}
224237

225-
func (p *Project) getWatchGlobs() []string {
238+
func (p *Project) getRootFileWatchGlobs() []string {
226239
if p.kind == KindConfigured {
227240
wildcardDirectories := p.parsedCommandLine.WildcardDirectories()
228241
result := make([]string, 0, len(wildcardDirectories)+1)
@@ -235,48 +248,78 @@ func (p *Project) getWatchGlobs() []string {
235248
return nil
236249
}
237250

251+
func (p *Project) getModuleResolutionWatchGlobs() (failedLookups map[tspath.Path]string, affectingLocaions map[tspath.Path]string) {
252+
failedLookups = make(map[tspath.Path]string)
253+
affectingLocaions = make(map[tspath.Path]string)
254+
for _, resolvedModulesInFile := range p.program.GetResolvedModules() {
255+
for _, resolvedModule := range resolvedModulesInFile {
256+
for _, failedLookupLocation := range resolvedModule.FailedLookupLocations {
257+
path := p.toPath(failedLookupLocation)
258+
if _, ok := failedLookups[path]; !ok {
259+
failedLookups[path] = failedLookupLocation
260+
}
261+
}
262+
for _, affectingLocation := range resolvedModule.AffectingLocations {
263+
path := p.toPath(affectingLocation)
264+
if _, ok := affectingLocaions[path]; !ok {
265+
affectingLocaions[path] = affectingLocation
266+
}
267+
}
268+
}
269+
}
270+
return failedLookups, affectingLocaions
271+
}
272+
238273
func (p *Project) updateWatchers() {
239274
client := p.host.Client()
240275
if client == nil {
241276
return
242277
}
243278

244-
globs := p.getWatchGlobs()
245-
if !slices.Equal(p.watchedGlobs, globs) {
246-
if p.watcherID != "" {
247-
if err := client.UnwatchFiles(p.watcherID); err != nil {
248-
p.log(fmt.Sprintf("Failed to unwatch files: %v", err))
249-
}
250-
}
279+
rootFileGlobs := p.getRootFileWatchGlobs()
280+
failedLookupGlobs, affectingLocationGlobs := p.getModuleResolutionWatchGlobs()
251281

252-
p.watchedGlobs = globs
253-
if len(globs) > 0 {
254-
watchers := make([]lsproto.FileSystemWatcher, len(globs))
255-
kind := lsproto.WatchKindChange | lsproto.WatchKindDelete | lsproto.WatchKindCreate
256-
for i, glob := range globs {
257-
watchers[i] = lsproto.FileSystemWatcher{
258-
GlobPattern: lsproto.GlobPattern{
259-
Pattern: &glob,
260-
},
261-
Kind: &kind,
262-
}
263-
}
264-
if watcherID, err := client.WatchFiles(watchers); err != nil {
265-
p.log(fmt.Sprintf("Failed to watch files: %v", err))
266-
} else {
267-
p.watcherID = watcherID
268-
}
269-
}
282+
if updated, err := p.rootFilesWatch.update(rootFileGlobs); err != nil {
283+
p.log(fmt.Sprintf("Failed to update root file watch: %v", err))
284+
} else if updated {
285+
p.log("Root file watches updated:\n" + formatFileList(rootFileGlobs, "\t", hr))
286+
}
287+
288+
if updated, err := p.failedLookupsWatch.update(failedLookupGlobs); err != nil {
289+
p.log(fmt.Sprintf("Failed to update failed lookup watch: %v", err))
290+
} else if updated {
291+
p.log("Failed lookup watches updated:\n" + formatFileList(p.failedLookupsWatch.globs, "\t", hr))
292+
}
293+
294+
if updated, err := p.affectingLocationsWatch.update(affectingLocationGlobs); err != nil {
295+
p.log(fmt.Sprintf("Failed to update affecting location watch: %v", err))
296+
} else if updated {
297+
p.log("Affecting location watches updated:\n" + formatFileList(p.affectingLocationsWatch.globs, "\t", hr))
270298
}
271299
}
272300

273-
func (p *Project) onWatchedFileCreated(fileName string) {
301+
// onWatchEventForNilScriptInfo is fired for watch events that are not the
302+
// project tsconfig, and do not have a ScriptInfo for the associated file.
303+
// This could be a case of one of the following:
304+
// - A file is being created that will be added to the project.
305+
// - An affecting location was changed.
306+
// - A file is being created that matches a watch glob, but is not actually
307+
// part of the project, e.g., a .js file in a project without --allowJs.
308+
func (p *Project) onWatchEventForNilScriptInfo(fileName string) {
309+
path := p.toPath(fileName)
274310
if p.kind == KindConfigured {
275-
if p.rootFileNames.Has(p.toPath(fileName)) || p.parsedCommandLine.MatchesFileName(fileName, p.comparePathsOptions) {
311+
if p.rootFileNames.Has(path) || p.parsedCommandLine.MatchesFileName(fileName, p.comparePathsOptions) {
276312
p.pendingConfigReload = true
277313
p.markAsDirty()
314+
return
278315
}
279316
}
317+
318+
if _, ok := p.failedLookupsWatch.data[path]; ok {
319+
p.markAsDirty()
320+
} else if _, ok := p.affectingLocationsWatch.data[path]; ok {
321+
p.markAsDirty()
322+
}
280323
}
281324

282325
func (p *Project) getOrCreateScriptInfoAndAttachToProject(fileName string, scriptKind core.ScriptKind) *ScriptInfo {
@@ -533,7 +576,7 @@ func (p *Project) print(writeFileNames bool, writeFileExplanation bool, writeFil
533576
// if writeFileExplanation {}
534577
}
535578
}
536-
builder.WriteString("-----------------------------------------------")
579+
builder.WriteString(hr)
537580
return builder.String()
538581
}
539582

@@ -548,3 +591,19 @@ func (p *Project) logf(format string, args ...interface{}) {
548591
func (p *Project) Close() {
549592
// !!!
550593
}
594+
595+
func formatFileList(files []string, linePrefix string, groupSuffix string) string {
596+
var builder strings.Builder
597+
length := len(groupSuffix)
598+
for _, file := range files {
599+
length += len(file) + len(linePrefix) + 1
600+
}
601+
builder.Grow(length)
602+
for _, file := range files {
603+
builder.WriteString(linePrefix)
604+
builder.WriteString(file)
605+
builder.WriteRune('\n')
606+
}
607+
builder.WriteString(groupSuffix)
608+
return builder.String()
609+
}

internal/project/service.go

+1-4
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,8 @@ func (s *Service) OnWatchedFilesChanged(changes []lsproto.FileEvent) error {
255255
// !!! s.handleSourceMapProjects(info)
256256
}
257257
} else {
258-
if change.Type != lsproto.FileChangeTypeCreated {
259-
panic("unexpected file change type")
260-
}
261258
for _, project := range s.configuredProjects {
262-
project.onWatchedFileCreated(fileName)
259+
project.onWatchEventForNilScriptInfo(fileName)
263260
}
264261
}
265262
}

internal/project/watch.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package project
2+
3+
import (
4+
"slices"
5+
6+
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
7+
)
8+
9+
const (
10+
fileGlobPattern = "*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}"
11+
recursiveFileGlobPattern = "**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}"
12+
)
13+
14+
type watchedFiles[T any] struct {
15+
client Client
16+
getGlobs func(data T) []string
17+
watchKind lsproto.WatchKind
18+
19+
data T
20+
globs []string
21+
watcherID WatcherHandle
22+
}
23+
24+
func newWatchedFiles[T any](client Client, watchKind lsproto.WatchKind, getGlobs func(data T) []string) *watchedFiles[T] {
25+
return &watchedFiles[T]{
26+
client: client,
27+
watchKind: watchKind,
28+
getGlobs: getGlobs,
29+
}
30+
}
31+
32+
func (w *watchedFiles[T]) update(newData T) (updated bool, err error) {
33+
newGlobs := w.getGlobs(newData)
34+
w.data = newData
35+
if slices.Equal(w.globs, newGlobs) {
36+
return false, nil
37+
}
38+
39+
w.globs = newGlobs
40+
if w.watcherID != "" {
41+
if err := w.client.UnwatchFiles(w.watcherID); err != nil {
42+
return false, err
43+
}
44+
}
45+
46+
watchers := make([]lsproto.FileSystemWatcher, 0, len(newGlobs))
47+
for _, glob := range newGlobs {
48+
watchers = append(watchers, lsproto.FileSystemWatcher{
49+
GlobPattern: lsproto.PatternOrRelativePattern{
50+
Pattern: &glob,
51+
},
52+
Kind: &w.watchKind,
53+
})
54+
}
55+
watcherID, err := w.client.WatchFiles(watchers)
56+
if err != nil {
57+
return false, err
58+
}
59+
w.watcherID = watcherID
60+
return true, nil
61+
}

0 commit comments

Comments
 (0)