From 9338fdfe1611f742c65c61e5035315e1d64c4972 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 19 Mar 2024 09:33:06 +0100 Subject: [PATCH] Warn about possibly stale result cache with custom extensions --- .github/workflows/e2e-tests.yml | 7 ++ e2e/result-cache-8/.gitignore | 1 + e2e/result-cache-8/build/CustomRule.php | 24 +++++ e2e/result-cache-8/build/CustomRule2.php | 24 +++++ e2e/result-cache-8/composer.json | 14 +++ e2e/result-cache-8/composer.lock | 132 +++++++++++++++++++++++ e2e/result-cache-8/phpstan.neon | 17 +++ e2e/result-cache-8/src/CustomRule3.php | 24 +++++ e2e/result-cache-8/src/Foo.php | 8 ++ src/Command/AnalyseCommand.php | 95 +++++++++++++++- 10 files changed, 344 insertions(+), 2 deletions(-) create mode 100644 e2e/result-cache-8/.gitignore create mode 100644 e2e/result-cache-8/build/CustomRule.php create mode 100644 e2e/result-cache-8/build/CustomRule2.php create mode 100644 e2e/result-cache-8/composer.json create mode 100644 e2e/result-cache-8/composer.lock create mode 100644 e2e/result-cache-8/phpstan.neon create mode 100644 e2e/result-cache-8/src/CustomRule3.php create mode 100644 e2e/result-cache-8/src/Foo.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index b05d89840b..703714eecb 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -173,6 +173,13 @@ jobs: export PHPSTAN_SCOPE_CLASS=MyTestScope ACTUAL=$(../../bin/phpstan dump-parameters -c phpstan.neon --json -l 9 | jq --raw-output '.scopeClass') [[ "$ACTUAL" == "MyTestScope" ]]; + - script: | + cd e2e/result-cache-8 + composer install + ../../bin/phpstan + OUTPUT=$(../../bin/phpstan 2>&1) + grep 'Warning: Result cache might not behave correctly' <<< "$OUTPUT" + grep 'ResultCache8E2E\\CustomRule' <<< "$OUTPUT" steps: - name: "Checkout" diff --git a/e2e/result-cache-8/.gitignore b/e2e/result-cache-8/.gitignore new file mode 100644 index 0000000000..61ead86667 --- /dev/null +++ b/e2e/result-cache-8/.gitignore @@ -0,0 +1 @@ +/vendor diff --git a/e2e/result-cache-8/build/CustomRule.php b/e2e/result-cache-8/build/CustomRule.php new file mode 100644 index 0000000000..a648c215a5 --- /dev/null +++ b/e2e/result-cache-8/build/CustomRule.php @@ -0,0 +1,24 @@ + + */ +class CustomRule implements Rule +{ + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return []; + } + +} diff --git a/e2e/result-cache-8/build/CustomRule2.php b/e2e/result-cache-8/build/CustomRule2.php new file mode 100644 index 0000000000..413d37b54d --- /dev/null +++ b/e2e/result-cache-8/build/CustomRule2.php @@ -0,0 +1,24 @@ + + */ +class CustomRule2 implements Rule +{ + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return []; + } + +} diff --git a/e2e/result-cache-8/composer.json b/e2e/result-cache-8/composer.json new file mode 100644 index 0000000000..d29ab86e75 --- /dev/null +++ b/e2e/result-cache-8/composer.json @@ -0,0 +1,14 @@ +{ + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-webmozart-assert": "^1.2" + }, + "autoload": { + "classmap": ["src"] + }, + "autoload-dev": { + "classmap": [ + "build" + ] + } +} diff --git a/e2e/result-cache-8/composer.lock b/e2e/result-cache-8/composer.lock new file mode 100644 index 0000000000..23a7f311ea --- /dev/null +++ b/e2e/result-cache-8/composer.lock @@ -0,0 +1,132 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "964b13a11680dbf7fa5291f0baa6d10c", + "packages": [], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "1.10.63", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "ad12836d9ca227301f5fb9960979574ed8628339" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ad12836d9ca227301f5fb9960979574ed8628339", + "reference": "ad12836d9ca227301f5fb9960979574ed8628339", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2024-03-18T16:53:53+00:00" + }, + { + "name": "phpstan/phpstan-webmozart-assert", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-webmozart-assert.git", + "reference": "d1ff28697bd4e1c9ef5d3f871367ce9092871fec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-webmozart-assert/zipball/d1ff28697bd4e1c9ef5d3f871367ce9092871fec", + "reference": "d1ff28697bd4e1c9ef5d3f871367ce9092871fec", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.10" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "webmozart/assert": "^1.11.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan webmozart/assert extension", + "support": { + "issues": "https://github.com/phpstan/phpstan-webmozart-assert/issues", + "source": "https://github.com/phpstan/phpstan-webmozart-assert/tree/1.2.4" + }, + "time": "2023-02-21T20:34:19+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/e2e/result-cache-8/phpstan.neon b/e2e/result-cache-8/phpstan.neon new file mode 100644 index 0000000000..c7fd83c756 --- /dev/null +++ b/e2e/result-cache-8/phpstan.neon @@ -0,0 +1,17 @@ +includes: + - vendor/phpstan/phpstan-webmozart-assert/extension.neon + +parameters: + paths: + - src + level: 8 + +rules: + - ResultCache8E2E\CustomRule + - ResultCache8E2E\CustomRule3 + +services: + - + class: ResultCache8E2E\CustomRule2 + tags: + - phpstan.rules.rule diff --git a/e2e/result-cache-8/src/CustomRule3.php b/e2e/result-cache-8/src/CustomRule3.php new file mode 100644 index 0000000000..1f0ca326e1 --- /dev/null +++ b/e2e/result-cache-8/src/CustomRule3.php @@ -0,0 +1,24 @@ + + */ +class CustomRule3 implements Rule +{ + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return []; + } + +} diff --git a/e2e/result-cache-8/src/Foo.php b/e2e/result-cache-8/src/Foo.php new file mode 100644 index 0000000000..0082675fde --- /dev/null +++ b/e2e/result-cache-8/src/Foo.php @@ -0,0 +1,8 @@ +getParameter('allConfigFiles')); + /** @var RelativePathHelper $relativePathHelper */ + $relativePathHelper = $container->getService('relativePathHelper'); foreach ($analysedConfigFiles as $analysedConfigFile) { $fileSize = @filesize($analysedConfigFile); if ($fileSize === false) { @@ -253,8 +262,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int continue; } - /** @var RelativePathHelper $relativePathHelper */ - $relativePathHelper = $container->getService('relativePathHelper'); $inceptionResult->getErrorOutput()->getStyle()->warning(sprintf( 'Configuration file %s (%s) is too big and might slow down PHPStan. Consider adding it to excludePaths.', $relativePathHelper->getRelativePath($analysedConfigFile), @@ -262,6 +269,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int )); } + /** @var AnalyseApplication $application */ $application = $container->getByType(AnalyseApplication::class); $debug = $input->getOption('debug'); @@ -526,6 +534,89 @@ protected function execute(InputInterface $input, OutputInterface $output): int $exitCode = 2; } + if ( + $analysisResult->isResultCacheUsed() + && $analysisResult->isResultCacheSaved() + && !$onlyFiles + && $inceptionResult->getProjectConfigArray() !== null + ) { + /** @var FileHelper $fileHelper */ + $fileHelper = $container->getByType(FileHelper::class); + + $vendorDirs = []; + foreach ($this->composerAutoloaderProjectPaths as $autoloaderProjectPath) { + $composer = ComposerHelper::getComposerConfig($autoloaderProjectPath); + if ($composer === null) { + continue; + } + $vendorDirectory = ComposerHelper::getVendorDirFromComposerConfig($autoloaderProjectPath, $composer); + $vendorDirs[] = $fileHelper->normalizePath($vendorDirectory); + } + + $services = ProjectConfigHelper::getServiceClassNames($inceptionResult->getProjectConfigArray()); + natcasesort($services); + $projectServicesNotInAnalysedPaths = []; + $projectServiceFileNamesNotInAnalysedPaths = []; + foreach ($services as $service) { + if (!class_exists($service, false)) { + continue; + } + + $reflection = new ReflectionClass($service); + $fileName = $reflection->getFileName(); + if ($fileName === false) { + continue; + } + + $fileName = $fileHelper->normalizePath($fileName); + if (in_array($fileName, $files, true)) { + continue; + } + + foreach ($vendorDirs as $vendorDir) { + if (str_starts_with($fileName, $vendorDir)) { + continue 2; + } + } + + $projectServicesNotInAnalysedPaths[] = $service; + $projectServiceFileNamesNotInAnalysedPaths[] = $fileName; + } + + if (count($projectServicesNotInAnalysedPaths) > 0) { + $one = count($projectServicesNotInAnalysedPaths) === 1; + $errorOutput->writeLineFormatted('Warning: Result cache might not behave correctly.'); + $errorOutput->writeLineFormatted(sprintf('You\'re using custom %s in your project config', $one ? 'extension' : 'extensions')); + $errorOutput->writeLineFormatted(sprintf('but %s not part of analysed paths:', $one ? 'this extension is' : 'these extensions are')); + $errorOutput->writeLineFormatted(''); + foreach ($projectServicesNotInAnalysedPaths as $service) { + $errorOutput->writeLineFormatted(sprintf('- %s', $service)); + } + + $errorOutput->writeLineFormatted(''); + + $errorOutput->writeLineFormatted('When you edit them and re-run PHPStan, the result cache will get stale.'); + + $directoriesToAdd = []; + foreach ($projectServiceFileNamesNotInAnalysedPaths as $path) { + $directoriesToAdd[] = dirname($relativePathHelper->getRelativePath($path)); + } + + $directoriesToAdd = array_unique($directoriesToAdd); + $oneDirectory = count($directoriesToAdd) === 1; + + $errorOutput->writeLineFormatted(sprintf('Add %s to your analysed paths to get rid of this problem:', $oneDirectory ? 'this directory' : 'these directories')); + + $errorOutput->writeLineFormatted(''); + + foreach ($directoriesToAdd as $directory) { + $errorOutput->writeLineFormatted(sprintf('- %s', $directory)); + } + + $errorOutput->writeLineFormatted(''); + } + } + return $inceptionResult->handleReturn( $exitCode, $analysisResult->getPeakMemoryUsageBytes(),