From 8c875fd9627d0dae01b0f567e14518ea7085ac47 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 15 Nov 2026 23:36:07 +0700 Subject: [PATCH 1/2] [Caching] Fix removed skip not invalidating cache after --clear-cache run --- src/Caching/Detector/ChangedFilesDetector.php | 19 ++++++++++++++++++ .../Detector/ChangedFilesDetectorTest.php | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/Caching/Detector/ChangedFilesDetector.php b/src/Caching/Detector/ChangedFilesDetector.php index 173cfabc055..f3e5056a217 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 @@ -143,6 +159,9 @@ private function storeConfigurationDataHash(string $filePath, array $configurati $key = CacheKey::CONFIGURATION_HASH_KEY . '_' . $this->getFilePathCacheKey($filePath); $this->invalidateCacheIfConfigurationChanged($key, $configurationSnapshot); + $this->configurationCacheKey = $key; + $this->configurationSnapshot = $configurationSnapshot; + $this->cache->save($key, CacheKey::CONFIGURATION_HASH_KEY, $configurationSnapshot); } 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']); From bbe48f096d188e60c1babfd8d237d56b7894f30d Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 15 Nov 2026 23:38:44 +0700 Subject: [PATCH 2/2] fix extra --- src/Caching/Detector/ChangedFilesDetector.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Caching/Detector/ChangedFilesDetector.php b/src/Caching/Detector/ChangedFilesDetector.php index f3e5056a217..5ff2fee21c6 100644 --- a/src/Caching/Detector/ChangedFilesDetector.php +++ b/src/Caching/Detector/ChangedFilesDetector.php @@ -157,21 +157,21 @@ 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); } /** * @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;