Skip to content

Commit f0ea3e0

Browse files
committed
Preserve ATA state across project lifecycles
1 parent a50c7fa commit f0ea3e0

6 files changed

Lines changed: 486 additions & 80 deletions

File tree

‎tsc/internal/project/ata/ata.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ type TypingsInfo struct {
2727
func (ti TypingsInfo) Equals(other TypingsInfo) bool {
2828
return ti.TypeAcquisition.Equals(other.TypeAcquisition) &&
2929
ti.CompilerOptions.GetAllowJS() == other.CompilerOptions.GetAllowJS() &&
30+
(ti.CompilerOptions.Types == nil) == (other.CompilerOptions.Types == nil) &&
31+
slices.Equal(ti.CompilerOptions.Types, other.CompilerOptions.Types) &&
3032
ti.UnresolvedImports.Equals(other.UnresolvedImports)
3133
}
3234

‎tsc/internal/project/project.go‎

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,84 @@ type Project struct {
173173
installedTypingsInfo *ata.TypingsInfo
174174
// typingsFiles are the root files added by the typings installer.
175175
typingsFiles []string
176+
// installedTypingsFileNames are the JavaScript files used during the most
177+
// recently completed typings installation.
178+
installedTypingsFileNames []string
179+
// installedTypingsFilesToWatch are discovery inputs whose changes require
180+
// typings discovery to run again.
181+
installedTypingsFilesToWatch []string
182+
// ataInvalidationSnapshotID is the latest snapshot that invalidated this
183+
// project's ATA discovery inputs.
184+
ataInvalidationSnapshotID uint64
185+
}
186+
187+
type inferredProjectATAState struct {
188+
installedTypingsInfo *ata.TypingsInfo
189+
installedTypingsFileNames []string
190+
installedTypingsFilesToWatch []string
191+
typingsFiles []string
192+
typingsWatch *WatchedFiles[PatternsAndIgnored]
193+
}
194+
195+
func (p *Project) inferredProjectATAState() *inferredProjectATAState {
196+
if p.installedTypingsInfo == nil && len(p.installedTypingsFilesToWatch) == 0 {
197+
return nil
198+
}
199+
return &inferredProjectATAState{
200+
installedTypingsInfo: p.installedTypingsInfo,
201+
installedTypingsFileNames: slices.Clone(p.installedTypingsFileNames),
202+
installedTypingsFilesToWatch: slices.Clone(p.installedTypingsFilesToWatch),
203+
typingsFiles: slices.Clone(p.typingsFiles),
204+
typingsWatch: p.typingsWatch,
205+
}
206+
}
207+
208+
func (s *inferredProjectATAState) canApply(project *Project, fs *snapshotFSBuilder, watchEnabled bool) bool {
209+
if s == nil || s.installedTypingsInfo == nil {
210+
return false
211+
}
212+
if !watchEnabled && len(s.installedTypingsFilesToWatch) > 0 {
213+
return false
214+
}
215+
if !s.installedTypingsInfo.Equals(project.ComputeTypingsInfo()) ||
216+
!slices.Equal(s.installedTypingsFileNames, project.ComputeTypingsFileNames()) {
217+
return false
218+
}
219+
for _, fileName := range s.typingsFiles {
220+
if !fs.FileExists(fileName, fs.toPath(fileName)) {
221+
return false
222+
}
223+
}
224+
return true
225+
}
226+
227+
func (s *inferredProjectATAState) apply(project *Project) {
228+
typingsFilesChanged := !slices.Equal(project.typingsFiles, s.typingsFiles)
229+
project.installedTypingsInfo = s.installedTypingsInfo
230+
project.installedTypingsFileNames = slices.Clone(s.installedTypingsFileNames)
231+
project.installedTypingsFilesToWatch = slices.Clone(s.installedTypingsFilesToWatch)
232+
project.typingsFiles = slices.Clone(s.typingsFiles)
233+
project.typingsWatch = s.typingsWatch
234+
if typingsFilesChanged {
235+
project.dirty = true
236+
project.dirtyFilePath = ""
237+
}
238+
}
239+
240+
func (s *inferredProjectATAState) canApplyWatchState(project *Project, watchEnabled bool) bool {
241+
return s != nil &&
242+
s.installedTypingsInfo == nil &&
243+
watchEnabled &&
244+
slices.Equal(s.installedTypingsFileNames, project.ComputeTypingsFileNames())
245+
}
246+
247+
func (s *inferredProjectATAState) applyWatchState(project *Project) {
248+
project.installedTypingsFileNames = slices.Clone(s.installedTypingsFileNames)
249+
project.installedTypingsFilesToWatch = slices.Concat(
250+
slices.Clone(s.installedTypingsFilesToWatch),
251+
s.typingsFiles,
252+
)
253+
project.typingsWatch = s.typingsWatch
176254
}
177255

178256
var _ ls.Project = (*Project)(nil)
@@ -203,6 +281,7 @@ func NewInferredProject(
203281
logger *logging.LogTree,
204282
) *Project {
205283
p := NewProject(inferredProjectID.AsID(), KindInferred, currentDirectory, builder, logger)
284+
p.ataInvalidationSnapshotID = builder.inferredProjectATAInvalidationSnapshotID
206285
if compilerOptions == nil {
207286
compilerOptions = &core.CompilerOptions{
208287
AllowJs: core.TSTrue,
@@ -278,10 +357,11 @@ func NewProject(
278357
logger.Log(fmt.Sprintf("Creating %sProject: %s, currentDirectory: %s", kind.String(), id, currentDirectory))
279358
}
280359
project := &Project{
281-
Kind: kind,
282-
id: id,
283-
currentDirectory: currentDirectory,
284-
dirty: true,
360+
Kind: kind,
361+
id: id,
362+
currentDirectory: currentDirectory,
363+
dirty: true,
364+
ataInvalidationSnapshotID: builder.newSnapshotID,
285365
}
286366

287367
project.programFilesWatch = NewWatchedFiles(
@@ -291,12 +371,7 @@ func NewProject(
291371
createResolutionLookupGlobMapper(builder.sessionOptions.CurrentDirectory, builder.sessionOptions.DefaultLibraryPath, project.currentDirectory, builder.fs.fs.UseCaseSensitiveFileNames()),
292372
)
293373
if builder.sessionOptions.TypingsLocation != "" {
294-
project.typingsWatch = NewWatchedFiles(
295-
"typings installer files",
296-
lsproto.WatchKindCreate|lsproto.WatchKindChange|lsproto.WatchKindDelete,
297-
lsproto.GetClientCapabilities(builder.ctx).Workspace.DidChangeWatchedFiles.RelativePatternSupport,
298-
core.Identity,
299-
)
374+
project.typingsWatch = newTypingsWatch(builder)
300375
}
301376
project.contentMapperWatch = NewWatchedFilesForPaths(
302377
"content mapper configuration files for "+string(id),
@@ -309,6 +384,15 @@ func NewProject(
309384
return project
310385
}
311386

387+
func newTypingsWatch(builder *ProjectCollectionBuilder) *WatchedFiles[PatternsAndIgnored] {
388+
return NewWatchedFiles(
389+
"typings installer files",
390+
lsproto.WatchKindCreate|lsproto.WatchKindChange|lsproto.WatchKindDelete,
391+
lsproto.GetClientCapabilities(builder.ctx).Workspace.DidChangeWatchedFiles.RelativePatternSupport,
392+
core.Identity,
393+
)
394+
}
395+
312396
func (p *Project) CurrentDirectory() string {
313397
return p.currentDirectory
314398
}
@@ -418,8 +502,11 @@ func (p *Project) Clone() *Project {
418502
moduleResolverFactory: p.moduleResolverFactory,
419503
moduleResolverID: p.moduleResolverID,
420504

421-
installedTypingsInfo: p.installedTypingsInfo,
422-
typingsFiles: p.typingsFiles,
505+
installedTypingsInfo: p.installedTypingsInfo,
506+
installedTypingsFileNames: p.installedTypingsFileNames,
507+
installedTypingsFilesToWatch: p.installedTypingsFilesToWatch,
508+
typingsFiles: p.typingsFiles,
509+
ataInvalidationSnapshotID: p.ataInvalidationSnapshotID,
423510
}
424511
}
425512

@@ -666,7 +753,8 @@ func (p *Project) ShouldTriggerATA(snapshotID uint64) bool {
666753
return true
667754
}
668755

669-
return !p.installedTypingsInfo.Equals(p.ComputeTypingsInfo())
756+
return !p.installedTypingsInfo.Equals(p.ComputeTypingsInfo()) ||
757+
!slices.Equal(p.installedTypingsFileNames, p.ComputeTypingsFileNames())
670758
}
671759

672760
func (p *Project) ComputeTypingsInfo() ata.TypingsInfo {
@@ -676,3 +764,17 @@ func (p *Project) ComputeTypingsInfo() ata.TypingsInfo {
676764
UnresolvedImports: p.GetUnresolvedImports(),
677765
}
678766
}
767+
768+
func (p *Project) ComputeTypingsFileNames() []string {
769+
if p.Program == nil {
770+
return nil
771+
}
772+
var fileNames []string
773+
for _, file := range p.Program.GetSourceFiles() {
774+
if tspath.HasJSFileExtension(file.FileName()) && !p.Program.IsSourceFileFromExternalLibrary(file) {
775+
fileNames = append(fileNames, file.FileName())
776+
}
777+
}
778+
slices.Sort(fileNames)
779+
return fileNames
780+
}

‎tsc/internal/project/projectcollection.go‎

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ type ProjectCollection struct {
3131
// inferredProject is a fallback project that is used when no configured
3232
// project can be found for an open file.
3333
inferredProject *Project
34+
// inferredProjectATAState preserves acquired typings while there is no active
35+
// inferred project, so reopening a loose file does not wait for ATA again.
36+
inferredProjectATAState *inferredProjectATAState
37+
// inferredProjectATAInvalidationSnapshotID is the latest snapshot that
38+
// invalidated inferred-project ATA discovery inputs.
39+
inferredProjectATAInvalidationSnapshotID uint64
3440
// apiState tracks the projects and files that API clients have explicitly
3541
// opened so they are kept loaded across snapshots.
3642
apiState APIState
@@ -71,6 +77,16 @@ type apiOpenedFile struct {
7177

7278
func (c *ProjectCollection) ConfigFileRegistry() *ConfigFileRegistry { return c.configFileRegistry }
7379

80+
func (c *ProjectCollection) inferredProjectTypingsWatch() *WatchedFiles[PatternsAndIgnored] {
81+
if c.inferredProject != nil {
82+
return c.inferredProject.typingsWatch
83+
}
84+
if c.inferredProjectATAState != nil {
85+
return c.inferredProjectATAState.typingsWatch
86+
}
87+
return nil
88+
}
89+
7490
func (c *ProjectCollection) ConfiguredProject(path tspath.Path) *Project {
7591
return c.configuredProjects[ConfiguredProjectID(path)]
7692
}
@@ -328,14 +344,16 @@ func (c *ProjectCollection) findDefaultConfiguredProjectWorker(path tspath.Path,
328344
// clone creates a shallow copy of the project collection.
329345
func (c *ProjectCollection) clone() *ProjectCollection {
330346
return &ProjectCollection{
331-
toPath: c.toPath,
332-
configFileRegistry: c.configFileRegistry,
333-
configuredProjects: c.configuredProjects,
334-
syntheticProjects: c.syntheticProjects,
335-
openFiles: c.openFiles,
336-
inferredProject: c.inferredProject,
337-
fileDefaultProjects: c.fileDefaultProjects,
338-
apiState: c.apiState,
347+
toPath: c.toPath,
348+
configFileRegistry: c.configFileRegistry,
349+
configuredProjects: c.configuredProjects,
350+
syntheticProjects: c.syntheticProjects,
351+
openFiles: c.openFiles,
352+
inferredProject: c.inferredProject,
353+
inferredProjectATAState: c.inferredProjectATAState,
354+
inferredProjectATAInvalidationSnapshotID: c.inferredProjectATAInvalidationSnapshotID,
355+
fileDefaultProjects: c.fileDefaultProjects,
356+
apiState: c.apiState,
339357
}
340358
}
341359

0 commit comments

Comments
 (0)