Skip to content

Commit 41e38d3

Browse files
committed
Handle the configFile and extended config cache in editor
1 parent a44901d commit 41e38d3

File tree

10 files changed

+365
-143
lines changed

10 files changed

+365
-143
lines changed

internal/api/api.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ type API struct {
2929
host APIHost
3030
options APIOptions
3131

32-
documentRegistry *project.DocumentRegistry
33-
scriptInfosMu sync.RWMutex
34-
scriptInfos map[tspath.Path]*project.ScriptInfo
32+
documentRegistry *project.DocumentRegistry
33+
scriptInfosMu sync.RWMutex
34+
scriptInfos map[tspath.Path]*project.ScriptInfo
35+
configFileRegistry *project.ConfigFileRegistry
3536

3637
projects handleMap[project.Project]
3738
filesMu sync.Mutex
@@ -65,6 +66,9 @@ func NewAPI(host APIHost, options APIOptions) *API {
6566
},
6667
},
6768
}
69+
api.configFileRegistry = &project.ConfigFileRegistry{
70+
Host: api,
71+
}
6872
return api
6973
}
7074

@@ -83,9 +87,9 @@ func (api *API) DocumentRegistry() *project.DocumentRegistry {
8387
return api.documentRegistry
8488
}
8589

86-
func (api *API) GetResolvedProjectReference(fileName string, path tspath.Path) *tsoptions.ParsedCommandLine {
87-
commandLine, _ := tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, nil, api.host, nil)
88-
return commandLine
90+
// ConfigFileRegistry implements ProjectHost.
91+
func (api *API) ConfigFileRegistry() *project.ConfigFileRegistry {
92+
return api.configFileRegistry
8993
}
9094

9195
// FS implements ProjectHost.

internal/compiler/program.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ func (p *Program) GetSourceAndProjectReference(path tspath.Path) *tsoptions.Sour
118118
return p.projectReferenceFileMapper.getSourceAndProjectReference(path)
119119
}
120120

121+
func (p *Program) GetResolvedProjectReferenceFor(path tspath.Path) (*tsoptions.ParsedCommandLine, bool) {
122+
return p.projectReferenceFileMapper.getResolvedReferenceFor(path)
123+
}
124+
125+
func (p *Program) ForEachResolvedProjectReference(
126+
fn func(path tspath.Path, config *tsoptions.ParsedCommandLine) bool,
127+
) {
128+
p.projectReferenceFileMapper.forEachResolvedProjectReference(fn)
129+
}
130+
121131
// UseCaseSensitiveFileNames implements checker.Program.
122132
func (p *Program) UseCaseSensitiveFileNames() bool {
123133
return p.Host().FS().UseCaseSensitiveFileNames()

internal/compiler/projectreferencefilemapper.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ func (mapper *ProjectReferenceFileMapper) init(loader *fileLoader, rootTasks []*
5959
}
6060
}
6161
}
62-
return
6362
})
6463
}
6564

@@ -134,6 +133,33 @@ func (mapper *ProjectReferenceFileMapper) getRedirectForResolution(file ast.HasF
134133
return nil
135134
}
136135

