Match custom extension files to analysed paths by realpath so symlinked packages are not reported as outside analysed paths#6015
Open
phpstan-bot wants to merge 1 commit into
Conversation
…ed packages are not reported as outside analysed paths - ResultCacheManager::getProjectExtensionFiles() looks up the class file reported by reflection in the analysed dependency graph. Reflection returns the file's realpath, while analysed paths may reach the same file through a symlink (e.g. a Composer path repository symlinked into vendor), so the direct lookup missed it and the file was wrongly treated as not analysed. - Add findAnalysedFileNameByRealpath() which resolves the reflection file name to the analysed dependency key by comparing realpaths, and use it as a fallback before concluding an extension is outside of analysed paths. - The realpath map over the dependency graph is built lazily and only when a service class is neither found directly nor located under a vendor dir, so normal (non-symlinked) projects pay no extra cost. - Add e2e/result-cache-symlink covering a custom rule provided by a package symlinked into vendor via a Composer path repository; modifying the rule must not print "Result cache might not behave correctly".
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
When a custom PHPStan extension (rule, service, etc.) is provided by a package that is symlinked into
vendor(for example a Composer path repository, a common mono-repo setup) and that package's source is included in the analysedpaths, PHPStan still printed:even though the extension is analysed. This is a false positive.
Changes
src/Analyser/ResultCache/ResultCacheManager.phpgetProjectExtensionFiles()now, when a service class file is not found directly in the analysed dependency graph and is not located under a vendor directory, tries to match it to an analysed file by realpath before treating it as outside of analysed paths.findAnalysedFileNameByRealpath()builds a lazy map fromrealpath(dependencyKey) => dependencyKeyand resolves the reflection file name against it. The map is only built when needed, so non-symlinked projects are unaffected in behaviour and cost.?array $analysedFileNamesByRealpathcache property, reset at the start ofgetProjectExtensionFiles()..github/workflows/e2e-tests.yml+e2e/result-cache-symlink/extpackage is symlinked into theapp'svendorthrough a Composer path repository and itssrcis added to the analysed paths. After modifying the rule, the run must not containResult cache might not behave correctly.Root cause
ReflectionClass::getFileName()returns the realpath of the class file (PHP resolves symlinks when including a file), whereas the analysed file/dependency keys are the paths as traversed from the configuredpaths— i.e. the symlinked path (vendor/.../src/MyRule.php). The two never matched, sogetAllDependencies()returned nothing, the file wasn't under the app'svendorrealpath (it resolves to somewhere outside, e.g.../ext/src), and it was recorded asisAnalysed = false, triggering the warning.The fix reconciles the two path forms by comparing realpaths:
realpath(symlinkedAnalysedPath) === realpath(reflectionRealpath), so a symlinked-in extension that is actually analysed is correctly recognized. Genuinely non-analysed extensions (e.g. those living only invendoror an unanalysedbuild/dir) still fail the realpath lookup and are reported as before.Test
e2e/result-cache-symlinkreproduces the reported setup with a Composer path-repository symlink and asserts the false-positive warning is gone. Verified it fails without the fix (the warning is printed) and passes with it.vendordir, and extensions in an unanalysed dir such ase2e/result-cache-8'sbuild/): both still correctly report the extension as outside analysed paths, because their realpath is not among the analysed files — no regression.e2e/result-cache-8continues to assert the warning is shown.Fixes phpstan/phpstan#14924