Skip to content

Commit ddc24ce

Browse files
committed
[Caching] Invalidate on configured rule value change, reuse full-run cache for --only
1 parent c1c61c4 commit ddc24ce

5 files changed

Lines changed: 71 additions & 2 deletions

File tree

‎src/Caching/Detector/ChangedFilesDetector.php‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,14 @@ public function addCacheableFile(string $filePath): void
6161

6262
public function hasFileChanged(string $filePath): bool
6363
{
64-
$fileInfoCacheKey = $this->getFilePathCacheKey($filePath);
65-
$cachedValue = $this->cache->load($fileInfoCacheKey, CacheKey::FILE_HASH_KEY);
64+
$cachedValue = $this->cache->load($this->getFilePathCacheKey($filePath), CacheKey::FILE_HASH_KEY);
65+
66+
// a scoped (--only) run reuses the full-run cache: a file left clean by all rules stays
67+
// clean under a single rule too, and the content is still compared below
68+
if ($cachedValue === null && $this->scopeSuffix !== '') {
69+
$unscopedCacheKey = $this->fileHasher->hash($this->resolvePath($filePath));
70+
$cachedValue = $this->cache->load($unscopedCacheKey, CacheKey::FILE_HASH_KEY);
71+
}
6672

6773
if ($cachedValue !== null) {
6874
$currentFileHash = $this->hashFile($filePath);

‎src/Config/RectorConfig.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ public function ruleWithConfiguration(string $rectorClass, array $configuration)
215215
$configuration
216216
);
217217

218+
// feed values into the cache hash, so a changed configuration invalidates the cache
219+
SimpleParameterProvider::setParameter(Option::RULE_CONFIGURATIONS, $this->ruleConfigurations);
220+
218221
$this->rule($rectorClass);
219222

220223
$this->afterResolving($rectorClass, function (ConfigurableRectorInterface $configurableRector) use (

‎src/Configuration/Option.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ final class Option
218218
*/
219219
public const string REGISTERED_RECTOR_SETS = 'registered_rector_sets';
220220

221+
/**
222+
* @internal For cache invalidation when a configurable rule value changes
223+
*/
224+
public const string RULE_CONFIGURATIONS = 'rule_configurations';
225+
221226
/**
222227
* @internal For verify RectorConfigBuilder instance recreated
223228
*/

‎tests/Caching/Config/FileHashComputer/FileHashComputerTest.php‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ final class FileHashComputerTest extends AbstractLazyTestCase
2323
*/
2424
private array $originalFileExtensions = [];
2525

26+
/**
27+
* @var mixed[]
28+
*/
29+
private array $originalRuleConfigurations = [];
30+
2631
protected function setUp(): void
2732
{
2833
parent::setUp();
@@ -32,12 +37,14 @@ protected function setUp(): void
3237
// the parameter bag is a global static shared across the whole test process, restore it after
3338
$this->originalRules = SimpleParameterProvider::provideArrayParameter(Option::REGISTERED_RECTOR_RULES);
3439
$this->originalFileExtensions = SimpleParameterProvider::provideArrayParameter(Option::FILE_EXTENSIONS);
40+
$this->originalRuleConfigurations = SimpleParameterProvider::provideArrayParameter(Option::RULE_CONFIGURATIONS);
3541
}
3642

3743
protected function tearDown(): void
3844
{
3945
SimpleParameterProvider::setParameter(Option::REGISTERED_RECTOR_RULES, $this->originalRules);
4046
SimpleParameterProvider::setParameter(Option::FILE_EXTENSIONS, $this->originalFileExtensions);
47+
SimpleParameterProvider::setParameter(Option::RULE_CONFIGURATIONS, $this->originalRuleConfigurations);
4148
}
4249

4350
public function testOutputAffectingParameterChangesHash(): void
@@ -66,4 +73,22 @@ public function testRuleChangeIsExcludedFromHash(): void
6673

6774
$this->assertSame($hashBefore, $hashAfter);
6875
}
76+
77+
public function testConfiguredRuleValueChangeChangesHash(): void
78+
{
79+
$configFilePath = __DIR__ . '/Fixture/rector.php';
80+
81+
SimpleParameterProvider::setParameter(Option::RULE_CONFIGURATIONS, [
82+
'Rector\\SomeRule' => ['old value'],
83+
]);
84+
$hashBefore = $this->fileHashComputer->compute($configFilePath);
85+
86+
// same rule, changed configuration value must invalidate the cache
87+
SimpleParameterProvider::setParameter(Option::RULE_CONFIGURATIONS, [
88+
'Rector\\SomeRule' => ['new value'],
89+
]);
90+
$hashAfter = $this->fileHashComputer->compute($configFilePath);
91+
92+
$this->assertNotSame($hashBefore, $hashAfter);
93+
}
6994
}

‎tests/Caching/Detector/ChangedFilesDetectorTest.php‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,36 @@ public function testHasFileChanged(): void
6363
$this->assertTrue($this->changedFilesDetector->hasFileChanged($filePath));
6464
}
6565

66+
public function testScopedRunReusesFullRunCache(): void
67+
{
68+
$filePath = __DIR__ . '/Source/file.php';
69+
70+
// full run caches the file as clean
71+
$this->changedFilesDetector->setActiveScope(null, null);
72+
$this->changedFilesDetector->addCacheableFile($filePath);
73+
$this->changedFilesDetector->cacheFile($filePath);
74+
$this->assertFalse($this->changedFilesDetector->hasFileChanged($filePath));
75+
76+
// an --only run reuses the full-run cache instead of re-analysing the file
77+
$this->changedFilesDetector->setActiveScope('Rector\\SomeRule', null);
78+
$this->assertFalse($this->changedFilesDetector->hasFileChanged($filePath));
79+
}
80+
81+
public function testScopedCacheDoesNotLeakToFullRun(): void
82+
{
83+
$filePath = __DIR__ . '/Source/file.php';
84+
85+
// a scoped run only caches under its own key
86+
$this->changedFilesDetector->setActiveScope('Rector\\SomeRule', null);
87+
$this->changedFilesDetector->addCacheableFile($filePath);
88+
$this->changedFilesDetector->cacheFile($filePath);
89+
$this->assertFalse($this->changedFilesDetector->hasFileChanged($filePath));
90+
91+
// a full run must not treat the file as cached, as only one rule ran
92+
$this->changedFilesDetector->setActiveScope(null, null);
93+
$this->assertTrue($this->changedFilesDetector->hasFileChanged($filePath));
94+
}
95+
6696
public function testCacheKeptWhenRuleRemoved(): void
6797
{
6898
$filePath = $this->cacheFileUnderRules(['Rector\\RuleA', 'Rector\\RuleB']);

0 commit comments

Comments
 (0)