diff --git a/src/Analyser/Analyser.php b/src/Analyser/Analyser.php index b4928e66550..d62081ca272 100644 --- a/src/Analyser/Analyser.php +++ b/src/Analyser/Analyser.php @@ -75,6 +75,7 @@ public function analyse( $dependencies = []; $usedTraitDependencies = []; $exportedNodes = []; + $externalFileDependencies = []; foreach ($files as $file) { if ($preFileCallback !== null) { $preFileCallback($file); @@ -99,6 +100,10 @@ public function analyse( $collectedData = array_merge($collectedData, $fileAnalyserResult->getCollectedData()); $dependencies[$file] = $fileAnalyserResult->getDependencies(); $usedTraitDependencies[$file] = $fileAnalyserResult->getUsedTraitDependencies(); + $fileExternalDeps = $fileAnalyserResult->getExternalFileDependencies(); + if (count($fileExternalDeps) > 0) { + $externalFileDependencies[$file] = $fileExternalDeps; + } $fileExportedNodes = $fileAnalyserResult->getExportedNodes(); if (count($fileExportedNodes) > 0) { @@ -143,6 +148,7 @@ public function analyse( exportedNodes: $exportedNodes, reachedInternalErrorsCountLimit: $reachedInternalErrorsCountLimit, peakMemoryUsageBytes: memory_get_peak_usage(true), + externalFileDependencies: $internalErrorsCount === 0 ? $externalFileDependencies : null, ); } diff --git a/src/Analyser/AnalyserResult.php b/src/Analyser/AnalyserResult.php index 576471d1ff3..a61263f491f 100644 --- a/src/Analyser/AnalyserResult.php +++ b/src/Analyser/AnalyserResult.php @@ -28,6 +28,7 @@ final class AnalyserResult * @param array>|null $dependencies * @param array>|null $usedTraitDependencies * @param array> $exportedNodes + * @param array>|null $externalFileDependencies */ public function __construct( private array $unorderedErrors, @@ -43,6 +44,7 @@ public function __construct( private array $exportedNodes, private bool $reachedInternalErrorsCountLimit, private int $peakMemoryUsageBytes, + private ?array $externalFileDependencies = null, ) { } @@ -159,6 +161,14 @@ public function getExportedNodes(): array return $this->exportedNodes; } + /** + * @return array>|null + */ + public function getExternalFileDependencies(): ?array + { + return $this->externalFileDependencies; + } + public function hasReachedInternalErrorsCountLimit(): bool { return $this->reachedInternalErrorsCountLimit; diff --git a/src/Analyser/AnalyserResultFinalizer.php b/src/Analyser/AnalyserResultFinalizer.php index e4d5cbd7587..539c8dd2f18 100644 --- a/src/Analyser/AnalyserResultFinalizer.php +++ b/src/Analyser/AnalyserResultFinalizer.php @@ -148,6 +148,7 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $ exportedNodes: $analyserResult->getExportedNodes(), reachedInternalErrorsCountLimit: $analyserResult->hasReachedInternalErrorsCountLimit(), peakMemoryUsageBytes: $analyserResult->getPeakMemoryUsageBytes(), + externalFileDependencies: $analyserResult->getExternalFileDependencies(), ), $collectorErrors, $locallyIgnoredCollectorErrors); } @@ -167,6 +168,7 @@ private function mergeFilteredPhpErrors(AnalyserResult $analyserResult): Analyse exportedNodes: $analyserResult->getExportedNodes(), reachedInternalErrorsCountLimit: $analyserResult->hasReachedInternalErrorsCountLimit(), peakMemoryUsageBytes: $analyserResult->getPeakMemoryUsageBytes(), + externalFileDependencies: $analyserResult->getExternalFileDependencies(), ); } @@ -231,6 +233,7 @@ private function addUnmatchedIgnoredErrors( exportedNodes: $analyserResult->getExportedNodes(), reachedInternalErrorsCountLimit: $analyserResult->hasReachedInternalErrorsCountLimit(), peakMemoryUsageBytes: $analyserResult->getPeakMemoryUsageBytes(), + externalFileDependencies: $analyserResult->getExternalFileDependencies(), ), $collectorErrors, $locallyIgnoredCollectorErrors, diff --git a/src/Analyser/ExternalFileDependencyRegistrar.php b/src/Analyser/ExternalFileDependencyRegistrar.php new file mode 100644 index 00000000000..1ec3526e0ce --- /dev/null +++ b/src/Analyser/ExternalFileDependencyRegistrar.php @@ -0,0 +1,46 @@ + */ + private array $currentFileDependencies = []; + + /** + * Register a dependency on an external file for the currently analyzed file. + */ + public function add(string $externalFilePath): void + { + $this->currentFileDependencies[] = $externalFilePath; + } + + /** + * @return list + * @internal Used by FileAnalyser after each file analysis + */ + public function getAndReset(): array + { + $deps = array_values(array_unique($this->currentFileDependencies)); + $this->currentFileDependencies = []; + + return $deps; + } + +} diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index c59e3545fb2..d292dfd8ed6 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -60,6 +60,7 @@ public function __construct( private IgnoreErrorExtensionProvider $ignoreErrorExtensionProvider, private RuleErrorTransformer $ruleErrorTransformer, private LocalIgnoresProcessor $localIgnoresProcessor, + private ExternalFileDependencyRegistrar $externalFileDependencyRegistrar, #[AutowiredParameter] private bool $reportIgnoresWithoutComments, ) @@ -247,6 +248,7 @@ public function analyseFile( $linesToIgnore, $unmatchedLineIgnores, $processedFiles, + $this->externalFileDependencyRegistrar->getAndReset(), ); } diff --git a/src/Analyser/FileAnalyserResult.php b/src/Analyser/FileAnalyserResult.php index 320b736c6b7..933f19c7356 100644 --- a/src/Analyser/FileAnalyserResult.php +++ b/src/Analyser/FileAnalyserResult.php @@ -25,6 +25,7 @@ final class FileAnalyserResult * @param LinesToIgnore $linesToIgnore * @param LinesToIgnore $unmatchedLineIgnores * @param list $processedFiles + * @param list $externalFileDependencies */ public function __construct( private array $errors, @@ -38,6 +39,7 @@ public function __construct( private array $linesToIgnore, private array $unmatchedLineIgnores, private array $processedFiles, + private array $externalFileDependencies = [], ) { } @@ -130,4 +132,12 @@ public function getProcessedFiles(): array return $this->processedFiles; } + /** + * @return list + */ + public function getExternalFileDependencies(): array + { + return $this->externalFileDependencies; + } + } diff --git a/src/Analyser/ResultCache/ResultCache.php b/src/Analyser/ResultCache/ResultCache.php index 5cfefc6f46a..85a9ddcaaac 100644 --- a/src/Analyser/ResultCache/ResultCache.php +++ b/src/Analyser/ResultCache/ResultCache.php @@ -27,6 +27,7 @@ final class ResultCache * @param array> $exportedNodes * @param array $projectExtensionFiles * @param array $currentFileHashes + * @param array> $externalFileDependencies */ public function __construct( private array $filesToAnalyse, @@ -43,6 +44,7 @@ public function __construct( private array $exportedNodes, private array $projectExtensionFiles, private array $currentFileHashes, + private array $externalFileDependencies = [], ) { } @@ -153,4 +155,14 @@ public function getCurrentFileHashes(): array return $this->currentFileHashes; } + /** + * Inverted external file dependencies: external file => dependent analyzed files. + * + * @return array> + */ + public function getExternalFileDependencies(): array + { + return $this->externalFileDependencies; + } + } diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index 3b929cc59ee..feb66e69962 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -356,6 +356,28 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $filesToAnalyse = []; $invertedDependenciesToReturn = []; $invertedUsedTraitDependenciesToReturn = []; + + // Check external file dependencies for incremental re-analysis + /** @var array}> $cachedExternalDependencies */ + $cachedExternalDependencies = $data['externalDependencies'] ?? []; + $externalDependenciesToReturn = []; + foreach ($cachedExternalDependencies as $externalFile => $externalData) { + $externalDependenciesToReturn[$externalFile] = $externalData['dependentFiles']; + if (is_file($externalFile) && $this->getFileHash($externalFile) === $externalData['fileHash']) { + continue; + } + + if ($output->isVeryVerbose()) { + $output->writeLineFormatted(sprintf('External file %s changed, re-analysing dependent files.', $externalFile)); + } + foreach ($externalData['dependentFiles'] as $dependentFile) { + if (!is_file($dependentFile)) { + continue; + } + + $filesToAnalyse[] = $dependentFile; + } + } $errors = $data['errorsCallback'](); $locallyIgnoredErrors = $data['locallyIgnoredErrorsCallback'](); $linesToIgnore = $data['linesToIgnore']; @@ -515,6 +537,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? exportedNodes: $filteredExportedNodes, projectExtensionFiles: $data['projectExtensionFiles'], currentFileHashes: $currentFileHashes, + externalFileDependencies: $externalDependenciesToReturn, ); } @@ -624,7 +647,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache if ($projectConfigArray !== null) { $meta['projectConfig'] = Neon::encode($projectConfigArray); } - $doSave = function (array $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, ?array $dependencies, ?array $usedTraitDependencies, array $exportedNodes, array $projectExtensionFiles) use ($internalErrors, $resultCache, $output, $onlyFiles, $meta): bool { + $doSave = function (array $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, ?array $dependencies, ?array $usedTraitDependencies, array $exportedNodes, array $projectExtensionFiles, array $externalFileDependencies = []) use ($internalErrors, $resultCache, $output, $onlyFiles, $meta): bool { if ($onlyFiles) { if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache was not saved because only files were passed as analysed paths.'); @@ -672,7 +695,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache } } - $this->save($resultCache->getLastFullAnalysisTime(), $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, $dependencies, $usedTraitDependencies, $exportedNodes, $projectExtensionFiles, $resultCache->getCurrentFileHashes(), $meta); + $this->save($resultCache->getLastFullAnalysisTime(), $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, $dependencies, $usedTraitDependencies, $exportedNodes, $projectExtensionFiles, $resultCache->getCurrentFileHashes(), $meta, $externalFileDependencies); if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache is saved.'); @@ -688,7 +711,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache if ($analyserResult->getDependencies() !== null) { $projectExtensionFiles = $this->getProjectExtensionFiles($projectConfigArray, $analyserResult->getDependencies()); } - $saved = $doSave($freshErrorsByFile, $freshLocallyIgnoredErrorsByFile, $analyserResult->getLinesToIgnore(), $analyserResult->getUnmatchedLineIgnores(), $freshCollectedDataByFile, $analyserResult->getDependencies(), $analyserResult->getUsedTraitDependencies(), $analyserResult->getExportedNodes(), $projectExtensionFiles); + $saved = $doSave($freshErrorsByFile, $freshLocallyIgnoredErrorsByFile, $analyserResult->getLinesToIgnore(), $analyserResult->getUnmatchedLineIgnores(), $freshCollectedDataByFile, $analyserResult->getDependencies(), $analyserResult->getUsedTraitDependencies(), $analyserResult->getExportedNodes(), $projectExtensionFiles, $analyserResult->getExternalFileDependencies() ?? []); } else { if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache was not saved because it was not requested.'); @@ -706,6 +729,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache $exportedNodes = $this->mergeExportedNodes($resultCache, $analyserResult->getExportedNodes()); $linesToIgnore = $this->mergeLinesToIgnore($resultCache, $analyserResult->getLinesToIgnore()); $unmatchedLineIgnores = $this->mergeUnmatchedLineIgnores($resultCache, $analyserResult->getUnmatchedLineIgnores()); + $externalFileDependencies = $this->mergeExternalFileDependencies($resultCache->getExternalFileDependencies(), $resultCache->getFilesToAnalyse(), $analyserResult->getExternalFileDependencies()); $saved = false; if ($save !== false) { @@ -729,7 +753,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache $projectExtensionFiles[$file] = [$hash, true, $className]; } } - $saved = $doSave($errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, $dependencies, $usedTraitDependencies, $exportedNodes, $projectExtensionFiles); + $saved = $doSave($errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, $dependencies, $usedTraitDependencies, $exportedNodes, $projectExtensionFiles, $externalFileDependencies); } $flatErrors = []; @@ -760,6 +784,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache exportedNodes: $exportedNodes, reachedInternalErrorsCountLimit: $analyserResult->hasReachedInternalErrorsCountLimit(), peakMemoryUsageBytes: $analyserResult->getPeakMemoryUsageBytes(), + externalFileDependencies: $externalFileDependencies !== [] ? $externalFileDependencies : null, ), $saved); } @@ -944,6 +969,46 @@ private function mergeUnmatchedLineIgnores(ResultCache $resultCache, array $fres return $newUnmatchedLineIgnores; } + /** + * Merges cached inverted external dependencies with fresh analysis results. + * + * @param array> $cachedExternalDependencies Inverted: external file => dependent analyzed files + * @param string[] $filesToAnalyse Files that were re-analyzed + * @param array>|null $freshExternalDependencies Non-inverted: analyzed file => external files + * @return array> Non-inverted: analyzed file => external files + */ + private function mergeExternalFileDependencies( + array $cachedExternalDependencies, + array $filesToAnalyse, + ?array $freshExternalDependencies, + ): array + { + if ($freshExternalDependencies === null) { + return []; + } + + // Un-invert cached external dependencies: external file => [dependents] → dependent => [external files] + $cachedPerFile = []; + foreach ($cachedExternalDependencies as $externalFile => $dependentFiles) { + foreach ($dependentFiles as $dependentFile) { + $cachedPerFile[$dependentFile][] = $externalFile; + } + } + + // Replace re-analyzed files with fresh data + $merged = $cachedPerFile; + foreach ($filesToAnalyse as $file) { + unset($merged[$file]); + if (!array_key_exists($file, $freshExternalDependencies)) { + continue; + } + + $merged[$file] = $freshExternalDependencies[$file]; + } + + return $merged; + } + /** * @param array> $errors * @param array> $locallyIgnoredErrors @@ -956,6 +1021,7 @@ private function mergeUnmatchedLineIgnores(ResultCache $resultCache, array $fres * @param array $projectExtensionFiles * @param array $currentFileHashes * @param mixed[] $meta + * @param array> $externalFileDependencies */ private function save( int $lastFullAnalysisTime, @@ -970,6 +1036,7 @@ private function save( array $projectExtensionFiles, array $currentFileHashes, array $meta, + array $externalFileDependencies = [], ): void { $invertedDependencies = []; @@ -1043,6 +1110,31 @@ private function save( ksort($exportedNodes); + // Build inverted external dependencies: external file => {hash, dependentFiles} + $invertedExternalDependencies = []; + foreach ($externalFileDependencies as $analysedFile => $externalFiles) { + foreach ($externalFiles as $externalFile) { + if (!array_key_exists($externalFile, $invertedExternalDependencies)) { + if (!is_file($externalFile)) { + continue; + } + $invertedExternalDependencies[$externalFile] = [ + 'fileHash' => $this->getFileHash($externalFile), + 'dependentFiles' => [], + ]; + } + $invertedExternalDependencies[$externalFile]['dependentFiles'][] = $analysedFile; + } + } + + foreach ($invertedExternalDependencies as $externalFile => $externalData) { + $dependentFiles = array_values(array_unique($externalData['dependentFiles'])); + sort($dependentFiles); + $invertedExternalDependencies[$externalFile]['dependentFiles'] = $dependentFiles; + } + + ksort($invertedExternalDependencies); + $file = $this->cacheFilePath; FileWriter::write( @@ -1059,7 +1151,8 @@ private function save( 'unmatchedLineIgnores' => " . var_export($unmatchedLineIgnores, true) . ", 'collectedDataCallback' => static function (): array { return " . var_export($collectedData, true) . "; }, 'dependencies' => " . var_export($invertedDependencies, true) . ", - 'exportedNodesCallback' => static function (): array { return " . var_export($exportedNodes, true) . '; }, + 'exportedNodesCallback' => static function (): array { return " . var_export($exportedNodes, true) . "; }, + 'externalDependencies' => " . var_export($invertedExternalDependencies, true) . ', ]; ', ); diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index 3b6d5842b4f..62bc508d332 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -121,6 +121,7 @@ public function analyse( exportedNodes: $intermediateAnalyserResult->getExportedNodes(), reachedInternalErrorsCountLimit: $intermediateAnalyserResult->hasReachedInternalErrorsCountLimit(), peakMemoryUsageBytes: $intermediateAnalyserResult->getPeakMemoryUsageBytes(), + externalFileDependencies: $intermediateAnalyserResult->getExternalFileDependencies(), ); } @@ -346,6 +347,7 @@ private function switchTmpFileInAnalyserResult( exportedNodes: $exportedNodes, reachedInternalErrorsCountLimit: $analyserResult->hasReachedInternalErrorsCountLimit(), peakMemoryUsageBytes: $analyserResult->getPeakMemoryUsageBytes(), + externalFileDependencies: $analyserResult->getExternalFileDependencies(), ); } diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index 2a221f6e846..88b1c8e7a82 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -29,6 +29,7 @@ use function array_merge; use function array_unshift; use function array_values; +use function count; use function defined; use function is_array; use function is_bool; @@ -228,6 +229,7 @@ private function runWorker( $dependencies = []; $usedTraitDependencies = []; $exportedNodes = []; + $externalFileDependencies = []; foreach ($files as $file) { try { if ($file === $insteadOfFile) { @@ -242,6 +244,10 @@ private function runWorker( $dependencies[$file] = $fileAnalyserResult->getDependencies(); $usedTraitDependencies[$file] = $fileAnalyserResult->getUsedTraitDependencies(); $exportedNodes[$file] = $fileAnalyserResult->getExportedNodes(); + $fileExternalDeps = $fileAnalyserResult->getExternalFileDependencies(); + if (count($fileExternalDeps) > 0) { + $externalFileDependencies[$file] = $fileExternalDeps; + } foreach ($fileErrors as $fileError) { $errors[] = $fileError; } @@ -282,6 +288,7 @@ private function runWorker( 'dependencies' => $dependencies, 'usedTraitDependencies' => $usedTraitDependencies, 'exportedNodes' => $exportedNodes, + 'externalFileDependencies' => $externalFileDependencies, 'files' => $files, 'internalErrorsCount' => $internalErrorsCount, ]]); diff --git a/src/Parallel/ParallelAnalyser.php b/src/Parallel/ParallelAnalyser.php index 7792a0cb6ba..9dcd9c16963 100644 --- a/src/Parallel/ParallelAnalyser.php +++ b/src/Parallel/ParallelAnalyser.php @@ -92,12 +92,13 @@ public function analyse( $usedTraitDependencies = []; $reachedInternalErrorsCountLimit = false; $exportedNodes = []; + $externalFileDependencies = []; /** @var Deferred $deferred */ $deferred = new Deferred(); $server = new TcpServer('127.0.0.1:0', $loop); - $this->processPool = new ProcessPool($server, static function () use ($deferred, &$jobs, &$internalErrors, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, &$errors, &$filteredPhpErrors, &$allPhpErrors, &$locallyIgnoredErrors, &$linesToIgnore, &$unmatchedLineIgnores, &$collectedData, &$dependencies, &$usedTraitDependencies, &$exportedNodes, &$peakMemoryUsages): void { + $this->processPool = new ProcessPool($server, static function () use ($deferred, &$jobs, &$internalErrors, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, &$errors, &$filteredPhpErrors, &$allPhpErrors, &$locallyIgnoredErrors, &$linesToIgnore, &$unmatchedLineIgnores, &$collectedData, &$dependencies, &$usedTraitDependencies, &$exportedNodes, &$externalFileDependencies, &$peakMemoryUsages): void { if (count($jobs) > 0 && $internalErrorsCount === 0) { $internalErrors[] = new InternalError( 'Some parallel worker jobs have not finished.', @@ -123,6 +124,7 @@ public function analyse( exportedNodes: $exportedNodes, reachedInternalErrorsCountLimit: $reachedInternalErrorsCountLimit, peakMemoryUsageBytes: array_sum($peakMemoryUsages), // not 100% correct as the peak usages of workers might not have met + externalFileDependencies: $internalErrorsCount === 0 ? $externalFileDependencies : null, )); }); $server->on('connection', function (ConnectionInterface $connection) use (&$jobs): void { @@ -194,7 +196,7 @@ public function analyse( $commandOptions, $input, ), $loop, $this->processTimeout); - $process->start(function (array $json) use ($process, &$internalErrors, &$errors, &$filteredPhpErrors, &$allPhpErrors, &$locallyIgnoredErrors, &$linesToIgnore, &$unmatchedLineIgnores, &$collectedData, &$dependencies, &$usedTraitDependencies, &$exportedNodes, &$peakMemoryUsages, &$jobs, $postFileCallback, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, $processIdentifier, $onFileAnalysisHandler): void { + $process->start(function (array $json) use ($process, &$internalErrors, &$errors, &$filteredPhpErrors, &$allPhpErrors, &$locallyIgnoredErrors, &$linesToIgnore, &$unmatchedLineIgnores, &$collectedData, &$dependencies, &$usedTraitDependencies, &$exportedNodes, &$externalFileDependencies, &$peakMemoryUsages, &$jobs, $postFileCallback, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, $processIdentifier, $onFileAnalysisHandler): void { $fileErrors = []; foreach ($json['errors'] as $jsonError) { $fileErrors[] = Error::decode($jsonError); @@ -252,6 +254,14 @@ public function analyse( $usedTraitDependencies[$file] = $fileUsedTraitDependencies; } + /** + * @var string $file + * @var list $fileExternalDeps + */ + foreach ($json['externalFileDependencies'] ?? [] as $file => $fileExternalDeps) { + $externalFileDependencies[$file] = $fileExternalDeps; + } + foreach ($json['linesToIgnore'] as $file => $fileLinesToIgnore) { if (count($fileLinesToIgnore) === 0) { continue; diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 0565407424e..42c23625dd4 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Analyser; use PHPStan\Analyser\AnalyserResultFinalizer; use PHPStan\Analyser\Error; +use PHPStan\Analyser\ExternalFileDependencyRegistrar; use PHPStan\Analyser\Fiber\FiberNodeScopeResolver; use PHPStan\Analyser\FileAnalyser; use PHPStan\Analyser\IgnoreErrorExtensionProvider; @@ -137,6 +138,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser new IgnoreErrorExtensionProvider(self::getContainer()), self::getContainer()->getByType(RuleErrorTransformer::class), new LocalIgnoresProcessor(), + new ExternalFileDependencyRegistrar(), false, ); $this->analyser = new Analyser( diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 27f9daddb07..13bbc4a94e3 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -847,6 +847,7 @@ private function createAnalyser(): Analyser new IgnoreErrorExtensionProvider(new NetteContainer(new Container([]))), $container->getByType(RuleErrorTransformer::class), new LocalIgnoresProcessor(), + new ExternalFileDependencyRegistrar(), false, );