From 7be6feb70d737272c90e26948eb7da3240d0acd5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 21 May 2025 11:44:00 +0200 Subject: [PATCH 1/2] Fix reported file path for `--tmp-file` and ignoring errors --- .github/workflows/e2e-tests.yml | 3 +- src/Command/AnalyseApplication.php | 142 ++++++++++++++++++++++++++++- 2 files changed, 143 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9a351d6332..eb5856de18 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -206,8 +206,9 @@ jobs: OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw --tmp-file differentFoo.php --instead-of src/Foo.php") echo "$OUTPUT" - ../bashunit -a contains 'differentFoo.php:10:Method EditorModeE2E\Foo::doFoo() should return float but returns string. [identifier=return.type]' "$OUTPUT" + ../bashunit -a contains 'Foo.php:10:Method EditorModeE2E\Foo::doFoo() should return float but returns string. [identifier=return.type]' "$OUTPUT" ../bashunit -a not_contains 'Foo.php:10:Method EditorModeE2E\Foo::doFoo() should return int but returns string. [identifier=return.type]' "$OUTPUT" + ../bashunit -a not_contains 'differentFoo.php' "$OUTPUT" ../bashunit -a contains 'Bar.php:10:Parameter #1 $s of method EditorModeE2E\Bar::requireString() expects string, float given. [identifier=argument.type]' "$OUTPUT" ../bashunit -a contains 'Result cache restored. 2 files will be reanalysed.' "$OUTPUT" ../bashunit -a contains 'Result cache was not saved because of --tmp-file and --instead-of CLI options passed (editor mode).' "$OUTPUT" diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index 009bd17d97..b96bc347df 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -4,6 +4,8 @@ use PHPStan\Analyser\AnalyserResult; use PHPStan\Analyser\AnalyserResultFinalizer; +use PHPStan\Analyser\Error; +use PHPStan\Analyser\FileAnalyserResult; use PHPStan\Analyser\Ignore\IgnoredErrorHelper; use PHPStan\Analyser\ResultCache\ResultCacheManagerFactory; use PHPStan\Internal\BytesHelper; @@ -19,6 +21,9 @@ use function sha1_file; use function sprintf; +/** + * @phpstan-import-type LinesToIgnore from FileAnalyserResult + */ final class AnalyseApplication { @@ -111,7 +116,11 @@ public function analyse( } $resultCacheResult = $resultCacheManager->process($intermediateAnalyserResult, $resultCache, $errorOutput, $onlyFiles, true); - $analyserResult = $this->analyserResultFinalizer->finalize($resultCacheResult->getAnalyserResult(), $onlyFiles, $debug)->getAnalyserResult(); + $analyserResult = $this->analyserResultFinalizer->finalize( + $this->switchTmpFileInAnalyserResult($resultCacheResult->getAnalyserResult(), $insteadOfFile, $tmpFile), + $onlyFiles, + $debug, + )->getAnalyserResult(); $internalErrors = $analyserResult->getInternalErrors(); $errors = array_merge( $analyserResult->getErrors(), @@ -232,4 +241,135 @@ private function runAnalyser( return $analyserResult; } + private function switchTmpFileInAnalyserResult( + AnalyserResult $analyserResult, + ?string $insteadOfFile, + ?string $tmpFile, + ): AnalyserResult + { + if ($insteadOfFile === null || $tmpFile === null) { + return $analyserResult; + } + + $collectedData = []; + foreach ($analyserResult->getCollectedData() as $data) { + if ($data->getFilePath() === $tmpFile) { + $data = $data->changeFilePath($insteadOfFile); + } + + $collectedData[] = $data; + } + + $dependencies = null; + if ($analyserResult->getDependencies() !== null) { + $dependencies = $this->switchTmpFileInDependencies($analyserResult->getDependencies(), $insteadOfFile, $tmpFile); + } + $usedTraitDependencies = null; + if ($analyserResult->getUsedTraitDependencies() !== null) { + $usedTraitDependencies = $this->switchTmpFileInDependencies($analyserResult->getUsedTraitDependencies(), $insteadOfFile, $tmpFile); + } + + $exportedNodes = []; + foreach ($analyserResult->getExportedNodes() as $file => $fileExportedNodes) { + if ($file === $tmpFile) { + $file = $insteadOfFile; + } + + $exportedNodes[$file] = $fileExportedNodes; + } + + return new AnalyserResult( + $this->switchTmpFileInErrors($analyserResult->getUnorderedErrors(), $insteadOfFile, $tmpFile), + $this->switchTmpFileInErrors($analyserResult->getFilteredPhpErrors(), $insteadOfFile, $tmpFile), + $this->switchTmpFileInErrors($analyserResult->getAllPhpErrors(), $insteadOfFile, $tmpFile), + $this->switchTmpFileInErrors($analyserResult->getLocallyIgnoredErrors(), $insteadOfFile, $tmpFile), + $this->swittchTmpFileInLinesToIgnore($analyserResult->getLinesToIgnore(), $insteadOfFile, $tmpFile), + $this->swittchTmpFileInLinesToIgnore($analyserResult->getUnmatchedLineIgnores(), $insteadOfFile, $tmpFile), + $analyserResult->getInternalErrors(), + $collectedData, + $dependencies, + $usedTraitDependencies, + $exportedNodes, + $analyserResult->hasReachedInternalErrorsCountLimit(), + $analyserResult->getPeakMemoryUsageBytes(), + ); + } + + /** + * @param array> $dependencies + * @return array> + */ + private function switchTmpFileInDependencies(array $dependencies, string $insteadOfFile, string $tmpFile): array + { + $newDependencies = []; + foreach ($dependencies as $dependencyFile => $dependentFiles) { + $new = []; + foreach ($dependentFiles as $file) { + if ($file === $tmpFile) { + $new[] = $insteadOfFile; + continue; + } + + $new[] = $file; + } + + $key = $dependencyFile; + if ($key === $tmpFile) { + $key = $insteadOfFile; + } + + $newDependencies[$key] = $new; + } + + return $newDependencies; + } + + /** + * @param list $errors + * @return list + */ + private function switchTmpFileInErrors(array $errors, string $insteadOfFile, string $tmpFile): array + { + $newErrors = []; + foreach ($errors as $error) { + if ($error->getFilePath() === $tmpFile) { + $error = $error->changeFilePath($insteadOfFile); + } + if ($error->getTraitFilePath() === $tmpFile) { + $error = $error->changeTraitFilePath($insteadOfFile); + } + + $newErrors[] = $error; + } + + return $newErrors; + } + + /** + * @param array $linesToIgnore + * @return array + */ + private function swittchTmpFileInLinesToIgnore(array $linesToIgnore, string $insteadOfFile, string $tmpFile): array + { + $newLinesToIgnore = []; + foreach ($linesToIgnore as $file => $lines) { + if ($file === $tmpFile) { + $file = $insteadOfFile; + } + + $newLines = []; + foreach ($lines as $f => $line) { + if ($f === $tmpFile) { + $f = $insteadOfFile; + } + + $newLines[$f] = $line; + } + + $newLinesToIgnore[$file] = $newLines; + } + + return $newLinesToIgnore; + } + } From fc1833a2c04998a2c9bbdfe9e147e83ec567a3b4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 21 May 2025 22:07:37 +0200 Subject: [PATCH 2/2] File passed to `--tmp-file` cannot be in project files --- src/Command/AnalyseCommand.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 81dedfb56e..7452846e54 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -286,6 +286,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } + if ($inceptionResult->getEditorModeTmpFile() !== null) { + if (in_array($inceptionResult->getEditorModeTmpFile(), $files, true)) { + $inceptionResult->getStdOutput()->getStyle()->error(sprintf('File %s passed to --tmp-file is already in analysed project files.', $inceptionResult->getEditorModeInsteadOfFile())); + return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); + } + } + $analysedConfigFiles = array_intersect($files, $container->getParameter('allConfigFiles')); /** @var RelativePathHelper $relativePathHelper */ $relativePathHelper = $container->getService('relativePathHelper');