Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions tsc/internal/project/ata/ata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,41 @@ func TestATA(t *testing.T) {
assert.Assert(t, jqueryTypesFile != nil, "jquery types should be installed")
})

t.Run("inferred project retains typings after closing last file", func(t *testing.T) {
t.Parallel()

files := map[string]any{
"/user/username/projects/project/app.js": ``,
"/user/username/projects/project/package.json": `{
"name": "test",
"dependencies": {
"jquery": "^3.1.0"
}
}`,
}

session, _ := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
PackageToFile: map[string]string{
"jquery": `declare const $: { x: number }`,
},
})

uri := lsproto.DocumentUri("file:///user/username/projects/project/app.js")
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
session.WaitForBackgroundTasks()
_, err := session.GetLanguageService(context.Background(), uri)
assert.NilError(t, err)

session.DidCloseFile(context.Background(), uri)
session.WaitForBackgroundTasks()
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)

ls, err := session.GetLanguageService(context.Background(), uri)
assert.NilError(t, err)
typingsFile := ls.GetProgram().GetSourceFile(projecttestutil.TestTypingsLocation + "/node_modules/@types/jquery/index.d.ts")
assert.Assert(t, typingsFile != nil, "jquery types should be available immediately after reopening")
})

t.Run("type acquisition with disableFilenameBasedTypeAcquisition:true", func(t *testing.T) {
t.Parallel()

Expand Down
26 changes: 26 additions & 0 deletions tsc/internal/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,32 @@ type Project struct {
typingsFiles []string
}

type inferredProjectATAState struct {
installedTypingsInfo *ata.TypingsInfo
typingsFiles []string
typingsWatch *WatchedFiles[PatternsAndIgnored]
}

func (p *Project) inferredProjectATAState() *inferredProjectATAState {
if p.installedTypingsInfo == nil {
return nil
}
return &inferredProjectATAState{
installedTypingsInfo: p.installedTypingsInfo,
typingsFiles: slices.Clone(p.typingsFiles),
typingsWatch: p.typingsWatch,
}
}

func (s *inferredProjectATAState) apply(project *Project) {
if s == nil {
return
}
project.installedTypingsInfo = s.installedTypingsInfo
project.typingsFiles = slices.Clone(s.typingsFiles)
project.typingsWatch = s.typingsWatch
}

var _ ls.Project = (*Project)(nil)

func NewConfiguredProject(
Expand Down
20 changes: 12 additions & 8 deletions tsc/internal/project/projectcollection.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type ProjectCollection struct {
// inferredProject is a fallback project that is used when no configured
// project can be found for an open file.
inferredProject *Project
// inferredProjectATAState preserves acquired typings while there is no active
// inferred project, so reopening a loose file does not wait for ATA again.
inferredProjectATAState *inferredProjectATAState
// apiState tracks the projects and files that API clients have explicitly
// opened so they are kept loaded across snapshots.
apiState APIState
Expand Down Expand Up @@ -328,14 +331,15 @@ func (c *ProjectCollection) findDefaultConfiguredProjectWorker(path tspath.Path,
// clone creates a shallow copy of the project collection.
func (c *ProjectCollection) clone() *ProjectCollection {
return &ProjectCollection{
toPath: c.toPath,
configFileRegistry: c.configFileRegistry,
configuredProjects: c.configuredProjects,
syntheticProjects: c.syntheticProjects,
openFiles: c.openFiles,
inferredProject: c.inferredProject,
fileDefaultProjects: c.fileDefaultProjects,
apiState: c.apiState,
toPath: c.toPath,
configFileRegistry: c.configFileRegistry,
configuredProjects: c.configuredProjects,
syntheticProjects: c.syntheticProjects,
openFiles: c.openFiles,
inferredProject: c.inferredProject,
inferredProjectATAState: c.inferredProjectATAState,
fileDefaultProjects: c.fileDefaultProjects,
apiState: c.apiState,
}
}

Expand Down
18 changes: 13 additions & 5 deletions tsc/internal/project/projectcollectionbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ type ProjectCollectionBuilder struct {
defaultProjectsInvalidated bool
openFilesChanged bool

fileDefaultProjects map[tspath.Path]ID
configuredProjects *dirty.SyncMap[ConfiguredProjectID, *Project]
syntheticProjects *dirty.SyncMap[SyntheticProjectID, *Project]
inferredProject *dirty.Box[*Project]
createdPrograms []*Project
fileDefaultProjects map[tspath.Path]ID
configuredProjects *dirty.SyncMap[ConfiguredProjectID, *Project]
syntheticProjects *dirty.SyncMap[SyntheticProjectID, *Project]
inferredProject *dirty.Box[*Project]
inferredProjectATAState *inferredProjectATAState
createdPrograms []*Project

apiState APIState
}
Expand Down Expand Up @@ -105,6 +106,7 @@ func newProjectCollectionBuilder(
configuredProjects: dirty.NewSyncMap(oldProjectCollection.configuredProjects),
syntheticProjects: dirty.NewSyncMap(oldProjectCollection.syntheticProjects),
inferredProject: dirty.NewBox(oldProjectCollection.inferredProject),
inferredProjectATAState: oldProjectCollection.inferredProjectATAState,
apiState: oldAPIState.clone(),
client: client,
}
Expand Down Expand Up @@ -148,6 +150,10 @@ func (b *ProjectCollectionBuilder) Finalize(logger *logging.LogTree) (*ProjectCo
ensureCloned()
newProjectCollection.inferredProject = newInferredProject
}
if b.inferredProjectATAState != b.base.inferredProjectATAState {
ensureCloned()
newProjectCollection.inferredProjectATAState = b.inferredProjectATAState
}

configFileRegistry := b.configFileRegistryBuilder.Finalize()
if configFileRegistry != b.base.configFileRegistry {
Expand Down Expand Up @@ -1343,6 +1349,7 @@ func (b *ProjectCollectionBuilder) deleteInferredProject(logger *logging.LogTree
return true
})
}
b.inferredProjectATAState = project.inferredProjectATAState()
b.inferredProject.Delete()
return true
}
Expand All @@ -1360,6 +1367,7 @@ func (b *ProjectCollectionBuilder) updateOrCreateInferredProject(
project := b.inferredProject.Value()
if project == nil {
project = NewInferredProject(b.sessionOptions.CurrentDirectory, compilerOptions, rootFileNames, projectReferences, contentMappers, b, logger)
b.inferredProjectATAState.apply(project)
Comment thread
jakebailey marked this conversation as resolved.
Outdated
project.CommandLine.Errors = configFileParsingDiagnostics
b.inferredProject.Set(project)
return true
Expand Down
Loading