136+
func (mapper *ProjectReferenceFileMapper) getResolvedReferenceFor(path tspath.Path) (*tsoptions.ParsedCommandLine, bool) {
137+
config, ok := mapper.configToProjectReference[path]
138+
return config, ok
139+
}
140+
141+
func (mapper *ProjectReferenceFileMapper) forEachResolvedProjectReference(
142+
fn func(path tspath.Path, config *tsoptions.ParsedCommandLine) bool,
143+
) {
144+
if mapper.opts.Config.ConfigFile == nil {
145+
return
146+
}
147+
refs := mapper.referencesInConfigFile[mapper.opts.Config.ConfigFile.SourceFile.Path()]
148+
mapper.forEachResolvedReferenceWorker(refs, fn)
149+
}
150+
151+
func (mapper *ProjectReferenceFileMapper) forEachResolvedReferenceWorker(
152+
referenes []tspath.Path,
153+
fn func(path tspath.Path, config *tsoptions.ParsedCommandLine) bool,
154+
) {
155+
for _, path := range referenes {
156+
config, _ := mapper.configToProjectReference[path]
157+
if !fn(path, config) {
158+
return
159+
}
160+
}
161+
}
162+
137163
func (mapper *ProjectReferenceFileMapper) getSourceToDtsIfSymlink(file ast.HasFileName) *tsoptions.SourceAndProjectReference {
138164
// If preserveSymlinks is true, module resolution wont jump the symlink
139165
// but the resolved real path may be the .d.ts from project reference
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package project
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"slices"
7+
"sync"
8+
9+
"github.com/microsoft/typescript-go/internal/collections"
10+
"github.com/microsoft/typescript-go/internal/core"
11+
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
12+
"github.com/microsoft/typescript-go/internal/tsoptions"
13+
"github.com/microsoft/typescript-go/internal/tspath"
14+
)
15+
16+
type ConfigFileEntry struct {
17+
mu sync.Mutex
18+
commandLine *tsoptions.ParsedCommandLine
19+
projects core.Set[*Project]
20+
pendingReload PendingReload
21+
rootFilesWatch *watchedFiles[[]string]
22+
}
23+
24+
type ExtendedConfigFileEntry struct {
25+
mu sync.Mutex
26+
configFiles core.Set[tspath.Path]
27+
}
28+
29+
type ConfigFileRegistry struct {
30+
Host ProjectHost
31+
ConfigFiles collections.SyncMap[tspath.Path, *ConfigFileEntry]
32+
ExtendedConfigCache collections.SyncMap[tspath.Path, *tsoptions.ExtendedConfigCacheEntry]
33+
ExtendedConfigsUsedBy collections.SyncMap[tspath.Path, *ExtendedConfigFileEntry]
34+
}
35+
36+
func (e *ConfigFileEntry) SetPendingReload(level PendingReload) bool {
37+
if e.pendingReload < level {
38+
e.pendingReload = level
39+
return true
40+
}
41+
return false
42+
}
43+
44+
var _ watchFileHost = (*configFileWatchHost)(nil)
45+
46+
type configFileWatchHost struct {
47+
fileName string
48+
host ProjectHost
49+
}
50+
51+
func (h *configFileWatchHost) Name() string {
52+
return h.fileName
53+
}
54+
55+
func (c *configFileWatchHost) Client() Client {
56+
return c.host.Client()
57+
}
58+
59+
func (c *configFileWatchHost) Log(message string) {
60+
c.host.Log(message)
61+
}
62+
63+
func (c *ConfigFileRegistry) ReleaseConfig(path tspath.Path, project *Project) {
64+
entry, ok := c.ConfigFiles.Load(path)
65+
if !ok {
66+
return
67+
}
68+
entry.mu.Lock()
69+
defer entry.mu.Unlock()
70+
entry.projects.Delete(project)
71+
if entry.projects.Len() == 0 {
72+
c.ConfigFiles.Delete(path)
73+
commandLine := entry.commandLine
74+
entry.commandLine = nil
75+
c.updateExtendedConfigsUsedBy(path, entry, commandLine)
76+
if entry.rootFilesWatch != nil {
77+
entry.rootFilesWatch.update(context.Background(), nil)
78+
}
79+
}
80+
}
81+
82+
func (c *ConfigFileRegistry) AcquireConfig(fileName string, path tspath.Path, project *Project) *tsoptions.ParsedCommandLine {
83+
entry, ok := c.ConfigFiles.Load(path)
84+
if !ok {
85+
// Create parsed command line
86+
config, _ := tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, nil, c.Host, &c.ExtendedConfigCache)
87+
var rootFilesWatch *watchedFiles[[]string]
88+
client := c.Host.Client()
89+
if c.Host.IsWatchEnabled() && client != nil {
90+
rootFilesWatch = newWatchedFiles(&configFileWatchHost{fileName: fileName, host: c.Host}, lsproto.WatchKindChange|lsproto.WatchKindCreate|lsproto.WatchKindDelete, core.Identity, "root files")
91+
}
92+
entry, _ = c.ConfigFiles.LoadOrStore(path, &ConfigFileEntry{
93+
commandLine: config,
94+
pendingReload: PendingReloadFull,
95+
rootFilesWatch: rootFilesWatch,
96+
})
97+
}
98+
entry.mu.Lock()
99+
defer entry.mu.Unlock()
100+
entry.projects.Add(project)
101+
if entry.pendingReload == PendingReloadNone {
102+
return entry.commandLine
103+
}
104+
switch entry.pendingReload {
105+
case PendingReloadFileNames:
106+
entry.commandLine = tsoptions.ReloadFileNamesOfParsedCommandLine(entry.commandLine, c.Host.FS())
107+
case PendingReloadFull:
108+
oldCommandLine := entry.commandLine
109+
entry.commandLine, _ = tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, nil, c.Host, &c.ExtendedConfigCache)
110+
c.updateExtendedConfigsUsedBy(path, entry, oldCommandLine)
111+
c.updateRootFilesWatch(fileName, entry)
112+
}
113+
entry.pendingReload = PendingReloadNone
114+
return entry.commandLine
115+
}
116+
117+
func (c *ConfigFileRegistry) updateRootFilesWatch(fileName string, entry *ConfigFileEntry) {
118+
if entry.rootFilesWatch == nil {
119+
return
120+
}
121+
122+
wildcardGlobs := entry.commandLine.WildcardDirectories()
123+
rootFileGlobs := make([]string, 0, len(wildcardGlobs)+1+len(entry.commandLine.ExtendedSourceFiles()))
124+
rootFileGlobs = append(rootFileGlobs, fileName)
125+
for _, extendedConfig := range entry.commandLine.ExtendedSourceFiles() {
126+
rootFileGlobs = append(rootFileGlobs, extendedConfig)
127+
}
128+
for dir, recursive := range wildcardGlobs {
129+
rootFileGlobs = append(rootFileGlobs, fmt.Sprintf("%s/%s", tspath.NormalizePath(dir), core.IfElse(recursive, recursiveFileGlobPattern, fileGlobPattern)))
130+
}
131+
for _, fileName := range entry.commandLine.LiteralFileNames() {
132+
rootFileGlobs = append(rootFileGlobs, fileName)
133+
}
134+
entry.rootFilesWatch.update(context.Background(), rootFileGlobs)
135+
}
136+
137+
func (c *ConfigFileRegistry) updateExtendedConfigsUsedBy(path tspath.Path, entry *ConfigFileEntry, oldCommandLine *tsoptions.ParsedCommandLine) {
138+
extendedConfigs := entry.commandLine.ExtendedSourceFiles()
139+
newConfigs := make([]tspath.Path, 0, len(extendedConfigs))
140+
for _, extendedConfig := range extendedConfigs {
141+
extendedPath := tspath.ToPath(extendedConfig, c.Host.GetCurrentDirectory(), c.Host.FS().UseCaseSensitiveFileNames())
142+
newConfigs = append(newConfigs, extendedPath)
143+
extendedEntry, _ := c.ExtendedConfigsUsedBy.LoadOrStore(extendedPath, &ExtendedConfigFileEntry{
144+
mu: sync.Mutex{},
145+
})
146+
extendedEntry.mu.Lock()
147+
extendedEntry.configFiles.Add(path)
148+
extendedEntry.mu.Unlock()
149+
}
150+
for _, extendedConfig := range oldCommandLine.ExtendedSourceFiles() {
151+
extendedPath := tspath.ToPath(extendedConfig, c.Host.GetCurrentDirectory(), c.Host.FS().UseCaseSensitiveFileNames())
152+
if !slices.Contains(newConfigs, extendedPath) {
153+
extendedEntry, _ := c.ExtendedConfigsUsedBy.Load(extendedPath)
154+
extendedEntry.mu.Lock()
155+
extendedEntry.configFiles.Delete(path)
156+
if extendedEntry.configFiles.Len() == 0 {
157+
c.ExtendedConfigsUsedBy.Delete(extendedPath)
158+
c.ExtendedConfigCache.Delete(extendedPath)
159+
}
160+
extendedEntry.mu.Unlock()
161+
}
162+
}
163+
}
164+
165+
func (c *ConfigFileRegistry) onWatchedFilesChanged(path tspath.Path, changeKind lsproto.FileChangeType) (err error, handled bool) {
166+
if c.onConfigChange(path, changeKind) {
167+
handled = true
168+
}
169+
170+
if entry, loaded := c.ExtendedConfigsUsedBy.Load(path); loaded {
171+
entry.mu.Lock()
172+
for configFilePath := range entry.configFiles.Keys() {
173+
if c.onConfigChange(configFilePath, changeKind) {
174+
handled = true
175+
}
176+
}
177+
entry.mu.Unlock()
178+
}
179+
return err, handled
180+
}
181+
182+
func (c *ConfigFileRegistry) onConfigChange(path tspath.Path, changeKind lsproto.FileChangeType) bool {
183+
entry, ok := c.ConfigFiles.Load(path)
184+
if !ok {
185+
return false
186+
}
187+
entry.mu.Lock()
188+
defer entry.mu.Unlock()
189+
if entry.SetPendingReload(PendingReloadFull) {
190+
for project := range entry.projects.Keys() {
191+
if project.configFilePath == path {
192+
switch changeKind {
193+
case lsproto.FileChangeTypeCreated:
194+
fallthrough
195+
case lsproto.FileChangeTypeChanged:
196+
project.deferredClose = false
197+
project.SetPendingReload(PendingReloadFull)
198+
case lsproto.FileChangeTypeDeleted:
199+
project.deferredClose = true
200+
}
201+
} else {
202+
project.markAsDirty()
203+
}
204+
}
205+
}
206+
return false
207+
}
208+
209+
func (c *ConfigFileRegistry) tryInvokeWildCardDirectories(fileName string, path tspath.Path) {
210+
configFiles := c.ConfigFiles.ToMap()
211+
for configPath, entry := range configFiles {
212+
entry.mu.Lock()
213+
if entry.commandLine != nil && entry.commandLine.MatchesFileName(fileName) {
214+
if entry.SetPendingReload(PendingReloadFileNames) {
215+
for project := range entry.projects.Keys() {
216+
if project.configFilePath == configPath {
217+
project.SetPendingReload(PendingReloadFileNames)
218+
} else {
219+
project.markAsDirty()
220+
}
221+
}
222+
}
223+
}
224+
entry.mu.Unlock()
225+
}
226+
}

0 commit comments

Comments
 (0)