-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* misc * move finalize here * fixup! move finalize here * tidy up * bump
- Loading branch information
1 parent
acb6fe4
commit 5d74acc
Showing
11 changed files
with
554 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Rector\SwissKnife\Analyzer; | ||
|
||
use PhpParser\NodeTraverser; | ||
use Rector\SwissKnife\PhpParser\CachedPhpParser; | ||
use Rector\SwissKnife\PhpParser\NodeVisitor\NeedForFinalizeNodeVisitor; | ||
use Webmozart\Assert\Assert; | ||
|
||
final class NeedsFinalizeAnalyzer | ||
{ | ||
private readonly NodeTraverser $finalizingNodeTraverser; | ||
|
||
private readonly NeedForFinalizeNodeVisitor $needForFinalizeNodeVisitor; | ||
|
||
/** | ||
* @param string[] $excludedClasses | ||
*/ | ||
public function __construct( | ||
array $excludedClasses, | ||
private readonly CachedPhpParser $cachedPhpParser | ||
) { | ||
Assert::allString($excludedClasses); | ||
|
||
$finalizingNodeTraverser = new NodeTraverser(); | ||
$this->needForFinalizeNodeVisitor = new NeedForFinalizeNodeVisitor($excludedClasses); | ||
$finalizingNodeTraverser->addVisitor($this->needForFinalizeNodeVisitor); | ||
|
||
$this->finalizingNodeTraverser = $finalizingNodeTraverser; | ||
} | ||
|
||
public function isNeeded(string $filePath): bool | ||
{ | ||
$stmts = $this->cachedPhpParser->parseFile($filePath); | ||
$this->finalizingNodeTraverser->traverse($stmts); | ||
|
||
return $this->needForFinalizeNodeVisitor->isNeeded(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<?php | ||
|
||
namespace Rector\SwissKnife\Command; | ||
|
||
use Nette\Utils\FileSystem; | ||
use Nette\Utils\Strings; | ||
use Rector\SwissKnife\Analyzer\NeedsFinalizeAnalyzer; | ||
use Rector\SwissKnife\EntityClassResolver; | ||
use Rector\SwissKnife\FileSystem\PhpFilesFinder; | ||
use Rector\SwissKnife\ParentClassResolver; | ||
use Rector\SwissKnife\PhpParser\CachedPhpParser; | ||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputArgument; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
use Symfony\Component\Console\Style\SymfonyStyle; | ||
|
||
final class FinalizeClassesCommand extends Command | ||
{ | ||
/** | ||
* @see https://regex101.com/r/Q5Nfbo/1 | ||
*/ | ||
public const NEWLINE_CLASS_START_REGEX = '#^class\s#m'; | ||
|
||
public function __construct( | ||
private readonly SymfonyStyle $symfonyStyle, | ||
private readonly ParentClassResolver $parentClassResolver, | ||
private readonly EntityClassResolver $entityClassResolver, | ||
private readonly CachedPhpParser $cachedPhpParser | ||
) { | ||
parent::__construct(); | ||
} | ||
|
||
protected function configure(): void | ||
{ | ||
$this->setName('finalize-classes'); | ||
|
||
$this->setDescription('Finalize classes without children'); | ||
|
||
$this->addArgument('paths', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Directories to finalize'); | ||
} | ||
|
||
/** | ||
* @return self::FAILURE|self::SUCCESS | ||
*/ | ||
protected function execute(InputInterface $input, OutputInterface $output): int | ||
{ | ||
$paths = (array) $input->getArgument('paths'); | ||
|
||
$phpFileInfos = PhpFilesFinder::findPhpFileInfos($paths); | ||
|
||
$this->symfonyStyle->title('1. Detecting parent and entity classes'); | ||
|
||
// double to count for both parent and entity resolver | ||
$this->symfonyStyle->progressStart(2 * count($phpFileInfos)); | ||
|
||
$progressClosure = function (): void { | ||
$this->symfonyStyle->progressAdvance(); | ||
}; | ||
|
||
$parentClassNames = $this->parentClassResolver->resolve($phpFileInfos, $progressClosure); | ||
$entityClassNames = $this->entityClassResolver->resolve($phpFileInfos, $progressClosure); | ||
|
||
$this->symfonyStyle->progressFinish(); | ||
|
||
$this->symfonyStyle->writeln(sprintf( | ||
'Found %d parent and %d entity classes', | ||
count($parentClassNames), | ||
count($entityClassNames) | ||
)); | ||
|
||
$this->symfonyStyle->newLine(1); | ||
|
||
$this->symfonyStyle->title('2. Finalizing safe classes'); | ||
|
||
$excludedClasses = array_merge($parentClassNames, $entityClassNames); | ||
$needsFinalizeAnalyzer = new NeedsFinalizeAnalyzer($excludedClasses, $this->cachedPhpParser); | ||
|
||
$finalizedFilePaths = []; | ||
|
||
foreach ($phpFileInfos as $phpFileInfo) { | ||
// should be file be finalize, is not and is not excluded? | ||
if (! $needsFinalizeAnalyzer->isNeeded($phpFileInfo->getRealPath())) { | ||
continue; | ||
} | ||
|
||
$this->symfonyStyle->writeln(sprintf('File "%s" was finalized', $phpFileInfo->getRelativePath())); | ||
|
||
$finalizedContents = Strings::replace( | ||
$phpFileInfo->getContents(), | ||
self::NEWLINE_CLASS_START_REGEX, | ||
'final class ' | ||
); | ||
|
||
$finalizedFilePaths[] = $phpFileInfo->getRelativePath(); | ||
FileSystem::write($phpFileInfo->getRealPath(), $finalizedContents); | ||
} | ||
|
||
if ($finalizedFilePaths === []) { | ||
$this->symfonyStyle->success('Nothign to finalize'); | ||
return self::SUCCESS; | ||
} | ||
|
||
$this->symfonyStyle->listing($finalizedFilePaths); | ||
$this->symfonyStyle->success(sprintf('%d classes were finalized', count($finalizedFilePaths))); | ||
|
||
return Command::SUCCESS; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Rector\SwissKnife; | ||
|
||
use Closure; | ||
use PhpParser\NodeTraverser; | ||
use Rector\SwissKnife\PhpParser\CachedPhpParser; | ||
use Rector\SwissKnife\PhpParser\NodeVisitor\EntityClassNameCollectingNodeVisitor; | ||
use Symfony\Component\Finder\SplFileInfo; | ||
|
||
final readonly class EntityClassResolver | ||
{ | ||
public function __construct( | ||
private CachedPhpParser $cachedPhpParser | ||
) { | ||
} | ||
|
||
/** | ||
* @param SplFileInfo[] $phpFileInfos | ||
* @return string[] | ||
*/ | ||
public function resolve(array $phpFileInfos, Closure $progressClosure): array | ||
{ | ||
$entityClassNameCollectingNodeVisitor = new EntityClassNameCollectingNodeVisitor(); | ||
|
||
$nodeTraverser = new NodeTraverser(); | ||
$nodeTraverser->addVisitor($entityClassNameCollectingNodeVisitor); | ||
|
||
$this->traverseFileInfos($phpFileInfos, $nodeTraverser, $progressClosure); | ||
|
||
return $entityClassNameCollectingNodeVisitor->getEntityClassNames(); | ||
} | ||
|
||
/** | ||
* @param SplFileInfo[] $phpFileInfos | ||
*/ | ||
private function traverseFileInfos( | ||
array $phpFileInfos, | ||
NodeTraverser $nodeTraverser, | ||
callable $progressClosure | ||
): void { | ||
foreach ($phpFileInfos as $phpFileInfo) { | ||
$stmts = $this->cachedPhpParser->parseFile($phpFileInfo->getRealPath()); | ||
|
||
$nodeTraverser->traverse($stmts); | ||
$progressClosure(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Rector\SwissKnife\FileSystem; | ||
|
||
use Symfony\Component\Finder\Finder; | ||
use Symfony\Component\Finder\SplFileInfo; | ||
|
||
final class PhpFilesFinder | ||
{ | ||
/** | ||
* @param string[] $paths | ||
* @return SplFileInfo[] | ||
*/ | ||
public static function findPhpFileInfos(array $paths): array | ||
{ | ||
$phpFinder = Finder::create() | ||
->files() | ||
->in($paths) | ||
->name('*.php'); | ||
|
||
return iterator_to_array($phpFinder); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Rector\SwissKnife; | ||
|
||
use PhpParser\NodeTraverser; | ||
use Rector\SwissKnife\PhpParser\CachedPhpParser; | ||
use Rector\SwissKnife\PhpParser\NodeVisitor\ParentClassNameCollectingNodeVisitor; | ||
use Symfony\Component\Finder\SplFileInfo; | ||
|
||
final readonly class ParentClassResolver | ||
{ | ||
public function __construct( | ||
private CachedPhpParser $cachedPhpParser | ||
) { | ||
} | ||
|
||
/** | ||
* @param SplFileInfo[] $phpFileInfos | ||
* @return string[] | ||
*/ | ||
public function resolve(array $phpFileInfos, callable $progressClosure): array | ||
{ | ||
$parentClassNameCollectingNodeVisitor = new ParentClassNameCollectingNodeVisitor(); | ||
|
||
$nodeTraverser = new NodeTraverser(); | ||
$nodeTraverser->addVisitor($parentClassNameCollectingNodeVisitor); | ||
|
||
$this->traverseFileInfos($phpFileInfos, $nodeTraverser, $progressClosure); | ||
|
||
return $parentClassNameCollectingNodeVisitor->getParentClassNames(); | ||
} | ||
|
||
/** | ||
* @param SplFileInfo[] $phpFileInfos | ||
*/ | ||
private function traverseFileInfos( | ||
array $phpFileInfos, | ||
NodeTraverser $nodeTraverser, | ||
callable $progressClosure | ||
): void { | ||
foreach ($phpFileInfos as $phpFileInfo) { | ||
$stmts = $this->cachedPhpParser->parseFile($phpFileInfo->getRealPath()); | ||
|
||
$nodeTraverser->traverse($stmts); | ||
$progressClosure(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Rector\SwissKnife\PhpParser; | ||
|
||
use Nette\Utils\FileSystem; | ||
use PhpParser\Node\Stmt; | ||
use PhpParser\NodeTraverser; | ||
use PhpParser\NodeVisitor\NameResolver; | ||
use PhpParser\Parser; | ||
|
||
/** | ||
* Parse file just once | ||
*/ | ||
final class CachedPhpParser | ||
{ | ||
/** | ||
* @var array<string, Stmt[]> | ||
*/ | ||
private array $cachedStmts = []; | ||
|
||
public function __construct( | ||
private readonly Parser $phpParser | ||
) { | ||
} | ||
|
||
/** | ||
* @return Stmt[] | ||
*/ | ||
public function parseFile(string $filePath): array | ||
{ | ||
if (isset($this->cachedStmts[$filePath])) { | ||
return $this->cachedStmts[$filePath]; | ||
} | ||
|
||
$fileContents = FileSystem::read($filePath); | ||
$stmts = $this->phpParser->parse($fileContents); | ||
|
||
if (is_array($stmts)) { | ||
$nodeTraverser = new NodeTraverser(); | ||
$nodeTraverser->addVisitor(new NameResolver()); | ||
$nodeTraverser->traverse($stmts); | ||
} | ||
|
||
$this->cachedStmts[$filePath] = $stmts ?? []; | ||
|
||
return $stmts ?? []; | ||
} | ||
} |
Oops, something went wrong.