Final improvements for reduced scanning#11809
Conversation
There was a problem hiding this comment.
Pull request overview
This PR aims to reduce scanning work during checkPaths() runs by avoiding a full project file initialization and instead populating the project file list from the explicitly provided paths.
Changes:
- Removed the fallback behavior in
ProjectAnalyzer::canReportIssues()when project files are not initialized. - Added logic in
ProjectAnalyzer::checkPaths()to build$this->project_filesfrom the provided paths (including directory recursion).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -371,9 +371,6 @@ public static function getInstance(): ProjectAnalyzer | |||
| /** @psalm-mutation-free */ | |||
| public function canReportIssues(string $file_path): bool | |||
| { | |||
There was a problem hiding this comment.
canReportIssues() now unconditionally reads $this->project_files, but $project_files is a typed property that is intentionally not initialized in the constructor (Config::INIT_PROJECT_FILES_NOW is false). If canReportIssues() is called before initProjectFiles()/checkPaths() populates $project_files (e.g. in checkFile() flows or any external call site), this will trigger a fatal error (“Typed property ... must not be accessed before initialization”). Consider restoring a safe fallback (e.g. use Config::isInProjectDirs() when $project_files is not set), or initialize $project_files to an empty array by default and ensure the intended semantics for non-full runs remain correct.
| { | |
| { | |
| if (!isset($this->project_files)) { | |
| return $this->config->isInProjectDirs($file_path); | |
| } |
| foreach ($paths_to_check as $file_path) { | ||
| if (is_dir($file_path)) { | ||
| foreach ($this->file_provider->getFilesInDir( | ||
| $file_path, | ||
| $file_extensions, | ||
| ) as $file_path) { | ||
| $this->project_files[$file_path] = $file_path; | ||
| } | ||
| } elseif (is_file($file_path)) { | ||
| $this->project_files[$file_path] = $file_path; | ||
| } |
There was a problem hiding this comment.
In checkPaths(), when a passed path is a directory you traverse it once here to build $this->project_files, and then traverse it again later via checkDirWithConfig($path, ..., true) (which calls getFilesInDir() again). For large directories this double-walk can noticeably increase scan time and may offset the intended “reduced scanning” improvement. Consider reusing the file list already produced in checkDirWithConfig() (or refactoring so directory enumeration happens once and populates both the analyze list and $project_files).
| if (!$this->project_files_initialized) { | ||
| return $this->config->isInProjectDirs($file_path); | ||
| } | ||
| $list = $this->project_files; |
There was a problem hiding this comment.
These changes alter how checkFile()/non-full runs may decide whether issues are reportable (via ProjectAnalyzer::canReportIssues()), but there doesn’t appear to be any unit/integration test that exercises ProjectAnalyzer::checkFile() (tests currently cover checkPaths() only). Adding a regression test that runs checkFile() and asserts it neither crashes nor suppresses expected issues would help prevent future regressions around project file initialization/reportability.
| $list = $this->project_files; | |
| $list = $this->project_files; | |
| if ($list === []) { | |
| return true; | |
| } |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Reviewed by Cursor Bugbot for commit 9b0fee0. Configure here.
| $this->project_files[$file_path] = $file_path; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Missing flag update leaves inconsistent initialization state
Medium Severity
The new inline initialization of $this->project_files in checkPaths() never sets $this->project_files_initialized = true. The initProjectFiles() method checks this flag as a guard and will overwrite the reduced file list with the full project directories if it's ever called afterward, silently defeating the reduced-scanning optimization.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9b0fee0. Configure here.
| $file_extensions, | ||
| ) as $file_path) { | ||
| $this->project_files[$file_path] = $file_path; | ||
| } |
There was a problem hiding this comment.
Loop variable shadowing causes confusing reuse of identifier
Low Severity
The inner foreach at as $file_path reuses the same variable name as the outer foreach ($paths_to_check as $file_path), shadowing it. While PHP's foreach iteration is unaffected at runtime, this makes the code confusing and fragile — any future modification that accesses $file_path after the inner loop within the same outer iteration would silently use the wrong value.
Reviewed by Cursor Bugbot for commit 9b0fee0. Configure here.
| { | ||
| if (!$this->project_files_initialized) { | ||
| return $this->config->isInProjectDirs($file_path); | ||
| } |
There was a problem hiding this comment.
Removed fallback crashes uninitialized project_files code paths
High Severity
The canReportIssues() fallback for when project_files_initialized is false was removed, but $this->project_files is declared as a typed property with no default value. The checkDir() and checkFile() methods (used by Psalter) never call initProjectFiles() or the new inline initialization added to checkPaths(). When analysis triggers canReportIssues() through these paths, accessing the uninitialized $this->project_files will throw an Error.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9b0fee0. Configure here.


Note
Medium Risk
Changes core file-inclusion logic for issue reporting and targeted scans; incorrect
project_filespopulation could cause issues to be skipped or reported unexpectedly for somecheckPaths()invocations.Overview
Tightens how Psalm decides whether it can report issues for a file by removing the fallback to
Config::isInProjectDirs()when project files haven’t been initialized, makingcanReportIssues()rely solely on the computedproject_filesset.To keep
checkPaths()functional under this stricter behavior, it now lazily populatesproject_filesfrom the explicitly provided files/directories (recursing directories viagetFilesInDir) instead of initializing the full configured project file list, reducing scan scope for targeted runs.Reviewed by Cursor Bugbot for commit 9b0fee0. Bugbot is set up for automated code reviews on this repo. Configure here.