mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-07 03:40:50 +00:00
Add changed files caching
This commit is contained in:
parent
7e2394e9b6
commit
5f4a2c1e3b
2
.github/workflows/rector_ci.yaml
vendored
2
.github/workflows/rector_ci.yaml
vendored
|
@ -38,7 +38,7 @@ jobs:
|
|||
- run: composer install --no-progress
|
||||
|
||||
## First run Rector
|
||||
- run: composer rector-ci-fix
|
||||
- run: composer rector
|
||||
|
||||
-
|
||||
name: Check for Rector modified files
|
||||
|
|
2
.github/workflows/test_with_doctrine.yaml
vendored
2
.github/workflows/test_with_doctrine.yaml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
# cannot install dev deps (--no-dev), because doctrine/orm might inherit from them in different version
|
||||
composer install --no-progress --no-dev
|
||||
# loads autoload-dev packages as well, there are utils that might be used later
|
||||
git clone https://github.com/doctrine/orm.git
|
||||
git clone https://github.com/doctrine/orm.git --depth 1
|
||||
|
||||
# older version have breaking "object" type
|
||||
composer require doctrine/cache:^1.10 -d orm --no-update
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\Caching\ChangedFilesDetector;
|
||||
use Rector\Core\Configuration\Configuration;
|
||||
use Rector\Core\Console\Application;
|
||||
use Rector\Core\Console\Style\SymfonyStyleFactory;
|
||||
|
@ -36,6 +37,10 @@ try {
|
|||
/** @var Configuration $configuration */
|
||||
$configuration = $container->get(Configuration::class);
|
||||
$configuration->setFirstResolverConfig($rectorConfigsResolver->getFirstResolvedConfig());
|
||||
|
||||
/** @var ChangedFilesDetector $changedFilesDetector */
|
||||
$changedFilesDetector = $container->get(ChangedFilesDetector::class);
|
||||
$changedFilesDetector->setFirstUsedConfig(realpath($rectorConfigsResolver->getFirstResolvedConfig()));
|
||||
} catch (Throwable $throwable) {
|
||||
$symfonyStyle = (new SymfonyStyleFactory(new PrivatesCaller()))->create();
|
||||
$symfonyStyle->error($throwable->getMessage());
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
parameters:
|
||||
auto_import_names: false
|
||||
|
||||
sets:
|
||||
- "dead-code"
|
||||
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
"phpstan/phpdoc-parser": "^0.4",
|
||||
"phpstan/phpstan": "^0.12.19",
|
||||
"phpstan/phpstan-phpunit": "^0.12",
|
||||
"psr/simple-cache": "^1.0",
|
||||
"sebastian/diff": "^3.0|^4.0",
|
||||
"symfony/cache": "^4.4.6|^5.0.6",
|
||||
"symfony/console": "^4.4.6|^5.0.6",
|
||||
"symfony/dependency-injection": "^4.4.6|^5.0.6",
|
||||
"symfony/finder": "^4.4.6|^5.0.6",
|
||||
|
@ -88,6 +90,7 @@
|
|||
"Rector\\PHPStanStaticTypeMapper\\": "packages/phpstan-static-type-mapper/src",
|
||||
"Rector\\PHPStan\\": "rules/phpstan/src",
|
||||
"Rector\\PHPUnitSymfony\\": "rules/phpunit-symfony/src",
|
||||
"Rector\\Caching\\": "packages/caching/src",
|
||||
"Rector\\PHPUnit\\": "rules/phpunit/src",
|
||||
"Rector\\PSR4\\": "rules/psr4/src",
|
||||
"Rector\\Phalcon\\": "rules/phalcon/src",
|
||||
|
@ -138,6 +141,7 @@
|
|||
"Rector\\CakePHP\\Tests\\": "rules/cakephp/tests",
|
||||
"Rector\\Celebrity\\Tests\\": "rules/celebrity/tests",
|
||||
"Rector\\CodeQuality\\Tests\\": "rules/code-quality/tests",
|
||||
"Rector\\Caching\\Tests\\": "packages/caching/tests",
|
||||
"Rector\\CodingStyle\\Tests\\": "rules/coding-style/tests",
|
||||
"Rector\\Core\\Tests\\": "tests",
|
||||
"Rector\\DeadCode\\Tests\\": "rules/dead-code/tests",
|
||||
|
@ -263,8 +267,7 @@
|
|||
"bin/rector dump-rectors > docs/AllRectorsOverview.md",
|
||||
"bin/rector dump-nodes > docs/NodesOverview.md"
|
||||
],
|
||||
"rector-ci-fix": "bin/rector process --config rector-ci.yaml --ansi --no-progress-bar",
|
||||
"rector-ci": "bin/rector process --config rector-ci.yaml --ansi --no-progress-bar --dry-run",
|
||||
"rector-ci": "bin/rector process --config rector-ci.yaml --debug --dry-run",
|
||||
"rector": "bin/rector process --config rector-ci.yaml --ansi"
|
||||
},
|
||||
"config": {
|
||||
|
|
30
packages/caching/config/config.yaml
Normal file
30
packages/caching/config/config.yaml
Normal file
|
@ -0,0 +1,30 @@
|
|||
parameters:
|
||||
is_cache_enabled: false
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
public: true
|
||||
|
||||
Rector\Caching\:
|
||||
resource: '../src'
|
||||
|
||||
PHPStan\Dependency\DependencyResolver:
|
||||
factory: ['@Rector\NodeTypeResolver\DependencyInjection\PHPStanServicesFactory', 'createDependencyResolver']
|
||||
|
||||
PHPStan\File\FileHelper:
|
||||
factory: ['@Rector\NodeTypeResolver\DependencyInjection\PHPStanServicesFactory', 'createFileHelper']
|
||||
|
||||
# inspired from https://raw.githubusercontent.com/symplify/easy-coding-standard/master/config/services/services_cache.yaml
|
||||
Symfony\Component\Cache\Psr16Cache: ~
|
||||
Psr\SimpleCache\CacheInterface: '@Symfony\Component\Cache\Psr16Cache'
|
||||
|
||||
Symfony\Component\Cache\Adapter\FilesystemAdapter:
|
||||
factory: ['@Rector\Caching\Cache\Adapter\FilesystemAdapterFactory', 'create']
|
||||
|
||||
Symfony\Component\Cache\Adapter\TagAwareAdapter:
|
||||
$itemsPool: '@Symfony\Component\Cache\Adapter\FilesystemAdapter'
|
||||
|
||||
Psr\Cache\CacheItemPoolInterface: '@Symfony\Component\Cache\Adapter\FilesystemAdapter'
|
||||
|
||||
Symfony\Component\Cache\Adapter\TagAwareAdapterInterface: '@Symfony\Component\Cache\Adapter\TagAwareAdapter'
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Caching\Cache\Adapter;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
|
||||
final class FilesystemAdapterFactory
|
||||
{
|
||||
public function create(): FilesystemAdapter
|
||||
{
|
||||
return new FilesystemAdapter(
|
||||
// unique per project
|
||||
Strings::webalize(getcwd()),
|
||||
0,
|
||||
sys_get_temp_dir() . '/_rector_cached_files'
|
||||
);
|
||||
}
|
||||
}
|
148
packages/caching/src/ChangedFilesDetector.php
Normal file
148
packages/caching/src/ChangedFilesDetector.php
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Caching;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use Rector\Caching\Config\FileHashComputer;
|
||||
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
/**
|
||||
* Inspired by https://github.com/symplify/symplify/pull/90/files#diff-72041b2e1029a08930e13d79d298ef11
|
||||
*/
|
||||
final class ChangedFilesDetector
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const CONFIGURATION_HASH_KEY = 'configuration_hash';
|
||||
|
||||
/**
|
||||
* @var TagAwareAdapterInterface
|
||||
*/
|
||||
private $tagAwareAdapter;
|
||||
|
||||
/**
|
||||
* @var FileHashComputer
|
||||
*/
|
||||
private $fileHashComputer;
|
||||
|
||||
public function __construct(TagAwareAdapterInterface $tagAwareAdapter, FileHashComputer $fileHashComputer)
|
||||
{
|
||||
$this->tagAwareAdapter = $tagAwareAdapter;
|
||||
$this->fileHashComputer = $fileHashComputer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $dependentFiles
|
||||
*/
|
||||
public function addFileWithDependencies(SmartFileInfo $smartFileInfo, array $dependentFiles): void
|
||||
{
|
||||
$fileInfoCacheKey = $this->getFileInfoCacheKey($smartFileInfo);
|
||||
$hash = $this->hashFile($smartFileInfo);
|
||||
|
||||
$this->saveItemWithValue($fileInfoCacheKey, $hash);
|
||||
|
||||
$this->saveItemWithValue($fileInfoCacheKey . '_files', $dependentFiles);
|
||||
}
|
||||
|
||||
public function hasFileChanged(SmartFileInfo $smartFileInfo): bool
|
||||
{
|
||||
$currentFileHash = $this->hashFile($smartFileInfo);
|
||||
|
||||
$fileInfoCacheKey = $this->getFileInfoCacheKey($smartFileInfo);
|
||||
$item = $this->tagAwareAdapter->getItem($fileInfoCacheKey);
|
||||
|
||||
$oldFileHash = $item->get();
|
||||
|
||||
return $currentFileHash !== $oldFileHash;
|
||||
}
|
||||
|
||||
public function invalidateFile(SmartFileInfo $smartFileInfo): void
|
||||
{
|
||||
$fileInfoCacheKey = $this->getFileInfoCacheKey($smartFileInfo);
|
||||
|
||||
$this->tagAwareAdapter->deleteItem($fileInfoCacheKey);
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$this->tagAwareAdapter->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SmartFileInfo[]
|
||||
*/
|
||||
public function getDependentFileInfos(SmartFileInfo $fileInfo): array
|
||||
{
|
||||
$fileInfoCacheKey = $this->getFileInfoCacheKey($fileInfo);
|
||||
|
||||
$item = $this->tagAwareAdapter->getItem($fileInfoCacheKey . '_files');
|
||||
if ($item->get() === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dependentFileInfos = [];
|
||||
|
||||
$dependentFiles = $item->get();
|
||||
foreach ($dependentFiles as $dependentFile) {
|
||||
if (! file_exists($dependentFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dependentFileInfos[] = new SmartFileInfo($dependentFile);
|
||||
}
|
||||
|
||||
return $dependentFileInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
public function setFirstUsedConfig(string $firstConfig): void
|
||||
{
|
||||
// the first config is core to all → if it was changed, just invalidate it
|
||||
$configHash = $this->fileHashComputer->compute($firstConfig);
|
||||
$this->storeConfigurationDataHash($firstConfig, $configHash);
|
||||
}
|
||||
|
||||
private function hashFile(SmartFileInfo $smartFileInfo): string
|
||||
{
|
||||
return hash_file('sha1', $smartFileInfo->getRealPath());
|
||||
}
|
||||
|
||||
private function getFileInfoCacheKey(SmartFileInfo $smartFileInfo): string
|
||||
{
|
||||
return sha1($smartFileInfo->getRealPath());
|
||||
}
|
||||
|
||||
private function storeConfigurationDataHash(string $configPath, string $configurationHash): void
|
||||
{
|
||||
$key = self::CONFIGURATION_HASH_KEY . '_' . Strings::webalize($configPath);
|
||||
|
||||
$this->invalidateCacheIfConfigurationChanged($key, $configurationHash);
|
||||
|
||||
$this->saveItemWithValue($key, $configurationHash);
|
||||
}
|
||||
|
||||
private function invalidateCacheIfConfigurationChanged(string $key, string $configurationHash): void
|
||||
{
|
||||
$cacheItem = $this->tagAwareAdapter->getItem($key);
|
||||
|
||||
$oldConfigurationHash = $cacheItem->get();
|
||||
if ($configurationHash !== $oldConfigurationHash) {
|
||||
// should be unique per getcwd()
|
||||
$this->clear();
|
||||
}
|
||||
}
|
||||
|
||||
private function saveItemWithValue(string $key, $value): void
|
||||
{
|
||||
$item = $this->tagAwareAdapter->getItem($key);
|
||||
$item->set($value);
|
||||
|
||||
$this->tagAwareAdapter->save($item);
|
||||
}
|
||||
}
|
49
packages/caching/src/Config/FileHashComputer.php
Normal file
49
packages/caching/src/Config/FileHashComputer.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Caching\Config;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||
|
||||
/**
|
||||
* Inspired by https://github.com/symplify/easy-coding-standard/blob/e598ab54686e416788f28fcfe007fd08e0f371d9/packages/changed-files-detector/src/FileHashComputer.php
|
||||
*/
|
||||
final class FileHashComputer
|
||||
{
|
||||
public function compute(string $filePath): string
|
||||
{
|
||||
$this->ensureIsYaml($filePath);
|
||||
|
||||
$containerBuilder = new ContainerBuilder();
|
||||
|
||||
$yamlFileLoader = new YamlFileLoader($containerBuilder, new FileLocator(dirname($filePath)));
|
||||
$yamlFileLoader->load($filePath);
|
||||
|
||||
return $this->arrayToHash($containerBuilder->getDefinitions()) .
|
||||
$this->arrayToHash($containerBuilder->getParameterBag()->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $array
|
||||
*/
|
||||
private function arrayToHash(array $array): string
|
||||
{
|
||||
return md5(serialize($array));
|
||||
}
|
||||
|
||||
private function ensureIsYaml(string $filePath): void
|
||||
{
|
||||
if (Strings::match($filePath, '#\.(yml|yaml)$#')) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ShouldNotHappenException(sprintf(
|
||||
'Provide only yml/yaml file, ready for Symfony DI. "%s" given', $filePath
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Caching\Contract\Rector;
|
||||
|
||||
/**
|
||||
* Rectors implementing this interface require to run with --clear-cache, so full application is analysed.
|
||||
* Such rules can be remove unused public method, remove unused class etc. They need full app to decide correctly.
|
||||
*/
|
||||
interface ZeroCacheRectorInterface
|
||||
{
|
||||
}
|
75
packages/caching/src/FileSystem/DependencyResolver.php
Normal file
75
packages/caching/src/FileSystem/DependencyResolver.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Caching\FileSystem;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Dependency\DependencyResolver as PHPStanDependencyResolver;
|
||||
use PHPStan\File\FileHelper;
|
||||
use Rector\Core\Configuration\Configuration;
|
||||
|
||||
final class DependencyResolver
|
||||
{
|
||||
/**
|
||||
* @var FileHelper
|
||||
*/
|
||||
private $fileHelper;
|
||||
|
||||
/**
|
||||
* @var Configuration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
/**
|
||||
* @var PHPStanDependencyResolver
|
||||
*/
|
||||
private $phpStanDependencyResolver;
|
||||
|
||||
public function __construct(
|
||||
FileHelper $fileHelper,
|
||||
Configuration $configuration,
|
||||
PHPStanDependencyResolver $phpStanDependencyResolver
|
||||
) {
|
||||
$this->fileHelper = $fileHelper;
|
||||
$this->configuration = $configuration;
|
||||
$this->phpStanDependencyResolver = $phpStanDependencyResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function resolveDependencies(Node $node, Scope $scope): array
|
||||
{
|
||||
$analysedFiles = $this->configuration->getFileInfos();
|
||||
|
||||
$analysedFileAbsolutesPaths = [];
|
||||
foreach ($analysedFiles as $analysedFile) {
|
||||
$analysedFileAbsolutesPaths[] = $analysedFile->getRealPath();
|
||||
}
|
||||
|
||||
$dependencies = [];
|
||||
foreach ($this->phpStanDependencyResolver->resolveDependencies($node, $scope) as $dependencyReflection) {
|
||||
$dependencyFile = $dependencyReflection->getFileName();
|
||||
if (! $dependencyFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dependencyFile = $this->fileHelper->normalizePath($dependencyFile);
|
||||
if ($scope->getFile() === $dependencyFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! in_array($dependencyFile, $analysedFileAbsolutesPaths, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dependencies[] = $dependencyFile;
|
||||
}
|
||||
|
||||
$dependencies = array_unique($dependencies, SORT_STRING);
|
||||
|
||||
return array_values($dependencies);
|
||||
}
|
||||
}
|
50
packages/caching/src/UnchangedFilesFilter.php
Normal file
50
packages/caching/src/UnchangedFilesFilter.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Caching;
|
||||
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class UnchangedFilesFilter
|
||||
{
|
||||
/**
|
||||
* @var ChangedFilesDetector
|
||||
*/
|
||||
private $changedFilesDetector;
|
||||
|
||||
public function __construct(ChangedFilesDetector $changedFilesDetector)
|
||||
{
|
||||
$this->changedFilesDetector = $changedFilesDetector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SmartFileInfo[] $fileInfos
|
||||
* @return SmartFileInfo[]
|
||||
*/
|
||||
public function filterAndJoinWithDependentFileInfos(array $fileInfos): array
|
||||
{
|
||||
$changedFileInfos = [];
|
||||
$dependentFileInfos = [];
|
||||
|
||||
foreach ($fileInfos as $fileInfo) {
|
||||
if (! $this->changedFilesDetector->hasFileChanged($fileInfo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changedFileInfos[] = $fileInfo;
|
||||
|
||||
$this->changedFilesDetector->invalidateFile($fileInfo);
|
||||
|
||||
$dependentFileInfos = array_merge(
|
||||
$dependentFileInfos,
|
||||
$this->changedFilesDetector->getDependentFileInfos($fileInfo)
|
||||
);
|
||||
}
|
||||
|
||||
// add dependent files
|
||||
$dependentFileInfos = array_merge($dependentFileInfos, $changedFileInfos);
|
||||
|
||||
return array_unique($dependentFileInfos);
|
||||
}
|
||||
}
|
48
packages/caching/tests/Config/FileHashComputerTest.php
Normal file
48
packages/caching/tests/Config/FileHashComputerTest.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Caching\Tests\Config;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Caching\Config\FileHashComputer;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\HttpKernel\RectorKernel;
|
||||
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;
|
||||
|
||||
final class FileHashComputerTest extends AbstractKernelTestCase
|
||||
{
|
||||
/**
|
||||
* @var FileHashComputer
|
||||
*/
|
||||
private $fileHashComputer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->bootKernel(RectorKernel::class);
|
||||
$this->fileHashComputer = self::$container->get(FileHashComputer::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataForIdenticalHash()
|
||||
*/
|
||||
public function testHashIsIdentical(string $firstConfig, string $secondConfig): void
|
||||
{
|
||||
$configAHash = $this->fileHashComputer->compute($firstConfig);
|
||||
$configBHash = $this->fileHashComputer->compute($secondConfig);
|
||||
|
||||
$this->assertSame($configAHash, $configBHash);
|
||||
}
|
||||
|
||||
public function provideDataForIdenticalHash(): Iterator
|
||||
{
|
||||
yield [__DIR__ . '/Source/config_content_a.yaml', __DIR__ . '/Source/config_content_b.yaml'];
|
||||
yield [__DIR__ . '/Source/Import/import_a.yaml', __DIR__ . '/Source/Import/import_b.yaml'];
|
||||
}
|
||||
|
||||
public function testInvalidType(): void
|
||||
{
|
||||
$this->expectException(ShouldNotHappenException::class);
|
||||
$this->fileHashComputer->compute(__DIR__ . '/Source/file.php');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
imports:
|
||||
- { resource: "imported_file_a.yaml"}
|
||||
|
||||
services:
|
||||
TwoService: null
|
|
@ -0,0 +1,5 @@
|
|||
imports:
|
||||
- { resource: "imported_file_b.yaml"}
|
||||
|
||||
services:
|
||||
TwoService: null
|
|
@ -0,0 +1,2 @@
|
|||
services:
|
||||
OneService: null
|
|
@ -0,0 +1,2 @@
|
|||
services:
|
||||
OneService: null
|
|
@ -0,0 +1,2 @@
|
|||
services:
|
||||
OneService: null
|
|
@ -0,0 +1,2 @@
|
|||
services:
|
||||
OneService: null
|
3
packages/caching/tests/Config/Source/file.php
Normal file
3
packages/caching/tests/Config/Source/file.php
Normal file
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
echo 'hi';
|
|
@ -119,6 +119,19 @@ final class ErrorAndDiffCollector
|
|||
return $this->fileDiffs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SmartFileInfo[]
|
||||
*/
|
||||
public function getAffectedFileInfos(): array
|
||||
{
|
||||
$fileInfos = [];
|
||||
foreach ($this->fileDiffs as $fileDiff) {
|
||||
$fileInfos[] = $fileDiff->getFileInfo();
|
||||
}
|
||||
|
||||
return array_unique($fileInfos);
|
||||
}
|
||||
|
||||
public function getFileDiffsCount(): int
|
||||
{
|
||||
return count($this->fileDiffs);
|
||||
|
|
|
@ -384,4 +384,4 @@ class ConstantPreservingAnnotationReader implements \Doctrine\Common\Annotations
|
|||
$this->imports[$name] = array_merge(self::$globalImports, $this->phpParser->parseClass($class), ['__NAMESPACE__' => $class->getNamespaceName()]);
|
||||
$this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -864,4 +864,4 @@ final class ConstantPreservingDocParser
|
|||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@ abstract class AbstractFileSystemRector implements FileSystemRectorInterface
|
|||
$oldStmts = $this->parser->parseFileInfo($smartFileInfo);
|
||||
// needed for format preserving
|
||||
$this->oldStmts = $oldStmts;
|
||||
|
||||
return $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($oldStmts, $smartFileInfo);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,3 +6,5 @@ services:
|
|||
|
||||
Rector\NodeNestingScope\:
|
||||
resource: '../src'
|
||||
exclude:
|
||||
- '../src/ValueObject/*'
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeNestingScope\NodeFinder;
|
||||
|
||||
use PhpParser\Node;
|
||||
use Rector\Core\PhpParser\Node\BetterNodeFinder;
|
||||
use Rector\NodeNestingScope\ValueObject\ControlStructure;
|
||||
|
||||
final class ScopeAwareNodeFinder
|
||||
{
|
||||
/**
|
||||
* @var BetterNodeFinder
|
||||
*/
|
||||
private $betterNodeFinder;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isBreakingNodeFoundFirst = false;
|
||||
|
||||
public function __construct(BetterNodeFinder $betterNodeFinder)
|
||||
{
|
||||
$this->betterNodeFinder = $betterNodeFinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find node based on $callable or null, when the nesting scope is broken
|
||||
* @param class-string[] $allowedTypes
|
||||
*/
|
||||
public function findParent(Node $node, callable $callable, array $allowedTypes): ?Node
|
||||
{
|
||||
$foundNode = null;
|
||||
|
||||
$parentNestingBreakTypes = array_diff(ControlStructure::NODE_TYPES, $allowedTypes);
|
||||
|
||||
$this->isBreakingNodeFoundFirst = false;
|
||||
$foundNode = $this->betterNodeFinder->findFirstPrevious($node, function (Node $node) use (
|
||||
$callable,
|
||||
$parentNestingBreakTypes
|
||||
) {
|
||||
if ($callable($node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($parentNestingBreakTypes as $parentNestingBreakType) {
|
||||
if (is_a($node, $parentNestingBreakType, true)) {
|
||||
$this->isBreakingNodeFoundFirst = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if ($this->isBreakingNodeFoundFirst) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $foundNode;
|
||||
}
|
||||
}
|
|
@ -5,36 +5,12 @@ declare(strict_types=1);
|
|||
namespace Rector\NodeNestingScope;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Stmt\Case_;
|
||||
use PhpParser\Node\Stmt\Catch_;
|
||||
use PhpParser\Node\Stmt\Do_;
|
||||
use PhpParser\Node\Stmt\Else_;
|
||||
use PhpParser\Node\Stmt\ElseIf_;
|
||||
use PhpParser\Node\Stmt\For_;
|
||||
use PhpParser\Node\Stmt\Foreach_;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
use PhpParser\Node\Stmt\While_;
|
||||
|
||||
use Rector\Core\PhpParser\Node\BetterNodeFinder;
|
||||
use Rector\NodeNestingScope\ValueObject\ControlStructure;
|
||||
|
||||
final class ScopeNestingComparator
|
||||
{
|
||||
/**
|
||||
* @var class-string[]
|
||||
*/
|
||||
private const CONTROL_STRUCTURE_NODES = [
|
||||
For_::class,
|
||||
Foreach_::class,
|
||||
If_::class,
|
||||
While_::class,
|
||||
Do_::class,
|
||||
Else_::class,
|
||||
ElseIf_::class,
|
||||
Catch_::class,
|
||||
Case_::class,
|
||||
FunctionLike::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var BetterNodeFinder
|
||||
*/
|
||||
|
@ -55,6 +31,6 @@ final class ScopeNestingComparator
|
|||
|
||||
private function findParentControlStructure(Node $node): ?Node
|
||||
{
|
||||
return $this->betterNodeFinder->findFirstParentInstanceOf($node, self::CONTROL_STRUCTURE_NODES);
|
||||
return $this->betterNodeFinder->findFirstParentInstanceOf($node, ControlStructure::NODE_TYPES);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeNestingScope\ValueObject;
|
||||
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Stmt\Case_;
|
||||
use PhpParser\Node\Stmt\Catch_;
|
||||
use PhpParser\Node\Stmt\Do_;
|
||||
use PhpParser\Node\Stmt\Else_;
|
||||
use PhpParser\Node\Stmt\ElseIf_;
|
||||
use PhpParser\Node\Stmt\For_;
|
||||
use PhpParser\Node\Stmt\Foreach_;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
use PhpParser\Node\Stmt\While_;
|
||||
|
||||
final class ControlStructure
|
||||
{
|
||||
/**
|
||||
* @var class-string[]
|
||||
*/
|
||||
public const NODE_TYPES = [
|
||||
For_::class,
|
||||
Foreach_::class,
|
||||
If_::class,
|
||||
While_::class,
|
||||
Do_::class,
|
||||
Else_::class,
|
||||
ElseIf_::class,
|
||||
Catch_::class,
|
||||
Case_::class,
|
||||
FunctionLike::class,
|
||||
];
|
||||
}
|
|
@ -9,10 +9,12 @@ use Nette\Utils\Strings;
|
|||
use PHPStan\Analyser\NodeScopeResolver;
|
||||
use PHPStan\Analyser\ScopeFactory;
|
||||
use PHPStan\Analyser\TypeSpecifier;
|
||||
use PHPStan\Dependency\DependencyResolver;
|
||||
use PHPStan\DependencyInjection\Container;
|
||||
use PHPStan\DependencyInjection\ContainerFactory;
|
||||
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
|
||||
use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider;
|
||||
use PHPStan\File\FileHelper;
|
||||
use PHPStan\PhpDoc\TypeNodeResolver;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
|
||||
|
@ -119,6 +121,22 @@ final class PHPStanServicesFactory
|
|||
return $this->container->getByType(DynamicReturnTypeExtensionRegistryProvider::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
public function createDependencyResolver(): DependencyResolver
|
||||
{
|
||||
return $this->container->getByType(DependencyResolver::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
public function createFileHelper(): FileHelper
|
||||
{
|
||||
return $this->container->getByType(FileHelper::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
|
|
|
@ -107,7 +107,7 @@ final class NodeScopeAndMetadataDecorator
|
|||
* @param Node[] $nodes
|
||||
* @return Node[]
|
||||
*/
|
||||
public function decorateNodesFromFile(array $nodes, SmartFileInfo $fileInfo, bool $needsScope = false): array
|
||||
public function decorateNodesFromFile(array $nodes, SmartFileInfo $smartFileInfo, bool $needsScope = false): array
|
||||
{
|
||||
$nodeTraverser = new NodeTraverser();
|
||||
$nodeTraverser->addVisitor(new NameResolver(null, [
|
||||
|
@ -119,15 +119,18 @@ final class NodeScopeAndMetadataDecorator
|
|||
|
||||
// node scoping is needed only for Scope
|
||||
if ($needsScope || $this->configuration->areAnyPhpRectorsLoaded()) {
|
||||
$nodes = $this->nodeScopeResolver->processNodes($nodes, $fileInfo);
|
||||
$nodes = $this->nodeScopeResolver->processNodes($nodes, $smartFileInfo);
|
||||
}
|
||||
|
||||
$nodeTraverser = new NodeTraverser();
|
||||
$nodeTraverser->addVisitor(new NameResolver(null, [
|
||||
|
||||
$preservingNameResolver = new NameResolver(null, [
|
||||
'preserveOriginalNames' => true,
|
||||
// this option would override old non-fqn-namespaced nodes otherwise, so it needs to be disabled
|
||||
'replaceNodes' => false,
|
||||
]));
|
||||
]);
|
||||
|
||||
$nodeTraverser->addVisitor($preservingNameResolver);
|
||||
$nodes = $nodeTraverser->traverse($nodes);
|
||||
|
||||
$nodeTraverser = new NodeTraverser();
|
||||
|
|
|
@ -10,15 +10,19 @@ use PhpParser\Node\Stmt\ClassLike;
|
|||
use PhpParser\Node\Stmt\Interface_;
|
||||
use PhpParser\Node\Stmt\Trait_;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PHPStan\AnalysedCodeException;
|
||||
use PHPStan\Analyser\MutatingScope;
|
||||
use PHPStan\Analyser\NodeScopeResolver as PHPStanNodeScopeResolver;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Node\UnreachableStatementNode;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use Rector\Caching\ChangedFilesDetector;
|
||||
use Rector\Caching\FileSystem\DependencyResolver;
|
||||
use Rector\Core\Configuration\Configuration;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector;
|
||||
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
/**
|
||||
|
@ -52,18 +56,51 @@ final class NodeScopeResolver
|
|||
*/
|
||||
private $traitNodeScopeCollector;
|
||||
|
||||
/**
|
||||
* @var DependencyResolver
|
||||
*/
|
||||
private $dependencyResolver;
|
||||
|
||||
/**
|
||||
* @var ChangedFilesDetector
|
||||
*/
|
||||
private $changedFilesDetector;
|
||||
|
||||
/**
|
||||
* @var Configuration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
/**
|
||||
* @var SymfonyStyle
|
||||
*/
|
||||
private $symfonyStyle;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $dependentFiles = [];
|
||||
|
||||
public function __construct(
|
||||
ChangedFilesDetector $changedFilesDetector,
|
||||
ScopeFactory $scopeFactory,
|
||||
PHPStanNodeScopeResolver $phpStanNodeScopeResolver,
|
||||
ReflectionProvider $reflectionProvider,
|
||||
RemoveDeepChainMethodCallNodeVisitor $removeDeepChainMethodCallNodeVisitor,
|
||||
TraitNodeScopeCollector $traitNodeScopeCollector
|
||||
TraitNodeScopeCollector $traitNodeScopeCollector,
|
||||
DependencyResolver $dependencyResolver,
|
||||
Configuration $configuration,
|
||||
SymfonyStyle $symfonyStyle
|
||||
) {
|
||||
$this->scopeFactory = $scopeFactory;
|
||||
$this->phpStanNodeScopeResolver = $phpStanNodeScopeResolver;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
$this->removeDeepChainMethodCallNodeVisitor = $removeDeepChainMethodCallNodeVisitor;
|
||||
$this->traitNodeScopeCollector = $traitNodeScopeCollector;
|
||||
$this->dependencyResolver = $dependencyResolver;
|
||||
$this->changedFilesDetector = $changedFilesDetector;
|
||||
$this->configuration = $configuration;
|
||||
$this->symfonyStyle = $symfonyStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,6 +113,8 @@ final class NodeScopeResolver
|
|||
|
||||
$scope = $this->scopeFactory->createFromFile($smartFileInfo);
|
||||
|
||||
$this->dependentFiles = [];
|
||||
|
||||
// skip chain method calls, performance issue: https://github.com/phpstan/phpstan/issues/254
|
||||
$nodeCallback = function (Node $node, MutatingScope $scope): void {
|
||||
// the class reflection is resolved AFTER entering to class node
|
||||
|
@ -100,11 +139,15 @@ final class NodeScopeResolver
|
|||
} else {
|
||||
$node->setAttribute(AttributeKey::SCOPE, $scope);
|
||||
}
|
||||
|
||||
$this->resolveDependentFiles($node, $scope);
|
||||
};
|
||||
|
||||
/** @var MutatingScope $scope */
|
||||
$this->phpStanNodeScopeResolver->processNodes($nodes, $scope, $nodeCallback);
|
||||
|
||||
$this->reportCacheDebugAndSaveDependentFiles($smartFileInfo, $this->dependentFiles);
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
|
@ -146,4 +189,49 @@ final class NodeScopeResolver
|
|||
|
||||
return $classLike->name->toString();
|
||||
}
|
||||
|
||||
private function reportCacheDebug(SmartFileInfo $smartFileInfo, array $dependentFiles): void
|
||||
{
|
||||
if (! $this->configuration->isCacheDebug()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->symfonyStyle->note(
|
||||
sprintf('[debug] %d dependencies for %s file', count($dependentFiles), $smartFileInfo->getRealPath())
|
||||
);
|
||||
|
||||
if ($dependentFiles !== []) {
|
||||
$this->symfonyStyle->listing($dependentFiles);
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveDependentFiles(Node $node, MutatingScope $mutatingScope): void
|
||||
{
|
||||
if (! $this->configuration->isCacheEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($this->dependencyResolver->resolveDependencies($node, $mutatingScope) as $dependentFile) {
|
||||
$this->dependentFiles[] = $dependentFile;
|
||||
}
|
||||
} catch (AnalysedCodeException $analysedCodeException) {
|
||||
// @ignoreException
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $dependentFiles
|
||||
*/
|
||||
private function reportCacheDebugAndSaveDependentFiles(SmartFileInfo $smartFileInfo, array $dependentFiles): void
|
||||
{
|
||||
if (! $this->configuration->isCacheEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->reportCacheDebug($smartFileInfo, $dependentFiles);
|
||||
|
||||
// save for cache
|
||||
$this->changedFilesDetector->addFileWithDependencies($smartFileInfo, $dependentFiles);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\PropertyTypeResolver\Source;
|
||||
|
||||
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\PropertyTypeResolver\Source\SomeChild;
|
||||
|
||||
class ActionClass
|
||||
{
|
||||
/**
|
||||
|
|
21
phpstan.neon
21
phpstan.neon
|
@ -23,30 +23,20 @@ parameters:
|
|||
- compiler/src
|
||||
|
||||
excludes_analyse:
|
||||
# generated files
|
||||
- 'packages/doctrine-annotation-generated/src/ConstantPreservingDocParser.php'
|
||||
- 'packages/doctrine-annotation-generated/src/ConstantPreservingAnnotationReader.php'
|
||||
|
||||
|
||||
- ci/check_services_in_yaml_configs.php
|
||||
- "*/Expected/*"
|
||||
# complex printer
|
||||
- "packages/ContributorTools/src/Command/DumpNodesCommand.php"
|
||||
- "utils/phpstan/generate-paths.php"
|
||||
# test files
|
||||
- '*packages/NodeTypeResolver/tests/Source/AnotherClass.php'
|
||||
- '*tests/Rector/MethodCall/RenameMethodRector/**/SomeClass.php'
|
||||
- '*packages/BetterReflection/tests/Reflector/NotLoadedSource/SomeClass.php'
|
||||
- 'packages/NodeTypeResolver/tests/PerNodeTypeResolver/VariableTypeResolver/Source/NewClass.php'
|
||||
# tests files
|
||||
- '*tests/*/Fixture/*'
|
||||
- '*tests/*/Source/*'
|
||||
- '*tests/Source/*'
|
||||
- 'packages/NodeTypeResolver/tests/Source/SomeClass.php'
|
||||
# intentionally original
|
||||
- 'packages/Php70/src/EregToPcreTransformer.php'
|
||||
- '*/packages/ContributorTools/templates/*'
|
||||
# part of composer
|
||||
- 'tests/Composer/AutoloadWrongCasesEventSubscriber.php'
|
||||
- '*/tests/Rector/Psr4/MultipleClassFileToPsr4ClassesRector/Expected/Just*ExceptionWithoutNamespace.php'
|
||||
# stubs
|
||||
- 'stubs/*'
|
||||
|
@ -159,9 +149,6 @@ parameters:
|
|||
# test
|
||||
- '#Class Rector\\DynamicTypeAnalysis\\Tests\\Rector\\ClassMethod\\AddArgumentTypeWithProbeDataRector\\Fixture\\SomeClass not found#'
|
||||
|
||||
# known value
|
||||
- '#Parameter \#1 \$type of method PhpParser\\Builder\\Param\:\:setType\(\) expects PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|string, PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType given#'
|
||||
|
||||
-
|
||||
message: '#Class Rector\\Core\\Tests\\Rector\\StaticCall\\SwapClassMethodArgumentsRector\\Fixture\\SomeClass not found#'
|
||||
path: tests/Rector/StaticCall/SwapClassMethodArgumentsRector/SwapClassMethodArgumentsRectorTest.php
|
||||
|
@ -226,10 +213,7 @@ parameters:
|
|||
- '#Method Rector\\SOLID\\Reflection\\ParentConstantReflectionResolver\:\:(.*?)\(\) should return ReflectionClassConstant\|null but returns ReflectionClassConstant\|false#'
|
||||
- '#Parameter \#1 \$firstStmt of method Rector\\Core\\Rector\\MethodBody\\NormalToFluentRector\:\:isBothMethodCallMatch\(\) expects PhpParser\\Node\\Stmt\\Expression, PhpParser\\Node\\Stmt given#'
|
||||
- '#Method Rector\\Core\\Rector\\AbstractRector\:\:wrapToArg\(\) should return array<PhpParser\\Node\\Arg\> but returns array<PhpParser\\Node\\Arg\|PhpParser\\Node\\Expr\>#'
|
||||
- '#Property PhpParser\\Node\\Stmt\\ClassMethod\:\:\$returnType \(PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType\|null\) does not accept PhpParser\\Node#'
|
||||
|
||||
- '#Parameter \#1 \$possibleSubtype of method Rector\\TypeDeclaration\\PhpParserTypeAnalyzer\:\:isSubtypeOf\(\) expects PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType, PhpParser\\Node given#'
|
||||
- '#Parameter \#2 \$inferredReturnNode of method Rector\\TypeDeclaration\\Rector\\FunctionLike\\ReturnTypeDeclarationRector\:\:addReturnType\(\) expects PhpParser\\Node, PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType\|null given#'
|
||||
- '#Method Rector\\FileSystemRector\\Rector\\AbstractFileSystemRector\:\:wrapToArg\(\) should return array<PhpParser\\Node\\Arg\> but returns array<PhpParser\\Node\\Arg\|PhpParser\\Node\\Expr\>#'
|
||||
- '#Strict comparison using \=\=\= between mixed and null will always evaluate to false#'
|
||||
- '#Cannot call method (.*?)\(\) on Rector\\BetterPhpDocParser\\PhpDocInfo\\PhpDocInfo\|null#'
|
||||
|
@ -263,3 +247,6 @@ parameters:
|
|||
- '#Method Rector\\PHPOffice\\Rector\\MethodCall\\IncreaseColumnIndexRector\:\:findVariableAssignName\(\) should return PhpParser\\Node\\Expr\\Assign\|null but returns PhpParser\\Node\|null#'
|
||||
|
||||
- '#Parameter \#1 \$keyName of method Rector\\AttributeAwarePhpDoc\\Ast\\Type\\AttributeAwareArrayShapeItemNode\:\:createKeyWithSpacePattern\(\) expects PHPStan\\PhpDocParser\\Ast\\ConstExpr\\ConstExprIntegerNode\|PHPStan\\PhpDocParser\\Ast\\Type\\IdentifierTypeNode\|null, PHPStan\\PhpDocParser\\Ast\\ConstExpr\\ConstExprIntegerNode\|PHPStan\\PhpDocParser\\Ast\\ConstExpr\\ConstExprStringNode\|PHPStan\\PhpDocParser\\Ast\\Type\\IdentifierTypeNode\|null given#'
|
||||
- '#Method Rector\\Caching\\ChangedFilesDetector\:\:hashFile\(\) should return string but returns string\|false#'
|
||||
|
||||
- '#If condition is always false#'
|
||||
|
|
|
@ -2,6 +2,9 @@ imports:
|
|||
- { resource: "create-rector.yaml", ignore_errors: 'not_found' }
|
||||
|
||||
parameters:
|
||||
# bleeding edge feature
|
||||
# is_cache_enabled: true
|
||||
|
||||
paths:
|
||||
- src
|
||||
- tests
|
||||
|
|
|
@ -13,6 +13,7 @@ use PhpParser\Node\Stmt\Foreach_;
|
|||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
use Rector\NodeNestingScope\NodeFinder\ScopeAwareNodeFinder;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
/**
|
||||
|
@ -20,6 +21,16 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
|
|||
*/
|
||||
final class ForeachItemsAssignToEmptyArrayToAssignRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var ScopeAwareNodeFinder
|
||||
*/
|
||||
private $scopeAwareNodeFinder;
|
||||
|
||||
public function __construct(ScopeAwareNodeFinder $scopeAwareNodeFinder)
|
||||
{
|
||||
$this->scopeAwareNodeFinder = $scopeAwareNodeFinder;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Change foreach() items assign to empty array to direct assign', [
|
||||
|
@ -85,7 +96,7 @@ PHP
|
|||
return null;
|
||||
}
|
||||
|
||||
// must be empty array, othewise it will false override
|
||||
// must be empty array, otherwise it will false override
|
||||
$defaultValue = $this->getValue($previousDeclarationParentNode->expr);
|
||||
if ($defaultValue !== []) {
|
||||
return null;
|
||||
|
@ -126,13 +137,13 @@ PHP
|
|||
|
||||
private function findPreviousNodeUsage(Node $node, Expr $expr): ?Node
|
||||
{
|
||||
return $this->betterNodeFinder->findFirstPrevious($node, function (Node $node) use ($expr) {
|
||||
return $this->scopeAwareNodeFinder->findParent($node, function (Node $node) use ($expr) {
|
||||
if ($node === $expr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->areNodesEqual($node, $expr);
|
||||
});
|
||||
}, [Foreach_::class]);
|
||||
}
|
||||
|
||||
private function shouldSkipAsPartOfNestedForeach(Foreach_ $foreach): bool
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\CodeQuality\Tests\Rector\Foreach_\ForeachItemsAssignToEmptyArrayToAssignRector\Fixture;
|
||||
|
||||
class SkipForeachAssign
|
||||
{
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $dependentFiles = [];
|
||||
|
||||
public function processNodes()
|
||||
{
|
||||
$this->dependentFiles = [];
|
||||
}
|
||||
|
||||
private function resolveDependentFiles(Node $node, MutatingScope $mutatingScope): void
|
||||
{
|
||||
if (! $this->configuration->isCacheEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->dependencyResolver->resolveDependencies($node, $mutatingScope) as $dependentFile) {
|
||||
$this->dependentFiles[] = $dependentFile;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ namespace Rector\DeadCode\Rector\ClassConst;
|
|||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\ClassConst;
|
||||
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
|
@ -15,7 +16,7 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
|
|||
/**
|
||||
* @see \Rector\DeadCode\Tests\Rector\ClassConst\RemoveUnusedClassConstantRector\RemoveUnusedClassConstantRectorTest
|
||||
*/
|
||||
final class RemoveUnusedClassConstantRector extends AbstractRector
|
||||
final class RemoveUnusedClassConstantRector extends AbstractRector implements ZeroCacheRectorInterface
|
||||
{
|
||||
/**
|
||||
* @var ClassConstParsedNodesFinder
|
||||
|
|
|
@ -9,6 +9,7 @@ use PhpParser\Node\Expr\MethodCall;
|
|||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
|
@ -21,7 +22,7 @@ use Rector\VendorLocker\NodeVendorLocker\ClassMethodVendorLockResolver;
|
|||
/**
|
||||
* @see \Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDeadRecursiveClassMethodRector\RemoveDeadRecursiveClassMethodRectorTest
|
||||
*/
|
||||
final class RemoveDeadRecursiveClassMethodRector extends AbstractRector
|
||||
final class RemoveDeadRecursiveClassMethodRector extends AbstractRector implements ZeroCacheRectorInterface
|
||||
{
|
||||
/**
|
||||
* @var MethodCallParsedNodesFinder
|
||||
|
|
|
@ -8,6 +8,7 @@ use PhpParser\Node;
|
|||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
|
||||
use Rector\Core\PhpParser\Node\Manipulator\ClassManipulator;
|
||||
use Rector\Core\PhpParser\Node\Manipulator\ClassMethodManipulator;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
|
@ -23,7 +24,7 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
|
|||
* @see \Rector\DeadCode\Tests\Rector\ClassMethod\RemoveUnusedParameterRector\RemoveUnusedParameterRectorTest
|
||||
* @see \Rector\DeadCode\Tests\Rector\ClassMethod\RemoveUnusedParameterRector\OpenSourceRectorTest
|
||||
*/
|
||||
final class RemoveUnusedParameterRector extends AbstractRector
|
||||
final class RemoveUnusedParameterRector extends AbstractRector implements ZeroCacheRectorInterface
|
||||
{
|
||||
/**
|
||||
* @var ClassManipulator
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace Rector\DeadCode\Rector\Class_;
|
|||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
|
@ -14,7 +15,7 @@ use Rector\DeadCode\UnusedNodeResolver\UnusedClassResolver;
|
|||
/**
|
||||
* @see \Rector\DeadCode\Tests\Rector\Class_\RemoveUnusedClassesRector\RemoveUnusedClassesRectorTest
|
||||
*/
|
||||
final class RemoveUnusedClassesRector extends AbstractRector
|
||||
final class RemoveUnusedClassesRector extends AbstractRector implements ZeroCacheRectorInterface
|
||||
{
|
||||
/**
|
||||
* @var UnusedClassResolver
|
||||
|
|
|
@ -13,6 +13,7 @@ use PhpParser\Node\Stmt\Class_;
|
|||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\IdTagValueNode;
|
||||
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
|
||||
use Rector\Core\PhpParser\Node\Manipulator\ClassManipulator;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
|
@ -25,7 +26,7 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
|
|||
* @sponsor Thanks https://spaceflow.io/ for sponsoring this rule - visit them on https://github.com/SpaceFlow-app
|
||||
* @see \Rector\DeadCode\Tests\Rector\Class_\RemoveUnusedDoctrineEntityMethodAndPropertyRector\RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest
|
||||
*/
|
||||
final class RemoveUnusedDoctrineEntityMethodAndPropertyRector extends AbstractRector
|
||||
final class RemoveUnusedDoctrineEntityMethodAndPropertyRector extends AbstractRector implements ZeroCacheRectorInterface
|
||||
{
|
||||
/**
|
||||
* @var Assign[]
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace Rector\DeadCode\Rector\Function_;
|
|||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
|
@ -14,7 +15,7 @@ use Rector\NodeCollector\NodeCollector\ParsedFunctionLikeNodeCollector;
|
|||
/**
|
||||
* @see \Rector\DeadCode\Tests\Rector\Function_\RemoveUnusedFunctionRector\RemoveUnusedFunctionRectorTest
|
||||
*/
|
||||
final class RemoveUnusedFunctionRector extends AbstractRector
|
||||
final class RemoveUnusedFunctionRector extends AbstractRector implements ZeroCacheRectorInterface
|
||||
{
|
||||
/**
|
||||
* @var ParsedFunctionLikeNodeCollector
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace Rector\Privatization\Rector\ClassConst;
|
|||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\ClassConst;
|
||||
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
|
@ -19,7 +20,7 @@ use Rector\SOLID\ValueObject\ConstantVisibility;
|
|||
/**
|
||||
* @see \Rector\Privatization\Tests\Rector\ClassConst\PrivatizeLocalClassConstantRector\PrivatizeLocalClassConstantRectorTest
|
||||
*/
|
||||
final class PrivatizeLocalClassConstantRector extends AbstractRector
|
||||
final class PrivatizeLocalClassConstantRector extends AbstractRector implements ZeroCacheRectorInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
|
|
|
@ -9,6 +9,7 @@ use PhpParser\Node\Stmt\Class_;
|
|||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
|
@ -19,7 +20,7 @@ use Rector\VendorLocker\NodeVendorLocker\ClassMethodVisibilityVendorLockResolver
|
|||
/**
|
||||
* @see \Rector\Privatization\Tests\Rector\ClassMethod\PrivatizeLocalOnlyMethodRector\PrivatizeLocalOnlyMethodRectorTest
|
||||
*/
|
||||
final class PrivatizeLocalOnlyMethodRector extends AbstractRector
|
||||
final class PrivatizeLocalOnlyMethodRector extends AbstractRector implements ZeroCacheRectorInterface
|
||||
{
|
||||
/**
|
||||
* @var ClassMethodVisibilityVendorLockResolver
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Rector\Privatization\Tests\Rector\ClassMethod\PrivatizeLocalOnlyMethodRector\Source;
|
||||
|
||||
final class ExternalClass
|
||||
|
|
|
@ -112,6 +112,7 @@ PHP
|
|||
private function getClassDirectInterfaces(string $typeName): array
|
||||
{
|
||||
$interfaceNames = class_implements($typeName);
|
||||
|
||||
foreach ($interfaceNames as $possibleDirectInterfaceName) {
|
||||
foreach ($interfaceNames as $key => $interfaceName) {
|
||||
if ($possibleDirectInterfaceName === $interfaceName) {
|
||||
|
|
|
@ -4,7 +4,17 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\SOLID\Tests\Rector\ClassMethod\UseInterfaceOverImplementationInConstructorRector\Source;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Coconut implements Fruit
|
||||
{
|
||||
private $id;
|
||||
|
||||
public function getId(): \Ramsey\Uuid\UuidInterface
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Rector\Doctrine\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Fixture;
|
||||
|
||||
use Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Source\Apple;
|
||||
use Rector\SOLID\Tests\Rector\ClassMethod\UseInterfaceOverImplementationInConstructorRector\Source\Coconut;
|
||||
|
||||
class AssignedVariable
|
||||
{
|
||||
|
@ -15,10 +15,10 @@ class CallerClassAssignedVariable
|
|||
{
|
||||
public function run()
|
||||
{
|
||||
$building = new Apple();
|
||||
$coconut = new Coconut();
|
||||
$someClass = new AssignedVariable();
|
||||
|
||||
$assignedVariable = $building->getId();
|
||||
$assignedVariable = $coconut->getId();
|
||||
$someClass->getById($assignedVariable);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class CallerClassAssignedVariable
|
|||
|
||||
namespace Rector\Doctrine\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Fixture;
|
||||
|
||||
use Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Source\Apple;
|
||||
use Rector\SOLID\Tests\Rector\ClassMethod\UseInterfaceOverImplementationInConstructorRector\Source\Coconut;
|
||||
|
||||
class AssignedVariable
|
||||
{
|
||||
|
@ -42,10 +42,10 @@ class CallerClassAssignedVariable
|
|||
{
|
||||
public function run()
|
||||
{
|
||||
$building = new Apple();
|
||||
$coconut = new Coconut();
|
||||
$someClass = new AssignedVariable();
|
||||
|
||||
$assignedVariable = $building->getId();
|
||||
$assignedVariable = $coconut->getId();
|
||||
$someClass->getById($assignedVariable);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Rector\Doctrine\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Fixture;
|
||||
|
||||
use Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Source\Apple;
|
||||
use Rector\SOLID\Tests\Rector\ClassMethod\UseInterfaceOverImplementationInConstructorRector\Source\Coconut;
|
||||
|
||||
class SecondPosition
|
||||
{
|
||||
|
@ -15,7 +15,7 @@ class CallerClassForSecondPosition
|
|||
{
|
||||
public function run()
|
||||
{
|
||||
$building = new Apple();
|
||||
$building = new Coconut();
|
||||
$someClass = new SecondPosition();
|
||||
$someClass->process('hi', $building->getId());
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class CallerClassForSecondPosition
|
|||
|
||||
namespace Rector\Doctrine\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Fixture;
|
||||
|
||||
use Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Source\Apple;
|
||||
use Rector\SOLID\Tests\Rector\ClassMethod\UseInterfaceOverImplementationInConstructorRector\Source\Coconut;
|
||||
|
||||
class SecondPosition
|
||||
{
|
||||
|
@ -40,7 +40,7 @@ class CallerClassForSecondPosition
|
|||
{
|
||||
public function run()
|
||||
{
|
||||
$building = new Apple();
|
||||
$building = new Coconut();
|
||||
$someClass = new SecondPosition();
|
||||
$someClass->process('hi', $building->getId());
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Rector\Doctrine\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Fixture;
|
||||
|
||||
use Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Source\Apple;
|
||||
use Rector\SOLID\Tests\Rector\ClassMethod\UseInterfaceOverImplementationInConstructorRector\Source\Coconut;
|
||||
|
||||
class TestingStaticCall
|
||||
{
|
||||
|
@ -15,9 +15,8 @@ class CallerClassForStaticCall
|
|||
{
|
||||
public function run()
|
||||
{
|
||||
$building = new Apple();
|
||||
|
||||
TestingStaticCall::process('hi', $building->getId());
|
||||
$coconut = new Coconut();
|
||||
TestingStaticCall::process('hi', $coconut->getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,7 +26,7 @@ class CallerClassForStaticCall
|
|||
|
||||
namespace Rector\Doctrine\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Fixture;
|
||||
|
||||
use Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Source\Apple;
|
||||
use Rector\SOLID\Tests\Rector\ClassMethod\UseInterfaceOverImplementationInConstructorRector\Source\Coconut;
|
||||
|
||||
class TestingStaticCall
|
||||
{
|
||||
|
@ -40,9 +39,8 @@ class CallerClassForStaticCall
|
|||
{
|
||||
public function run()
|
||||
{
|
||||
$building = new Apple();
|
||||
|
||||
TestingStaticCall::process('hi', $building->getId());
|
||||
$coconut = new Coconut();
|
||||
TestingStaticCall::process('hi', $coconut->getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddMethodCallBasedParamTypeRector\Source;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Apple
|
||||
{
|
||||
private $id;
|
||||
|
||||
public function getId(): \Ramsey\Uuid\UuidInterface
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
|
@ -163,7 +163,7 @@ final class RectorApplication
|
|||
// 4. remove and add files
|
||||
$this->removedAndAddedFilesProcessor->run();
|
||||
|
||||
// 5. extensions on finish
|
||||
// 5. various extensions on finish
|
||||
$this->finishingExtensionRunner->run();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ use Rector\Core\Exception\Rector\RectorNotFoundOrNotValidRectorClassException;
|
|||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\Testing\PHPUnit\PHPUnitEnvironment;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class Configuration
|
||||
{
|
||||
|
@ -56,19 +57,44 @@ final class Configuration
|
|||
*/
|
||||
private $outputFile;
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $source = [];
|
||||
|
||||
/**
|
||||
* @var CiDetector
|
||||
*/
|
||||
private $ciDetector;
|
||||
|
||||
public function __construct(CiDetector $ciDetector)
|
||||
/**
|
||||
* @var SmartFileInfo[]
|
||||
*/
|
||||
private $fileInfos = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $shouldClearCache = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isCacheDebug = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isCacheEnabled = false;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $fileExtensions = [];
|
||||
|
||||
/**
|
||||
* @param string[] $fileExtensions
|
||||
*/
|
||||
public function __construct(CiDetector $ciDetector, bool $isCacheEnabled, array $fileExtensions)
|
||||
{
|
||||
$this->ciDetector = $ciDetector;
|
||||
$this->isCacheEnabled = $isCacheEnabled;
|
||||
$this->fileExtensions = $fileExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,9 +103,11 @@ final class Configuration
|
|||
public function resolveFromInput(InputInterface $input): void
|
||||
{
|
||||
$this->isDryRun = (bool) $input->getOption(Option::OPTION_DRY_RUN);
|
||||
$this->shouldClearCache = (bool) $input->getOption(Option::OPTION_CLEAR_CACHE);
|
||||
$this->hideAutoloadErrors = (bool) $input->getOption(Option::HIDE_AUTOLOAD_ERRORS);
|
||||
$this->mustMatchGitDiff = (bool) $input->getOption(Option::MATCH_GIT_DIFF);
|
||||
$this->showProgressBar = $this->canShowProgressBar($input);
|
||||
$this->isCacheDebug = (bool) $input->getOption(Option::CACHE_DEBUG);
|
||||
|
||||
$outputFileOption = $input->getOption(Option::OPTION_OUTPUT_FILE);
|
||||
$this->outputFile = $outputFileOption ? (string) $outputFileOption : null;
|
||||
|
@ -134,6 +162,10 @@ final class Configuration
|
|||
return false;
|
||||
}
|
||||
|
||||
if ($this->isCacheDebug) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->showProgressBar;
|
||||
}
|
||||
|
||||
|
@ -167,19 +199,42 @@ final class Configuration
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string[] $source
|
||||
* @param SmartFileInfo[] $fileInfos
|
||||
*/
|
||||
public function setSource(array $source): void
|
||||
public function setFileInfos(array $fileInfos): void
|
||||
{
|
||||
$this->source = $source;
|
||||
$this->fileInfos = $fileInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SmartFileInfo[]
|
||||
*/
|
||||
public function getFileInfos(): array
|
||||
{
|
||||
return $this->fileInfos;
|
||||
}
|
||||
|
||||
public function shouldClearCache(): bool
|
||||
{
|
||||
return $this->shouldClearCache;
|
||||
}
|
||||
|
||||
public function isCacheDebug(): bool
|
||||
{
|
||||
return $this->isCacheDebug;
|
||||
}
|
||||
|
||||
public function isCacheEnabled(): bool
|
||||
{
|
||||
return $this->isCacheEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSource(): array
|
||||
public function getFileExtensions(): array
|
||||
{
|
||||
return $this->source;
|
||||
return $this->fileExtensions;
|
||||
}
|
||||
|
||||
private function canShowProgressBar(InputInterface $input): bool
|
||||
|
|
|
@ -76,6 +76,11 @@ final class Option
|
|||
*/
|
||||
public const OPTION_OUTPUT_FILE = 'output-file';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_CLEAR_CACHE = 'clear-cache';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
@ -90,4 +95,9 @@ final class Option
|
|||
* @var string
|
||||
*/
|
||||
public const PROJECT_TYPE_OPEN_SOURCE_UNDESCORED = 'open_source';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const CACHE_DEBUG = 'cache-debug';
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\Core\Console\Command;
|
||||
|
||||
use Rector\Caching\ChangedFilesDetector;
|
||||
use Rector\Caching\UnchangedFilesFilter;
|
||||
use Rector\ChangesReporting\Application\ErrorAndDiffCollector;
|
||||
use Rector\ChangesReporting\Output\ConsoleOutputFormatter;
|
||||
use Rector\Core\Application\RectorApplication;
|
||||
|
@ -21,16 +23,13 @@ use Symfony\Component\Console\Input\InputArgument;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symplify\PackageBuilder\Console\Command\CommandNaming;
|
||||
use Symplify\PackageBuilder\Console\ShellCode;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class ProcessCommand extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $fileExtensions = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
|
@ -91,9 +90,23 @@ final class ProcessCommand extends AbstractCommand
|
|||
*/
|
||||
private $yamlProcessor;
|
||||
|
||||
/**
|
||||
* @var UnchangedFilesFilter
|
||||
*/
|
||||
private $unchangedFilesFilter;
|
||||
|
||||
/**
|
||||
* @var ChangedFilesDetector
|
||||
*/
|
||||
private $changedFilesDetector;
|
||||
|
||||
/**
|
||||
* @var SymfonyStyle
|
||||
*/
|
||||
private $symfonyStyle;
|
||||
|
||||
/**
|
||||
* @param string[] $paths
|
||||
* @param string[] $fileExtensions
|
||||
*/
|
||||
public function __construct(
|
||||
FilesFinder $phpFilesFinder,
|
||||
|
@ -107,8 +120,10 @@ final class ProcessCommand extends AbstractCommand
|
|||
RectorNodeTraverser $rectorNodeTraverser,
|
||||
StubLoader $stubLoader,
|
||||
YamlProcessor $yamlProcessor,
|
||||
array $paths,
|
||||
array $fileExtensions
|
||||
ChangedFilesDetector $changedFilesDetector,
|
||||
UnchangedFilesFilter $unchangedFilesFilter,
|
||||
SymfonyStyle $symfonyStyle,
|
||||
array $paths
|
||||
) {
|
||||
$this->filesFinder = $phpFilesFinder;
|
||||
$this->additionalAutoloader = $additionalAutoloader;
|
||||
|
@ -116,20 +131,24 @@ final class ProcessCommand extends AbstractCommand
|
|||
$this->errorAndDiffCollector = $errorAndDiffCollector;
|
||||
$this->configuration = $configuration;
|
||||
$this->rectorApplication = $rectorApplication;
|
||||
$this->fileExtensions = $fileExtensions;
|
||||
$this->outputFormatterCollector = $outputFormatterCollector;
|
||||
$this->reportingExtensionRunner = $reportingExtensionRunner;
|
||||
$this->rectorNodeTraverser = $rectorNodeTraverser;
|
||||
$this->stubLoader = $stubLoader;
|
||||
$this->paths = $paths;
|
||||
$this->yamlProcessor = $yamlProcessor;
|
||||
$this->unchangedFilesFilter = $unchangedFilesFilter;
|
||||
|
||||
parent::__construct();
|
||||
$this->yamlProcessor = $yamlProcessor;
|
||||
$this->changedFilesDetector = $changedFilesDetector;
|
||||
$this->symfonyStyle = $symfonyStyle;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName(CommandNaming::classToName(self::class));
|
||||
$this->setAliases(['rectify']);
|
||||
|
||||
$this->setDescription('Upgrade or refactor source code with provided rectors');
|
||||
$this->addArgument(
|
||||
Option::SOURCE,
|
||||
|
@ -193,6 +212,9 @@ final class ProcessCommand extends AbstractCommand
|
|||
InputOption::VALUE_REQUIRED,
|
||||
'Location for file to dump result in. Useful for Docker or automated processes'
|
||||
);
|
||||
|
||||
$this->addOption(Option::CACHE_DEBUG, null, InputOption::VALUE_NONE, 'Debug changed file cache');
|
||||
$this->addOption(Option::OPTION_CLEAR_CACHE, null, InputOption::VALUE_NONE, 'Clear un-chaged files cache');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
|
@ -205,21 +227,29 @@ final class ProcessCommand extends AbstractCommand
|
|||
$this->stubLoader->loadStubs();
|
||||
|
||||
$source = $this->resolvesSourcePaths($input);
|
||||
$this->configuration->setSource($source);
|
||||
|
||||
$phpFileInfos = $this->filesFinder->findInDirectoriesAndFiles(
|
||||
$source,
|
||||
$this->fileExtensions,
|
||||
$this->configuration->getFileExtensions(),
|
||||
$this->configuration->mustMatchGitDiff()
|
||||
);
|
||||
|
||||
$this->additionalAutoloader->autoloadWithInputAndSource($input, $source);
|
||||
|
||||
// yaml
|
||||
$this->yamlProcessor->run();
|
||||
$phpFileInfos = $this->processWithCache($phpFileInfos);
|
||||
|
||||
if ($this->configuration->isCacheDebug()) {
|
||||
$this->symfonyStyle->note(sprintf('[cache] %d files after cache filter', count($phpFileInfos)));
|
||||
$this->symfonyStyle->listing($phpFileInfos);
|
||||
}
|
||||
|
||||
$this->yamlProcessor->run($source);
|
||||
|
||||
$this->configuration->setFileInfos($phpFileInfos);
|
||||
$this->rectorApplication->runOnFileInfos($phpFileInfos);
|
||||
|
||||
$this->reportZeroCacheRectorsCondition();
|
||||
|
||||
// report diffs and errors
|
||||
$outputFormat = (string) $input->getOption(Option::OPTION_OUTPUT_FORMAT);
|
||||
$outputFormatter = $this->outputFormatterCollector->getByName($outputFormat);
|
||||
|
@ -227,6 +257,9 @@ final class ProcessCommand extends AbstractCommand
|
|||
|
||||
$this->reportingExtensionRunner->run();
|
||||
|
||||
// invalidate affected files
|
||||
$this->invalidateAffectedCacheFiles();
|
||||
|
||||
// some errors were found → fail
|
||||
if ($this->errorAndDiffCollector->getErrors() !== []) {
|
||||
return ShellCode::SUCCESS;
|
||||
|
@ -255,4 +288,53 @@ final class ProcessCommand extends AbstractCommand
|
|||
// fallback to config defined paths
|
||||
return $this->paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SmartFileInfo[] $phpFileInfos
|
||||
* @return SmartFileInfo[]
|
||||
*/
|
||||
private function processWithCache(array $phpFileInfos): array
|
||||
{
|
||||
if (! $this->configuration->isCacheEnabled()) {
|
||||
return $phpFileInfos;
|
||||
}
|
||||
|
||||
// cache stuff
|
||||
if ($this->configuration->shouldClearCache()) {
|
||||
$this->changedFilesDetector->clear();
|
||||
}
|
||||
|
||||
if ($this->configuration->isCacheDebug()) {
|
||||
$this->symfonyStyle->note(sprintf('[cache] %d files before cache filter', count($phpFileInfos)));
|
||||
}
|
||||
|
||||
return $this->unchangedFilesFilter->filterAndJoinWithDependentFileInfos($phpFileInfos);
|
||||
}
|
||||
|
||||
private function reportZeroCacheRectorsCondition(): void
|
||||
{
|
||||
if (! $this->configuration->isCacheEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->rectorNodeTraverser->hasZeroCacheRectors()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->symfonyStyle->note(sprintf(
|
||||
'Ruleset contains %d rules that need "--clear-cache" option to analyse full project',
|
||||
$this->rectorNodeTraverser->getZeroCacheRectorCount()
|
||||
));
|
||||
}
|
||||
|
||||
private function invalidateAffectedCacheFiles(): void
|
||||
{
|
||||
if (! $this->configuration->isCacheEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->errorAndDiffCollector->getAffectedFileInfos() as $affectedFileInfo) {
|
||||
$this->changedFilesDetector->invalidateFile($affectedFileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ namespace Rector\Core\PhpParser\NodeTraverser;
|
|||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeTraverser;
|
||||
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
|
||||
use Rector\Core\Configuration\Configuration;
|
||||
use Rector\Core\Contract\Rector\PhpRectorInterface;
|
||||
use Rector\Core\Testing\Application\EnabledRectorsProvider;
|
||||
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
|
||||
|
@ -30,10 +32,18 @@ final class RectorNodeTraverser extends NodeTraverser
|
|||
/**
|
||||
* @param PhpRectorInterface[] $phpRectors
|
||||
*/
|
||||
public function __construct(EnabledRectorsProvider $enabledRectorsProvider, array $phpRectors = [])
|
||||
{
|
||||
public function __construct(
|
||||
EnabledRectorsProvider $enabledRectorsProvider,
|
||||
Configuration $configuration,
|
||||
array $phpRectors = []
|
||||
) {
|
||||
$this->allPhpRectors = $phpRectors;
|
||||
|
||||
foreach ($phpRectors as $phpRector) {
|
||||
if ($configuration->isCacheEnabled() && ! $configuration->shouldClearCache() && $phpRector instanceof ZeroCacheRectorInterface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addVisitor($phpRector);
|
||||
}
|
||||
|
||||
|
@ -67,6 +77,20 @@ final class RectorNodeTraverser extends NodeTraverser
|
|||
return count($this->visitors);
|
||||
}
|
||||
|
||||
public function hasZeroCacheRectors(): bool
|
||||
{
|
||||
return (bool) $this->getZeroCacheRectorCount();
|
||||
}
|
||||
|
||||
public function getZeroCacheRectorCount(): int
|
||||
{
|
||||
$zeroCacheRectors = array_filter($this->allPhpRectors, function (PhpRectorInterface $phpRector) {
|
||||
return $phpRector instanceof ZeroCacheRectorInterface;
|
||||
});
|
||||
|
||||
return count($zeroCacheRectors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostly used for testing
|
||||
*/
|
||||
|
|
|
@ -59,6 +59,11 @@ final class FileDiff
|
|||
return $this->smartFileInfo->getRelativeFilePath();
|
||||
}
|
||||
|
||||
public function getFileInfo(): SmartFileInfo
|
||||
{
|
||||
return $this->smartFileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RectorWithFileAndLineChange[]
|
||||
*/
|
||||
|
|
|
@ -45,13 +45,14 @@ final class YamlProcessor
|
|||
$this->symfonyStyle = $symfonyStyle;
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
/**
|
||||
* @param string[] $source
|
||||
*/
|
||||
public function run(array $source): void
|
||||
{
|
||||
$source = $this->configuration->getSource();
|
||||
$yamlFileInfos = $this->filesFinder->findInDirectoriesAndFiles($source, ['yaml']);
|
||||
|
||||
// 1. raw class rename
|
||||
|
||||
$oldToNewClasses = $this->changeConfiguration->getOldToNewClasses();
|
||||
if ($oldToNewClasses === []) {
|
||||
return;
|
||||
|
|
|
@ -13,6 +13,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symplify\PackageBuilder\Console\Command\CommandNaming;
|
||||
use Symplify\PackageBuilder\Console\ShellCode;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class SyncAnnotationParserCommand extends Command
|
||||
{
|
||||
|
@ -57,16 +58,32 @@ final class SyncAnnotationParserCommand extends Command
|
|||
foreach ($this->classSyncers as $classSyncer) {
|
||||
$isSuccess = $classSyncer->sync($dryRun);
|
||||
if (! $isSuccess) {
|
||||
$message = sprintf('File "%s" has changed, regenerate it: ', $classSyncer->getTargetFilePath());
|
||||
$sourceFileInfo = new SmartFileInfo($classSyncer->getSourceFilePath());
|
||||
|
||||
$message = sprintf(
|
||||
'File "%s" has changed,%sregenerate it: %s',
|
||||
$sourceFileInfo->getRelativeFilePathFromCwd(),
|
||||
PHP_EOL,
|
||||
'bin/rector sync-annotation-parser'
|
||||
);
|
||||
$this->symfonyStyle->error($message);
|
||||
|
||||
return ShellCode::ERROR;
|
||||
}
|
||||
|
||||
$sourceFileInfo = new SmartFileInfo($classSyncer->getSourceFilePath());
|
||||
$targetFileInfo = new SmartFileInfo($classSyncer->getTargetFilePath());
|
||||
|
||||
if ($dryRun) {
|
||||
$messageFormat = 'Original "%s" is in sync with "%s"';
|
||||
} else {
|
||||
$messageFormat = 'Original "%s" was changed and refactored to "%s"';
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
'Original "%s" was changed and refactored to "%s"',
|
||||
$classSyncer->getSourceFilePath(),
|
||||
$classSyncer->getTargetFilePath()
|
||||
$messageFormat,
|
||||
$sourceFileInfo->getRelativeFilePathFromCwd(),
|
||||
$targetFileInfo->getRelativeFilePathFromCwd()
|
||||
);
|
||||
|
||||
$this->symfonyStyle->note($message);
|
||||
|
|
|
@ -51,6 +51,8 @@ abstract class AbstractClassSyncer implements ClassSyncerInterface
|
|||
{
|
||||
$printedContent = $this->printNodesToString($nodes);
|
||||
|
||||
$printedContent = rtrim($printedContent) . PHP_EOL;
|
||||
|
||||
FileSystem::write($this->getTargetFilePath(), $printedContent);
|
||||
}
|
||||
|
||||
|
@ -59,9 +61,7 @@ abstract class AbstractClassSyncer implements ClassSyncerInterface
|
|||
*/
|
||||
protected function printNodesToString(array $nodes): string
|
||||
{
|
||||
$printedContent = $this->betterStandardPrinter->prettyPrint($nodes);
|
||||
|
||||
return '<?php' . PHP_EOL . PHP_EOL . $printedContent;
|
||||
return $this->betterStandardPrinter->prettyPrintFile($nodes) . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue
Block a user