Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/Application/ApplicationFileProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public function __construct(

public function run(Configuration $configuration, InputInterface $input): ProcessResult
{
// scope the cache to this run's --only / --only-suffix selection before any cache read/write
$this->changedFilesDetector->setActiveScope($configuration->getOnlyRule(), $configuration->getOnlySuffix());

$filePaths = $this->filesFinder->findFilesInPaths($configuration->getPaths(), $configuration);

// no files found
Expand Down Expand Up @@ -121,6 +124,9 @@ public function processFiles(
?callable $preFileCallback = null,
?callable $postFileCallback = null
): ProcessResult {
// also set here: parallel workers reach processFiles() via WorkerCommand, bypassing run()
$this->changedFilesDetector->setActiveScope($configuration->getOnlyRule(), $configuration->getOnlySuffix());

/** @var SystemError[] $systemErrors */
$systemErrors = [];

Expand Down Expand Up @@ -179,11 +185,8 @@ private function processFile(File $file, Configuration $configuration): FileProc
if ($fileProcessResult->getSystemErrors() !== []) {
$this->changedFilesDetector->invalidateFile($file->getFilePath());
} elseif (! $configuration->isDryRun() || ! $fileProcessResult->getFileDiff() instanceof FileDiff) {
// a file clean under a subset of rules is not necessarily clean under all rules,
// caching it would hide its pending changes from the next full run
if ($configuration->getOnlyRule() === null && $configuration->getOnlySuffix() === null) {
$this->changedFilesDetector->cacheFile($file->getFilePath());
}
// selective runs are safe to cache now — the key is scoped to the rule selection
$this->changedFilesDetector->cacheFile($file->getFilePath());
}

return $fileProcessResult;
Expand Down
13 changes: 12 additions & 1 deletion src/Caching/Detector/ChangedFilesDetector.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,24 @@ final class ChangedFilesDetector
*/
private array $cacheableFiles = [];

// scopes the per-file cache key to the active --only / --only-suffix selection (empty = full run)
private string $scopeSuffix = '';

public function __construct(
private readonly FileHashComputer $fileHashComputer,
private readonly Cache $cache,
private readonly FileHasher $fileHasher
) {
}

public function setActiveScope(?string $onlyRule, ?string $onlySuffix): void
{
// each selection gets its own cache key, so --only and full runs coexist without clearing or poisoning
$this->scopeSuffix = ($onlyRule === null && $onlySuffix === null)
? ''
: '|only:' . ($onlyRule ?? '') . '|suffix:' . ($onlySuffix ?? '');
}

public function cacheFile(string $filePath): void
{
$filePathCacheKey = $this->getFilePathCacheKey($filePath);
Expand Down Expand Up @@ -95,7 +106,7 @@ private function resolvePath(string $filePath): string

private function getFilePathCacheKey(string $filePath): string
{
return $this->fileHasher->hash($this->resolvePath($filePath));
return $this->fileHasher->hash($this->resolvePath($filePath) . $this->scopeSuffix);
}

private function hashFile(string $filePath): string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function testCleanFileIsCachedAsUnchanged(): void
$this->assertFalse($this->changedFilesDetector->hasFileChanged($filePath));
}

public function testOnlyRuleRunDoesNotCacheFileAsUnchanged(): void
public function testOnlyRuleRunCachesUnderOwnScopeWithoutPoisoningFullRun(): void
{
$filePath = __DIR__ . '/Source/CleanFile.php';

Expand All @@ -47,11 +47,16 @@ public function testOnlyRuleRunDoesNotCacheFileAsUnchanged(): void
onlyRule: RemoveEmptyClassMethodRector::class
));

// a file clean under one rule is not necessarily clean under all rules
// a repeated --only run hits its own scoped cache entry
$this->changedFilesDetector->setActiveScope(RemoveEmptyClassMethodRector::class, null);
$this->assertFalse($this->changedFilesDetector->hasFileChanged($filePath));

// a full run uses a different scope key, so it is not poisoned
$this->changedFilesDetector->setActiveScope(null, null);
$this->assertTrue($this->changedFilesDetector->hasFileChanged($filePath));
}

public function testOnlySuffixRunDoesNotCacheFileAsUnchanged(): void
public function testOnlySuffixRunCachesUnderOwnScopeWithoutPoisoningFullRun(): void
{
$filePath = __DIR__ . '/Source/CleanFile.php';

Expand All @@ -60,6 +65,10 @@ public function testOnlySuffixRunDoesNotCacheFileAsUnchanged(): void
onlySuffix: 'Controller.php'
));

$this->changedFilesDetector->setActiveScope(null, 'Controller.php');
$this->assertFalse($this->changedFilesDetector->hasFileChanged($filePath));

$this->changedFilesDetector->setActiveScope(null, null);
$this->assertTrue($this->changedFilesDetector->hasFileChanged($filePath));
}
}
Loading