Skip to content

Commit db139cc

Browse files
committed
Handle closed files part of configured projects
1 parent 3c965cb commit db139cc

10 files changed

+328
-28
lines changed

internal/api/api.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ func (api *API) getOrCreateScriptInfo(fileName string, path tspath.Path, scriptK
356356
if !ok {
357357
return nil
358358
}
359-
info = project.NewScriptInfo(fileName, path, scriptKind)
359+
info = project.NewScriptInfo(fileName, path, scriptKind, api.host.FS())
360360
info.SetTextFromDisk(content)
361361
api.scriptInfosMu.Lock()
362362
defer api.scriptInfosMu.Unlock()

internal/project/documentregistry.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ func (r *DocumentRegistry) getDocumentWorker(
9292
if entry, ok := r.documents.Load(key); ok {
9393
// We have an entry for this file. However, it may be for a different version of
9494
// the script snapshot. If so, update it appropriately.
95-
if entry.sourceFile.Version != scriptInfo.version {
95+
if entry.sourceFile.Version != scriptInfo.Version() {
9696
sourceFile := parser.ParseSourceFile(scriptInfo.fileName, scriptInfo.path, scriptInfo.text, scriptTarget, scanner.JSDocParsingModeParseAll)
97-
sourceFile.Version = scriptInfo.version
97+
sourceFile.Version = scriptInfo.Version()
9898
entry.mu.Lock()
9999
defer entry.mu.Unlock()
100100
entry.sourceFile = sourceFile
@@ -104,7 +104,7 @@ func (r *DocumentRegistry) getDocumentWorker(
104104
} else {
105105
// Have never seen this file with these settings. Create a new source file for it.
106106
sourceFile := parser.ParseSourceFile(scriptInfo.fileName, scriptInfo.path, scriptInfo.text, scriptTarget, scanner.JSDocParsingModeParseAll)
107-
sourceFile.Version = scriptInfo.version
107+
sourceFile.Version = scriptInfo.Version()
108108
entry, _ := r.documents.LoadOrStore(key, &registryEntry{
109109
sourceFile: sourceFile,
110110
refCount: 0,

internal/project/project.go

+27-12
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import (
1919
)
2020

2121
//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+
)
2226

2327
var projectNamer = &namer{}
2428

@@ -62,18 +66,20 @@ type Project struct {
6266
deferredClose bool
6367
pendingConfigReload bool
6468

65-
currentDirectory string
69+
comparePathsOptions tspath.ComparePathsOptions
70+
currentDirectory string
6671
// Inferred projects only
6772
rootPath tspath.Path
6873

6974
configFileName string
7075
configFilePath tspath.Path
7176
// rootFileNames was a map from Path to { NormalizedPath, ScriptInfo? } in the original code.
7277
// But the ProjectService owns script infos, so it's not clear why there was an extra pointer.
73-
rootFileNames *collections.OrderedMap[tspath.Path, string]
74-
compilerOptions *core.CompilerOptions
75-
languageService *ls.LanguageService
76-
program *compiler.Program
78+
rootFileNames *collections.OrderedMap[tspath.Path, string]
79+
compilerOptions *core.CompilerOptions
80+
parsedCommandLine *tsoptions.ParsedCommandLine
81+
languageService *ls.LanguageService
82+
program *compiler.Program
7783

7884
watchedGlobs []string
7985
watcherID WatcherHandle
@@ -103,6 +109,10 @@ func NewProject(name string, kind Kind, currentDirectory string, host ProjectHos
103109
currentDirectory: currentDirectory,
104110
rootFileNames: &collections.OrderedMap[tspath.Path, string]{},
105111
}
112+
project.comparePathsOptions = tspath.ComparePathsOptions{
113+
CurrentDirectory: currentDirectory,
114+
UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
115+
}
106116
project.languageService = ls.NewLanguageService(project)
107117
project.markAsDirty()
108118
return project
@@ -213,25 +223,28 @@ func (p *Project) LanguageService() *ls.LanguageService {
213223
}
214224

215225
func (p *Project) getWatchGlobs() []string {
216-
// !!!
217226
if p.kind == KindConfigured {
218-
return []string{
219-
p.configFileName,
227+
wildcardDirectories := p.parsedCommandLine.WildcardDirectories()
228+
result := make([]string, 0, len(wildcardDirectories)+1)
229+
result = append(result, p.configFileName)
230+
for dir, recursive := range wildcardDirectories {
231+
result = append(result, fmt.Sprintf("%s/%s", dir, core.IfElse(recursive, recursiveFileGlobPattern, fileGlobPattern)))
220232
}
233+
return result
221234
}
222235
return nil
223236
}
224237

225238
func (p *Project) updateWatchers() {
226-
watchHost := p.host.Client()
227-
if watchHost == nil {
239+
client := p.host.Client()
240+
if client == nil {
228241
return
229242
}
230243

231244
globs := p.getWatchGlobs()
232245
if !slices.Equal(p.watchedGlobs, globs) {
233246
if p.watcherID != "" {
234-
if err := watchHost.UnwatchFiles(p.watcherID); err != nil {
247+
if err := client.UnwatchFiles(p.watcherID); err != nil {
235248
p.log(fmt.Sprintf("Failed to unwatch files: %v", err))
236249
}
237250
}
@@ -248,7 +261,7 @@ func (p *Project) updateWatchers() {
248261
Kind: &kind,
249262
}
250263
}
251-
if watcherID, err := watchHost.WatchFiles(watchers); err != nil {
264+
if watcherID, err := client.WatchFiles(watchers); err != nil {
252265
p.log(fmt.Sprintf("Failed to watch files: %v", err))
253266
} else {
254267
p.watcherID = watcherID
@@ -284,6 +297,7 @@ func (p *Project) markAsDirty() {
284297
}
285298
}
286299

300+
// updateIfDirty returns true if the project was updated.
287301
func (p *Project) updateIfDirty() bool {
288302
// !!! p.invalidateResolutionsOfFailedLookupLocations()
289303
return p.dirty && p.updateGraph()
@@ -437,6 +451,7 @@ func (p *Project) LoadConfig() error {
437451
}, " ", " ")),
438452
)
439453

454+
p.parsedCommandLine = parsedCommandLine
440455
p.compilerOptions = parsedCommandLine.CompilerOptions()
441456
p.setRootFiles(parsedCommandLine.FileNames())
442457
} else {

internal/project/scriptinfo.go

+20-3
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ type ScriptInfo struct {
2727
deferredDelete bool
2828

2929
containingProjects []*Project
30+
31+
fs vfs.FS
3032
}
3133

32-
func NewScriptInfo(fileName string, path tspath.Path, scriptKind core.ScriptKind) *ScriptInfo {
34+
func NewScriptInfo(fileName string, path tspath.Path, scriptKind core.ScriptKind, fs vfs.FS) *ScriptInfo {
3335
isDynamic := isDynamicFileName(fileName)
3436
realpath := core.IfElse(isDynamic, path, "")
3537
return &ScriptInfo{
@@ -38,6 +40,7 @@ func NewScriptInfo(fileName string, path tspath.Path, scriptKind core.ScriptKind
3840
realpath: realpath,
3941
isDynamic: isDynamic,
4042
scriptKind: scriptKind,
43+
fs: fs,
4144
}
4245
}
4346

@@ -51,15 +54,29 @@ func (s *ScriptInfo) Path() tspath.Path {
5154

5255
func (s *ScriptInfo) LineMap() *ls.LineMap {
5356
if s.lineMap == nil {
54-
s.lineMap = ls.ComputeLineStarts(s.text)
57+
s.lineMap = ls.ComputeLineStarts(s.Text())
5558
}
5659
return s.lineMap
5760
}
5861

5962
func (s *ScriptInfo) Text() string {
63+
s.reloadIfNeeded()
6064
return s.text
6165
}
6266

67+
func (s *ScriptInfo) Version() int {
68+
s.reloadIfNeeded()
69+
return s.version
70+
}
71+
72+
func (s *ScriptInfo) reloadIfNeeded() {
73+
if s.pendingReloadFromDisk {
74+
if newText, ok := s.fs.ReadFile(s.fileName); ok {
75+
s.SetTextFromDisk(newText)
76+
}
77+
}
78+
}
79+
6380
func (s *ScriptInfo) open(newText string) {
6481
s.isOpen = true
6582
s.pendingReloadFromDisk = false
@@ -133,7 +150,7 @@ func (s *ScriptInfo) isOrphan() bool {
133150
}
134151

135152
func (s *ScriptInfo) editContent(change ls.TextChange) {
136-
s.setText(change.ApplyTo(s.text))
153+
s.setText(change.ApplyTo(s.Text()))
137154
s.markContainingProjectsAsDirty()
138155
}
139156

internal/project/service.go

+22-4
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,31 @@ func (s *Service) OnWatchedFilesChanged(changes []lsproto.FileEvent) error {
237237
fileName := ls.DocumentURIToFileName(change.Uri)
238238
path := s.toPath(fileName)
239239
if project, ok := s.configuredProjects[path]; ok {
240+
// tsconfig of project
240241
if err := s.onConfigFileChanged(project, change.Type); err != nil {
241242
return fmt.Errorf("error handling config file change: %w", err)
242243
}
244+
} else if _, ok := s.openFiles[path]; ok {
245+
// open file
246+
continue
247+
} else if info := s.GetScriptInfoByPath(path); info != nil {
248+
// closed existing file
249+
if change.Type == lsproto.FileChangeTypeDeleted {
250+
s.handleDeletedFile(info, true /*deferredDelete*/)
251+
} else {
252+
info.deferredDelete = false
253+
info.delayReloadNonMixedContentFile()
254+
// !!! s.delayUpdateProjectGraphs(info.containingProjects, false /*clearSourceMapperCache*/)
255+
// !!! s.handleSourceMapProjects(info)
256+
}
243257
} else {
244-
// !!!
258+
// must be wildcard watcher?
259+
}
260+
}
261+
262+
for _, project := range s.configuredProjects {
263+
if project.updateIfDirty() {
264+
s.publishDiagnosticsForOpenFiles(project)
245265
}
246266
}
247267
return nil
@@ -262,8 +282,6 @@ func (s *Service) onConfigFileChanged(project *Project, changeKind lsproto.FileC
262282
if !project.deferredClose {
263283
project.pendingConfigReload = true
264284
project.markAsDirty()
265-
project.updateIfDirty()
266-
return s.publishDiagnosticsForOpenFiles(project)
267285
}
268286
return nil
269287
}
@@ -434,7 +452,7 @@ func (s *Service) getOrCreateScriptInfoWorker(fileName string, path tspath.Path,
434452
}
435453
}
436454

437-
info = NewScriptInfo(fileName, path, scriptKind)
455+
info = NewScriptInfo(fileName, path, scriptKind, s.host.FS())
438456
if fromDisk {
439457
info.SetTextFromDisk(fileContent)
440458
}

internal/tsoptions/commandlineparser.go

+6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/microsoft/typescript-go/internal/compiler/diagnostics"
1010
"github.com/microsoft/typescript-go/internal/core"
1111
"github.com/microsoft/typescript-go/internal/stringutil"
12+
"github.com/microsoft/typescript-go/internal/tspath"
1213
"github.com/microsoft/typescript-go/internal/vfs"
1314
)
1415

@@ -58,6 +59,11 @@ func ParseCommandLine(
5859
Errors: parser.errors,
5960
Raw: parser.options, // !!! keep optionsBase incase needed later. todo: figure out if this is still needed
6061
CompileOnSave: nil,
62+
63+
comparePathsOptions: tspath.ComparePathsOptions{
64+
UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
65+
CurrentDirectory: host.GetCurrentDirectory(),
66+
},
6167
}
6268
}
6369

internal/tsoptions/parsedcommandline.go

+52-5
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,38 @@ package tsoptions
22

33
import (
44
"slices"
5+
"sync"
56

67
"github.com/microsoft/typescript-go/internal/ast"
78
"github.com/microsoft/typescript-go/internal/core"
9+
"github.com/microsoft/typescript-go/internal/tspath"
810
)
911

1012
type ParsedCommandLine struct {
1113
ParsedConfig *core.ParsedOptions `json:"parsedConfig"`
1214

13-
ConfigFile *TsConfigSourceFile `json:"configFile"` // TsConfigSourceFile, used in Program and ExecuteCommandLine
14-
Errors []*ast.Diagnostic `json:"errors"`
15-
Raw any `json:"raw"`
16-
// WildcardDirectories map[string]watchDirectoryFlags
17-
CompileOnSave *bool `json:"compileOnSave"`
15+
ConfigFile *TsConfigSourceFile `json:"configFile"` // TsConfigSourceFile, used in Program and ExecuteCommandLine
16+
Errors []*ast.Diagnostic `json:"errors"`
17+
Raw any `json:"raw"`
18+
CompileOnSave *bool `json:"compileOnSave"`
1819
// TypeAquisition *core.TypeAcquisition
20+
21+
comparePathsOptions tspath.ComparePathsOptions
22+
wildcardDirectoriesOnce sync.Once
23+
wildcardDirectories map[string]bool
24+
}
25+
26+
// WildcardDirectories returns the cached wildcard directories, initializing them if needed
27+
func (p *ParsedCommandLine) WildcardDirectories() map[string]bool {
28+
p.wildcardDirectoriesOnce.Do(func() {
29+
p.wildcardDirectories = getWildcardDirectories(
30+
p.ConfigFile.configFileSpecs.validatedIncludeSpecs,
31+
p.ConfigFile.configFileSpecs.validatedExcludeSpecs,
32+
p.comparePathsOptions,
33+
)
34+
})
35+
36+
return p.wildcardDirectories
1937
}
2038

2139
func (p *ParsedCommandLine) SetParsedOptions(o *core.ParsedOptions) {
@@ -45,3 +63,32 @@ func (p *ParsedCommandLine) GetConfigFileParsingDiagnostics() []*ast.Diagnostic
4563
}
4664
return p.Errors
4765
}
66+
67+
func (p *ParsedCommandLine) MatchesFileName(fileName string, comparePathsOptions tspath.ComparePathsOptions) bool {
68+
path := tspath.ToPath(fileName, comparePathsOptions.CurrentDirectory, comparePathsOptions.UseCaseSensitiveFileNames)
69+
if slices.ContainsFunc(p.FileNames(), func(f string) bool {
70+
return path == tspath.ToPath(f, comparePathsOptions.CurrentDirectory, comparePathsOptions.UseCaseSensitiveFileNames)
71+
}) {
72+
return true
73+
}
74+
75+
if p.ConfigFile == nil {
76+
return false
77+
}
78+
79+
if slices.ContainsFunc(p.ConfigFile.configFileSpecs.validatedFilesSpec, func(f string) bool {
80+
return path == tspath.ToPath(f, comparePathsOptions.CurrentDirectory, comparePathsOptions.UseCaseSensitiveFileNames)
81+
}) {
82+
return true
83+
}
84+
85+
if len(p.ConfigFile.configFileSpecs.validatedIncludeSpecs) == 0 {
86+
return false
87+
}
88+
89+
if p.ConfigFile.configFileSpecs.matchesExclude(fileName, comparePathsOptions) {
90+
return false
91+
}
92+
93+
return p.ConfigFile.configFileSpecs.matchesInclude(fileName, comparePathsOptions)
94+
}

0 commit comments

Comments
 (0)