diff --git a/src/Caching/Detector/ChangedFilesDetector.php b/src/Caching/Detector/ChangedFilesDetector.php index 173cfabc055..5ff2fee21c6 100644 --- a/src/Caching/Detector/ChangedFilesDetector.php +++ b/src/Caching/Detector/ChangedFilesDetector.php @@ -26,6 +26,13 @@ final class ChangedFilesDetector // scopes the per-file cache key to the active --only / --only-suffix / --filter selection (empty = full run) private string $scopeSuffix = ''; + private ?string $configurationCacheKey = null; + + /** + * @var array{hash: string, rules: string[], sets: string[], skip: string[]}|null + */ + private ?array $configurationSnapshot = null; + public function __construct( private readonly FileHashComputer $fileHashComputer, private readonly Cache $cache, @@ -95,6 +102,15 @@ public function invalidateFile(string $filePath): void public function clear(): void { $this->cache->clear(); + + // keep the snapshot of the current run, so the next run can still detect a configuration change + if ($this->configurationCacheKey !== null && $this->configurationSnapshot !== null) { + $this->cache->save( + $this->configurationCacheKey, + CacheKey::CONFIGURATION_HASH_KEY, + $this->configurationSnapshot + ); + } } public function setFirstResolvedConfigFileInfo(string $filePath): void @@ -141,7 +157,12 @@ private function createConfigurationSnapshot(string $filePath): array private function storeConfigurationDataHash(string $filePath, array $configurationSnapshot): void { $key = CacheKey::CONFIGURATION_HASH_KEY . '_' . $this->getFilePathCacheKey($filePath); - $this->invalidateCacheIfConfigurationChanged($key, $configurationSnapshot); + $oldCachedValue = $this->cache->load($key, CacheKey::CONFIGURATION_HASH_KEY); + + $this->configurationCacheKey = $key; + $this->configurationSnapshot = $configurationSnapshot; + + $this->invalidateCacheIfConfigurationChanged($oldCachedValue, $configurationSnapshot); $this->cache->save($key, CacheKey::CONFIGURATION_HASH_KEY, $configurationSnapshot); } @@ -149,10 +170,8 @@ private function storeConfigurationDataHash(string $filePath, array $configurati /** * @param array{hash: string, rules: string[], sets: string[], skip: string[]} $configurationSnapshot */ - private function invalidateCacheIfConfigurationChanged(string $key, array $configurationSnapshot): void + private function invalidateCacheIfConfigurationChanged(mixed $oldCachedValue, array $configurationSnapshot): void { - $oldCachedValue = $this->cache->load($key, CacheKey::CONFIGURATION_HASH_KEY); - // first run, nothing to compare against if ($oldCachedValue === null) { return; diff --git a/tests/Caching/Detector/ChangedFilesDetectorTest.php b/tests/Caching/Detector/ChangedFilesDetectorTest.php index a904f939c04..2d2f49160fc 100644 --- a/tests/Caching/Detector/ChangedFilesDetectorTest.php +++ b/tests/Caching/Detector/ChangedFilesDetectorTest.php @@ -137,6 +137,26 @@ public function testCacheClearedWhenSkipRemoved(): void $this->assertTrue($this->changedFilesDetector->hasFileChanged($filePath)); } + public function testCacheClearedWhenSkipRemovedAfterClearCacheRun(): void + { + SimpleParameterProvider::setParameter(Option::SKIP, ['Rector\\RuleA']); + $this->applyRules(['Rector\\RuleA', 'Rector\\RuleB']); + + // --clear-cache wipes the cache after the configuration snapshot was stored + $this->changedFilesDetector->clear(); + + $filePath = __DIR__ . '/Source/file.php'; + $this->changedFilesDetector->addCacheableFile($filePath); + $this->changedFilesDetector->cacheFile($filePath); + $this->assertFalse($this->changedFilesDetector->hasFileChanged($filePath)); + + // the next run removes the skip, the snapshot of the cleared run must still be there to compare + SimpleParameterProvider::setParameter(Option::SKIP, []); + $this->snapshotConfiguration(); + + $this->assertTrue($this->changedFilesDetector->hasFileChanged($filePath)); + } + public function testCacheClearedWhenOutputAffectingParameterChanged(): void { SimpleParameterProvider::setParameter(Option::FILE_EXTENSIONS, ['php']);