-
Notifications
You must be signed in to change notification settings - Fork 649
Add cleanup code for projects and script infos #1007
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
9bd87cb
Add cleanup code for projects and script infos
sheetalkamat 3db2934
Handle concurrency
sheetalkamat 69a2853
Use sync maps
sheetalkamat b6e5833
refactor scriptinfo code
sheetalkamat 8995a39
More refactor and fixes
sheetalkamat a577da2
Print memory usage in the log for now
sheetalkamat fc36044
ContainsFunc
sheetalkamat c61c37f
Merge branch 'main' into cleanup
sheetalkamat 63609b7
Update internal/testutil/projecttestutil/projecttestutil.go
sheetalkamat 54947db
Feedback
sheetalkamat 58509c3
use hasAddedOrRemovedFile now that we have it available
sheetalkamat 98d8132
Revert to mutex and handle case where getProgram can return nil if it…
sheetalkamat File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -171,6 +171,7 @@ func NewConfiguredProject(configFileName string, configFilePath tspath.Path, hos | |
project.configFileName = configFileName | ||
project.configFilePath = configFilePath | ||
project.initialLoadPending = true | ||
project.pendingReload = PendingReloadFull | ||
client := host.Client() | ||
if host.IsWatchEnabled() && client != nil { | ||
project.rootFilesWatch = newWatchedFiles(project, lsproto.WatchKindChange|lsproto.WatchKindCreate|lsproto.WatchKindDelete, core.Identity, "root files") | ||
|
@@ -193,6 +194,7 @@ func NewProject(name string, kind Kind, currentDirectory string, host ProjectHos | |
kind: kind, | ||
currentDirectory: currentDirectory, | ||
rootFileNames: &collections.OrderedMap[tspath.Path, string]{}, | ||
dirty: true, | ||
} | ||
project.comparePathsOptions = tspath.ComparePathsOptions{ | ||
CurrentDirectory: currentDirectory, | ||
|
@@ -362,6 +364,16 @@ func (p *Project) updateWatchers(ctx context.Context) { | |
p.affectingLocationsWatch.update(ctx, affectingLocationGlobs) | ||
} | ||
|
||
func (p *Project) tryInvokeWildCardDirectories(fileName string, path tspath.Path) bool { | ||
if p.kind == KindConfigured { | ||
if p.rootFileNames.Has(path) || p.parsedCommandLine.MatchesFileName(fileName) { | ||
p.SetPendingReload(PendingReloadFileNames) | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// onWatchEventForNilScriptInfo is fired for watch events that are not the | ||
// project tsconfig, and do not have a ScriptInfo for the associated file. | ||
// This could be a case of one of the following: | ||
|
@@ -371,14 +383,9 @@ func (p *Project) updateWatchers(ctx context.Context) { | |
// part of the project, e.g., a .js file in a project without --allowJs. | ||
func (p *Project) onWatchEventForNilScriptInfo(fileName string) { | ||
path := p.toPath(fileName) | ||
if p.kind == KindConfigured { | ||
if p.rootFileNames.Has(path) || p.parsedCommandLine.MatchesFileName(fileName) { | ||
p.pendingReload = PendingReloadFileNames | ||
p.markAsDirty() | ||
return | ||
} | ||
if p.tryInvokeWildCardDirectories(fileName, path) { | ||
return | ||
} | ||
|
||
if _, ok := p.failedLookupsWatch.data[path]; ok { | ||
p.markAsDirty() | ||
} else if _, ok := p.affectingLocationsWatch.data[path]; ok { | ||
|
@@ -430,6 +437,15 @@ func (p *Project) MarkFileAsDirty(path tspath.Path) { | |
} | ||
} | ||
|
||
func (p *Project) SetPendingReload(level PendingReload) { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
if level > p.pendingReload { | ||
p.pendingReload = level | ||
p.markAsDirtyLocked() | ||
} | ||
} | ||
|
||
func (p *Project) markAsDirty() { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
|
@@ -473,12 +489,14 @@ func (p *Project) updateGraph() bool { | |
p.parsedCommandLine = tsoptions.ReloadFileNamesOfParsedCommandLine(p.parsedCommandLine, p.host.FS()) | ||
writeFileNames = p.setRootFiles(p.parsedCommandLine.FileNames()) | ||
p.programConfig = nil | ||
p.pendingReload = PendingReloadNone | ||
case PendingReloadFull: | ||
if err := p.loadConfig(); err != nil { | ||
var err error | ||
writeFileNames, err = p.LoadConfig() | ||
if err != nil { | ||
panic(fmt.Sprintf("failed to reload config: %v", err)) | ||
} | ||
} | ||
p.pendingReload = PendingReloadNone | ||
} | ||
|
||
oldProgramReused := p.updateProgram() | ||
|
@@ -496,6 +514,7 @@ func (p *Project) updateGraph() bool { | |
for _, oldSourceFile := range oldProgram.GetSourceFiles() { | ||
if p.program.GetSourceFileByPath(oldSourceFile.Path()) == nil { | ||
p.host.DocumentRegistry().ReleaseDocument(oldSourceFile, oldProgram.GetCompilerOptions()) | ||
p.detachScriptInfoIfNotInferredRoot(oldSourceFile.Path()) | ||
} | ||
} | ||
} | ||
|
@@ -707,7 +726,7 @@ func (p *Project) extractUnresolvedImportsFromSourceFile(file *ast.SourceFile, o | |
func (p *Project) UpdateTypingFiles(typingsInfo *TypingsInfo, typingFiles []string) { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
if p.typingsInfo != typingsInfo { | ||
if p.isClosed() || p.typingsInfo != typingsInfo { | ||
return | ||
} | ||
|
||
|
@@ -737,6 +756,12 @@ func (p *Project) UpdateTypingFiles(typingsInfo *TypingsInfo, typingFiles []stri | |
} | ||
|
||
func (p *Project) WatchTypingLocations(files []string) { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
if p.isClosed() { | ||
return | ||
} | ||
|
||
client := p.host.Client() | ||
if !p.host.IsWatchEnabled() || client == nil { | ||
return | ||
|
@@ -804,23 +829,13 @@ func (p *Project) isRoot(info *ScriptInfo) bool { | |
return p.rootFileNames.Has(info.path) | ||
} | ||
|
||
func (p *Project) RemoveFile(info *ScriptInfo, fileExists bool, detachFromProject bool) { | ||
func (p *Project) RemoveFile(info *ScriptInfo, fileExists bool) { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
p.removeFile(info, fileExists, detachFromProject) | ||
p.markAsDirtyLocked() | ||
} | ||
|
||
func (p *Project) removeFile(info *ScriptInfo, fileExists bool, detachFromProject bool) { | ||
if p.isRoot(info) { | ||
switch p.kind { | ||
case KindInferred: | ||
p.rootFileNames.Delete(info.path) | ||
p.typeAcquisition = nil | ||
p.programConfig = nil | ||
case KindConfigured: | ||
p.pendingReload = PendingReloadFileNames | ||
} | ||
if p.isRoot(info) && p.kind == KindInferred { | ||
p.rootFileNames.Delete(info.path) | ||
p.typeAcquisition = nil | ||
p.programConfig = nil | ||
} | ||
p.onFileAddedOrRemoved() | ||
|
||
|
@@ -832,49 +847,34 @@ func (p *Project) removeFile(info *ScriptInfo, fileExists bool, detachFromProjec | |
// this.resolutionCache.invalidateResolutionOfFile(info.path); | ||
// } | ||
// this.cachedUnresolvedImportsPerFile.delete(info.path); | ||
if detachFromProject { | ||
info.detachFromProject(p) | ||
} | ||
p.markAsDirtyLocked() | ||
} | ||
|
||
func (p *Project) AddRoot(info *ScriptInfo) { | ||
func (p *Project) AddInferredProjectRoot(info *ScriptInfo) { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
p.addRoot(info) | ||
if p.isRoot(info) { | ||
panic("script info is already a root") | ||
Comment on lines
+854
to
+858
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nitpick] The function panics if the script info is already a root; consider whether a graceful error handling strategy might be more appropriate for production usage. Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
} | ||
p.rootFileNames.Set(info.path, info.fileName) | ||
p.programConfig = nil | ||
p.markAsDirtyLocked() | ||
} | ||
|
||
func (p *Project) addRoot(info *ScriptInfo) { | ||
p.typeAcquisition = nil | ||
// !!! | ||
// if p.kind == KindInferred { | ||
// p.host.startWatchingConfigFilesForInferredProjectRoot(info.path); | ||
// // handle JS toggling | ||
// } | ||
if p.isRoot(info) { | ||
panic("script info is already a root") | ||
} | ||
p.rootFileNames.Set(info.path, info.fileName) | ||
if p.kind == KindInferred { | ||
p.typeAcquisition = nil | ||
} | ||
info.attachToProject(p) | ||
p.markAsDirtyLocked() | ||
} | ||
|
||
func (p *Project) LoadConfig() error { | ||
if err := p.loadConfig(); err != nil { | ||
return err | ||
} | ||
p.markAsDirty() | ||
return nil | ||
} | ||
|
||
func (p *Project) loadConfig() error { | ||
func (p *Project) LoadConfig() (bool, error) { | ||
if p.kind != KindConfigured { | ||
panic("loadConfig called on non-configured project") | ||
} | ||
|
||
p.programConfig = nil | ||
p.pendingReload = PendingReloadNone | ||
if configFileContent, ok := p.host.FS().ReadFile(p.configFileName); ok { | ||
configDir := tspath.GetDirectoryPath(p.configFileName) | ||
tsConfigSourceFile := tsoptions.NewTsconfigSourceFileFromFilePath(p.configFileName, p.configFilePath, configFileContent) | ||
|
@@ -901,52 +901,44 @@ func (p *Project) loadConfig() error { | |
p.parsedCommandLine = parsedCommandLine | ||
p.compilerOptions = parsedCommandLine.CompilerOptions() | ||
p.typeAcquisition = parsedCommandLine.TypeAcquisition() | ||
p.setRootFiles(parsedCommandLine.FileNames()) | ||
return p.setRootFiles(parsedCommandLine.FileNames()), nil | ||
} else { | ||
p.compilerOptions = &core.CompilerOptions{} | ||
p.typeAcquisition = nil | ||
return fmt.Errorf("could not read file %q", p.configFileName) | ||
return false, fmt.Errorf("could not read file %q", p.configFileName) | ||
} | ||
return nil | ||
} | ||
|
||
// setRootFiles returns true if the set of root files has changed. | ||
func (p *Project) setRootFiles(rootFileNames []string) bool { | ||
var hasChanged bool | ||
newRootScriptInfos := make(map[tspath.Path]struct{}, len(rootFileNames)) | ||
for _, file := range rootFileNames { | ||
scriptKind := p.getScriptKind(file) | ||
path := p.toPath(file) | ||
// !!! updateNonInferredProjectFiles uses a fileExists check, which I guess | ||
// could be needed if a watcher fails? | ||
scriptInfo := p.host.GetOrCreateScriptInfoForFile(file, path, scriptKind) | ||
newRootScriptInfos[path] = struct{}{} | ||
isAlreadyRoot := p.rootFileNames.Has(path) | ||
hasChanged = hasChanged || !isAlreadyRoot | ||
|
||
if !isAlreadyRoot && scriptInfo != nil { | ||
p.addRoot(scriptInfo) | ||
if scriptInfo.isOpen { | ||
// !!! | ||
// s.removeRootOfInferredProjectIfNowPartOfOtherProject(scriptInfo) | ||
} | ||
} else if !isAlreadyRoot { | ||
p.rootFileNames.Set(path, file) | ||
} | ||
p.rootFileNames.Set(path, file) | ||
// if !isAlreadyRoot { | ||
// if scriptInfo.isOpen { | ||
// !!!s.removeRootOfInferredProjectIfNowPartOfOtherProject(scriptInfo) | ||
// } | ||
// } | ||
} | ||
|
||
if p.rootFileNames.Size() > len(rootFileNames) { | ||
hasChanged = true | ||
for root := range p.rootFileNames.Keys() { | ||
if _, ok := newRootScriptInfos[root]; !ok { | ||
if info := p.host.GetScriptInfoByPath(root); info != nil { | ||
p.removeFile(info, true /*fileExists*/, true /*detachFromProject*/) | ||
} else { | ||
p.rootFileNames.Delete(root) | ||
} | ||
p.rootFileNames.Delete(root) | ||
} | ||
} | ||
} | ||
if hasChanged { | ||
p.onFileAddedOrRemoved() | ||
} | ||
return hasChanged | ||
} | ||
|
||
|
@@ -994,21 +986,20 @@ func (p *Project) GetFileNames(excludeFilesFromExternalLibraries bool, excludeCo | |
} | ||
|
||
func (p *Project) print(writeFileNames bool, writeFileExplanation bool, writeFileVersionAndText bool, builder *strings.Builder) string { | ||
builder.WriteString(fmt.Sprintf("Project '%s' (%s)\n", p.name, p.kind.String())) | ||
builder.WriteString(fmt.Sprintf("\nProject '%s' (%s)\n", p.name, p.kind.String())) | ||
if p.initialLoadPending { | ||
builder.WriteString("\tFiles (0) InitialLoadPending\n") | ||
builder.WriteString("\n\tFiles (0) InitialLoadPending\n") | ||
} else if p.program == nil { | ||
builder.WriteString("\tFiles (0) NoProgram\n") | ||
builder.WriteString("\n\tFiles (0) NoProgram\n") | ||
} else { | ||
sourceFiles := p.program.GetSourceFiles() | ||
builder.WriteString(fmt.Sprintf("\tFiles (%d)\n", len(sourceFiles))) | ||
builder.WriteString(fmt.Sprintf("\n\tFiles (%d)\n", len(sourceFiles))) | ||
if writeFileNames { | ||
for _, sourceFile := range sourceFiles { | ||
builder.WriteString("\t\t" + sourceFile.FileName()) | ||
builder.WriteString("\n\t\t" + sourceFile.FileName()) | ||
if writeFileVersionAndText { | ||
builder.WriteString(fmt.Sprintf(" %d %s", sourceFile.Version, sourceFile.Text())) | ||
} | ||
builder.WriteRune('\n') | ||
} | ||
// !!! | ||
// if writeFileExplanation {} | ||
|
@@ -1026,8 +1017,61 @@ func (p *Project) Logf(format string, args ...interface{}) { | |
p.Log(fmt.Sprintf(format, args...)) | ||
} | ||
|
||
func (p *Project) detachScriptInfoIfNotInferredRoot(path tspath.Path) { | ||
// We might not find the script info in case its not associated with the project any more | ||
// and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk) | ||
if scriptInfo := p.host.GetScriptInfoByPath(path); scriptInfo != nil && | ||
(p.kind != KindInferred || !p.isRoot(scriptInfo)) { | ||
scriptInfo.detachFromProject(p) | ||
} | ||
} | ||
|
||
func (p *Project) Close() { | ||
// !!! | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
|
||
if p.program != nil { | ||
for _, sourceFile := range p.program.GetSourceFiles() { | ||
p.host.DocumentRegistry().ReleaseDocument(sourceFile, p.program.GetCompilerOptions()) | ||
// Detach script info if its not root or is root of non inferred project | ||
p.detachScriptInfoIfNotInferredRoot(sourceFile.Path()) | ||
} | ||
p.program = nil | ||
} | ||
|
||
if p.kind == KindInferred { | ||
// Release root script infos for inferred projects. | ||
for path := range p.rootFileNames.Keys() { | ||
if info := p.host.GetScriptInfoByPath(path); info != nil { | ||
info.detachFromProject(p) | ||
} | ||
} | ||
} | ||
p.rootFileNames = nil | ||
p.parsedCommandLine = nil | ||
p.programConfig = nil | ||
p.checkerPool = nil | ||
p.unresolvedImportsPerFile = nil | ||
p.typingsInfo = nil | ||
p.typingFiles = nil | ||
|
||
// Clean up file watchers waiting for missing files | ||
client := p.host.Client() | ||
if p.host.IsWatchEnabled() && client != nil { | ||
ctx := context.Background() | ||
if p.rootFilesWatch != nil { | ||
p.rootFilesWatch.update(ctx, nil) | ||
} | ||
|
||
p.failedLookupsWatch.update(ctx, nil) | ||
p.affectingLocationsWatch.update(ctx, nil) | ||
p.typingsFilesWatch.update(ctx, nil) | ||
p.typingsDirectoryWatch.update(ctx, nil) | ||
} | ||
} | ||
|
||
func (p *Project) isClosed() bool { | ||
return p.rootFileNames == nil | ||
} | ||
|
||
func formatFileList(files []string, linePrefix string, groupSuffix string) string { | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.