@@ -2,6 +2,7 @@ package project
2
2
3
3
import (
4
4
"fmt"
5
+ "maps"
5
6
"strings"
6
7
"sync"
7
8
@@ -19,10 +20,7 @@ import (
19
20
)
20
21
21
22
//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 = "-----------------------------------------------"
26
24
27
25
var projectNamer = & namer {}
28
26
@@ -81,15 +79,21 @@ type Project struct {
81
79
languageService * ls.LanguageService
82
80
program * compiler.Program
83
81
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 ]
86
86
}
87
87
88
88
func NewConfiguredProject (configFileName string , configFilePath tspath.Path , host ProjectHost ) * Project {
89
89
project := NewProject (configFileName , KindConfigured , tspath .GetDirectoryPath (configFileName ), host )
90
90
project .configFileName = configFileName
91
91
project .configFilePath = configFilePath
92
92
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
+ }
93
97
return project
94
98
}
95
99
@@ -113,6 +117,15 @@ func NewProject(name string, kind Kind, currentDirectory string, host ProjectHos
113
117
CurrentDirectory : currentDirectory ,
114
118
UseCaseSensitiveFileNames : host .FS ().UseCaseSensitiveFileNames (),
115
119
}
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
+ }
116
129
project .languageService = ls .NewLanguageService (project )
117
130
project .markAsDirty ()
118
131
return project
@@ -222,7 +235,7 @@ func (p *Project) LanguageService() *ls.LanguageService {
222
235
return p .languageService
223
236
}
224
237
225
- func (p * Project ) getWatchGlobs () []string {
238
+ func (p * Project ) getRootFileWatchGlobs () []string {
226
239
if p .kind == KindConfigured {
227
240
wildcardDirectories := p .parsedCommandLine .WildcardDirectories ()
228
241
result := make ([]string , 0 , len (wildcardDirectories )+ 1 )
@@ -235,48 +248,78 @@ func (p *Project) getWatchGlobs() []string {
235
248
return nil
236
249
}
237
250
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
+
238
273
func (p * Project ) updateWatchers () {
239
274
client := p .host .Client ()
240
275
if client == nil {
241
276
return
242
277
}
243
278
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 ()
251
281
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 ))
270
298
}
271
299
}
272
300
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 )
274
310
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 ) {
276
312
p .pendingConfigReload = true
277
313
p .markAsDirty ()
314
+ return
278
315
}
279
316
}
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
+ }
280
323
}
281
324
282
325
func (p * Project ) getOrCreateScriptInfoAndAttachToProject (fileName string , scriptKind core.ScriptKind ) * ScriptInfo {
@@ -533,7 +576,7 @@ func (p *Project) print(writeFileNames bool, writeFileExplanation bool, writeFil
533
576
// if writeFileExplanation {}
534
577
}
535
578
}
536
- builder .WriteString ("-----------------------------------------------" )
579
+ builder .WriteString (hr )
537
580
return builder .String ()
538
581
}
539
582
@@ -548,3 +591,19 @@ func (p *Project) logf(format string, args ...interface{}) {
548
591
func (p * Project ) Close () {
549
592
// !!!
550
593
}
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
+ }
0 commit comments