mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-26 12:52:36 +00:00
Add PHPStanAttributeTypeSyncer
This commit is contained in:
parent
f586d42b0f
commit
464eb22030
|
@ -50,6 +50,7 @@
|
|||
"Rector\\": "src",
|
||||
"Rector\\Autodiscovery\\": "packages/Autodiscovery/src",
|
||||
"Rector\\Architecture\\": "packages/Architecture/src",
|
||||
"Rector\\AttributeAwarePhpDoc\\": "packages/AttributeAwarePhpDoc/src",
|
||||
"Rector\\BetterPhpDocParser\\": "packages/BetterPhpDocParser/src",
|
||||
"Rector\\CakePHP\\": "packages/CakePHP/src",
|
||||
"Rector\\Celebrity\\": "packages/Celebrity/src",
|
||||
|
@ -102,6 +103,7 @@
|
|||
"Rector\\ZendToSymfony\\": "packages/ZendToSymfony/src",
|
||||
"Rector\\Utils\\DocumentationGenerator\\": "utils/DocumentationGenerator/src",
|
||||
"Rector\\Utils\\RectorGenerator\\": "utils/RectorGenerator/src",
|
||||
"Rector\\Utils\\PHPStanAttributeTypeSyncer\\": "utils/PHPStanAttributeTypeSyncer/src",
|
||||
"Rector\\StrictCodeQuality\\": "packages/StrictCodeQuality/src",
|
||||
"Rector\\DynamicTypeAnalysis\\": "packages/DynamicTypeAnalysis/src",
|
||||
"Rector\\PhpDeglobalize\\": "packages/PhpDeglobalize/src",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
services:
|
||||
Rector\Renaming\Rector\Class_\RenameClassRector:
|
||||
# see https://github.com/doctrine/persistence/pull/71
|
||||
$oldToNewClasses:
|
||||
Doctrine\Common\Persistence\Event\LifecycleEventArgs: Doctrine\Persistence\Event\LifecycleEventArgs
|
||||
Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs: Doctrine\Persistence\Event\LoadClassMetadataEventArgs
|
||||
|
|
1
ecs.yaml
1
ecs.yaml
|
@ -22,6 +22,7 @@ services:
|
|||
parameters:
|
||||
sets:
|
||||
- 'psr12'
|
||||
- 'php70'
|
||||
- 'php71'
|
||||
- 'symplify'
|
||||
- 'common'
|
||||
|
|
|
@ -6,7 +6,7 @@ use Symfony\Component\Yaml\Yaml;
|
|||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
### 11. CONFIGURE INPUT
|
||||
### CONFIGURE INPUT HERE
|
||||
$diffPath = 'https://github.com/symfony/symfony/commit/53a4711520d52bccd20fa6e616731114fa6eb61f.diff';
|
||||
$classNamePrefix = 'Doctrine';
|
||||
|
||||
|
|
9
packages/AttributeAwarePhpDoc/config/config.yaml
Normal file
9
packages/AttributeAwarePhpDoc/config/config.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
public: true
|
||||
|
||||
Rector\AttributeAwarePhpDoc\:
|
||||
resource: '../src'
|
||||
exclude:
|
||||
- '../src/Ast/*'
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\AttributeAwarePhpDoc;
|
||||
|
||||
use Rector\AttributeAwarePhpDoc\Contract\AttributeNodeAwareFactory\AttributeNodeAwareFactoryInterface;
|
||||
|
||||
final class AttributeAwareNodeFactoryCollector
|
||||
{
|
||||
/**
|
||||
* @var AttributeNodeAwareFactoryInterface[]
|
||||
*/
|
||||
private $attributeAwareNodeFactories = [];
|
||||
|
||||
/**
|
||||
* @param AttributeNodeAwareFactoryInterface[] $attributeAwareNodeFactories
|
||||
*/
|
||||
public function __construct(array $attributeAwareNodeFactories)
|
||||
{
|
||||
$this->attributeAwareNodeFactories = $attributeAwareNodeFactories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AttributeNodeAwareFactoryInterface[]
|
||||
*/
|
||||
public function provide(): array
|
||||
{
|
||||
return $this->attributeAwareNodeFactories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSupportedNodeClasses(): array
|
||||
{
|
||||
$supportedNodeClasses = [];
|
||||
foreach ($this->attributeAwareNodeFactories as $attributeAwareNodeFactory) {
|
||||
$supportedNodeClasses[] = $attributeAwareNodeFactory->getOriginalNodeClass();
|
||||
}
|
||||
|
||||
return $supportedNodeClasses;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\AttributeAwarePhpDoc\Contract\AttributeNodeAwareFactory;
|
||||
|
||||
use PHPStan\PhpDocParser\Ast\Node;
|
||||
use Rector\BetterPhpDocParser\Contract\PhpDocNode\AttributeAwareNodeInterface;
|
||||
|
||||
interface AttributeNodeAwareFactoryInterface
|
||||
{
|
||||
public function getOriginalNodeClass(): string;
|
||||
|
||||
public function isMatch(Node $node): bool;
|
||||
|
||||
public function create(Node $node): AttributeAwareNodeInterface;
|
||||
}
|
|
@ -31,6 +31,7 @@ use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
|
|||
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
||||
use Rector\AttributeAwarePhpDoc\AttributeAwareNodeFactoryCollector;
|
||||
use Rector\BetterPhpDocParser\Ast\PhpDocNodeTraverser;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareDeprecatedTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareExtendsTagValueNode;
|
||||
|
@ -69,13 +70,21 @@ final class AttributeAwareNodeFactory
|
|||
*/
|
||||
private $phpDocNodeTraverser;
|
||||
|
||||
public function __construct(PhpDocNodeTraverser $phpDocNodeTraverser)
|
||||
{
|
||||
/**
|
||||
* @var AttributeAwareNodeFactoryCollector
|
||||
*/
|
||||
private $attributeAwareNodeFactoryCollector;
|
||||
|
||||
public function __construct(
|
||||
PhpDocNodeTraverser $phpDocNodeTraverser,
|
||||
AttributeAwareNodeFactoryCollector $attributeAwareNodeFactoryCollector
|
||||
) {
|
||||
$this->phpDocNodeTraverser = $phpDocNodeTraverser;
|
||||
$this->attributeAwareNodeFactoryCollector = $attributeAwareNodeFactoryCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PhpDocNode|PhpDocChildNode|PhpDocTagValueNode AttributeAwareNodeInterface
|
||||
* @return PhpDocNode|PhpDocChildNode|PhpDocTagValueNode|AttributeAwareNodeInterface
|
||||
*/
|
||||
public function createFromNode(Node $node): AttributeAwareNodeInterface
|
||||
{
|
||||
|
@ -95,6 +104,14 @@ final class AttributeAwareNodeFactory
|
|||
return new AttributeAwarePhpDocNode($node->children);
|
||||
}
|
||||
|
||||
foreach ($this->attributeAwareNodeFactoryCollector->provide() as $attributeNodeAwareFactory) {
|
||||
if (! $attributeNodeAwareFactory->isMatch($node)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $attributeNodeAwareFactory->create($node);
|
||||
}
|
||||
|
||||
if ($node instanceof PhpDocTagNode) {
|
||||
return new AttributeAwarePhpDocTagNode($node->name, $node->value);
|
||||
}
|
||||
|
|
10
utils/PHPStanAttributeTypeSyncer/config/config.yaml
Normal file
10
utils/PHPStanAttributeTypeSyncer/config/config.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
services:
|
||||
_defaults:
|
||||
public: true
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Rector\Utils\PHPStanAttributeTypeSyncer\:
|
||||
resource: '../src'
|
||||
exclude:
|
||||
- '../src/ValueObject/*'
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Utils\PHPStanAttributeTypeSyncer\ClassNaming;
|
||||
|
||||
use Rector\CodingStyle\Naming\ClassNaming;
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\ValueObject\Paths;
|
||||
|
||||
final class AttributeClassNaming
|
||||
{
|
||||
/**
|
||||
* @var ClassNaming
|
||||
*/
|
||||
private $classNaming;
|
||||
|
||||
public function __construct(ClassNaming $classNaming)
|
||||
{
|
||||
$this->classNaming = $classNaming;
|
||||
}
|
||||
|
||||
public function createAttributeAwareShortClassName(string $nodeClass): string
|
||||
{
|
||||
$shortMissingNodeClass = $this->classNaming->getShortName($nodeClass);
|
||||
|
||||
return 'AttributeAware' . $shortMissingNodeClass;
|
||||
}
|
||||
|
||||
public function createAttributeAwareFactoryShortClassName(string $nodeClass): string
|
||||
{
|
||||
$shortMissingNodeClass = $this->classNaming->getShortName($nodeClass);
|
||||
|
||||
return 'AttributeAware' . $shortMissingNodeClass . 'Factory';
|
||||
}
|
||||
|
||||
public function createAttributeAwareClassName(string $nodeClass): string
|
||||
{
|
||||
return Paths::NAMESPACE_PHPDOC_NODE . '\\' . $this->createAttributeAwareShortClassName($nodeClass);
|
||||
}
|
||||
|
||||
public function createAttributeAwareFactoryClassName(string $nodeClass): string
|
||||
{
|
||||
return Paths::NAMESPACE_NODE_FACTORY . '\\' . $this->createAttributeAwareFactoryShortClassName($nodeClass);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Utils\PHPStanAttributeTypeSyncer\Command;
|
||||
|
||||
use Rector\AttributeAwarePhpDoc\AttributeAwareNodeFactoryCollector;
|
||||
use Rector\Console\Command\AbstractCommand;
|
||||
use Rector\Console\Shell;
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\Finder\NodeClassFinder;
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\Generator\AttributeAwareNodeFactoryGenerator;
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\Generator\AttributeAwareNodeGenerator;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symplify\PackageBuilder\Console\Command\CommandNaming;
|
||||
|
||||
final class SyncTypesCommand extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* @var AttributeAwareNodeFactoryCollector
|
||||
*/
|
||||
private $attributeAwareNodeFactoryCollector;
|
||||
|
||||
/**
|
||||
* @var SymfonyStyle
|
||||
*/
|
||||
private $symfonyStyle;
|
||||
|
||||
/**
|
||||
* @var NodeClassFinder
|
||||
*/
|
||||
private $nodeClassFinder;
|
||||
|
||||
/**
|
||||
* @var AttributeAwareNodeGenerator
|
||||
*/
|
||||
private $attributeAwareNodeGenerator;
|
||||
|
||||
/**
|
||||
* @var AttributeAwareNodeFactoryGenerator
|
||||
*/
|
||||
private $attributeAwareNodeFactoryGenerator;
|
||||
|
||||
public function __construct(
|
||||
AttributeAwareNodeFactoryCollector $attributeAwareNodeFactoryCollector,
|
||||
SymfonyStyle $symfonyStyle,
|
||||
NodeClassFinder $nodeClassFinder,
|
||||
AttributeAwareNodeGenerator $attributeAwareNodeGenerator,
|
||||
AttributeAwareNodeFactoryGenerator $attributeAwareNodeFactoryGenerator
|
||||
) {
|
||||
$this->attributeAwareNodeFactoryCollector = $attributeAwareNodeFactoryCollector;
|
||||
$this->symfonyStyle = $symfonyStyle;
|
||||
$this->nodeClassFinder = $nodeClassFinder;
|
||||
$this->attributeAwareNodeGenerator = $attributeAwareNodeGenerator;
|
||||
$this->attributeAwareNodeFactoryGenerator = $attributeAwareNodeFactoryGenerator;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName(CommandNaming::classToName(self::class));
|
||||
$this->setDescription('[Dev] Synchronize PHPStan types to attribute aware types in Rectors');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$missingNodeClasses = $this->getMissingNodeClasses();
|
||||
if ($missingNodeClasses === []) {
|
||||
$this->symfonyStyle->success(
|
||||
'All PHPStan Doc Parser nodes are covered with attribute aware mirror in Rector'
|
||||
);
|
||||
|
||||
return Shell::CODE_SUCCESS;
|
||||
}
|
||||
|
||||
$this->symfonyStyle->error('These classes are missing their attribute aware brother');
|
||||
|
||||
foreach ($missingNodeClasses as $missingNodeClass) {
|
||||
// 1. generate node
|
||||
$this->attributeAwareNodeGenerator->generateFromPhpDocParserNodeClass($missingNodeClass);
|
||||
|
||||
// 2. generate node factory...
|
||||
$this->attributeAwareNodeFactoryGenerator->generateFromPhpDocParserNodeClass($missingNodeClass);
|
||||
}
|
||||
|
||||
return Shell::CODE_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getMissingNodeClasses(): array
|
||||
{
|
||||
$phpDocParserTagValueNodeClasses = $this->nodeClassFinder->findCurrentPHPDocParserNodeClasses();
|
||||
$supportedNodeClasses = $this->attributeAwareNodeFactoryCollector->getSupportedNodeClasses();
|
||||
|
||||
return array_diff($phpDocParserTagValueNodeClasses, $supportedNodeClasses);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Utils\PHPStanAttributeTypeSyncer\Finder;
|
||||
|
||||
use Nette\Loaders\RobotLoader;
|
||||
|
||||
final class NodeClassFinder
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function findCurrentPHPDocParserNodeClasses(): array
|
||||
{
|
||||
return $this->findClassesByNamePatternInDirectories(
|
||||
'*Node.php',
|
||||
[
|
||||
// @todo not sure if needed
|
||||
// __DIR__ . '/../../../../vendor/phpstan/phpdoc-parser/src/Ast/Type',
|
||||
__DIR__ . '/../../../../vendor/phpstan/phpdoc-parser/src/Ast/PhpDoc',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $directories
|
||||
* @return string[]
|
||||
*/
|
||||
private function findClassesByNamePatternInDirectories(string $namePattern, array $directories): array
|
||||
{
|
||||
$robotLoader = new RobotLoader();
|
||||
foreach ($directories as $directory) {
|
||||
$robotLoader->addDirectory($directory);
|
||||
}
|
||||
|
||||
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/_phpdoc_parser_ast');
|
||||
$robotLoader->acceptFiles = [$namePattern];
|
||||
$robotLoader->rebuild();
|
||||
|
||||
$classLikesToPaths = $robotLoader->getIndexedClasses();
|
||||
|
||||
$classLikes = array_keys($classLikesToPaths);
|
||||
$excludedClasses = ['PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode'];
|
||||
|
||||
// keep only classes, skip interfaces
|
||||
$classes = [];
|
||||
foreach ($classLikes as $classLike) {
|
||||
if (! class_exists($classLike)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classes[] = $classLike;
|
||||
}
|
||||
|
||||
return array_diff($classes, $excludedClasses);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Utils\PHPStanAttributeTypeSyncer\Generator;
|
||||
|
||||
use Nette\Utils\FileSystem;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use Rector\PhpParser\Printer\BetterStandardPrinter;
|
||||
|
||||
abstract class AbstractAttributeAwareNodeGenerator
|
||||
{
|
||||
/**
|
||||
* @var BetterStandardPrinter
|
||||
*/
|
||||
private $betterStandardPrinter;
|
||||
|
||||
/**
|
||||
* @required
|
||||
*/
|
||||
public function autowireAbstractAttributeAwareNodeGenerator(BetterStandardPrinter $betterStandardPrinter): void
|
||||
{
|
||||
$this->betterStandardPrinter = $betterStandardPrinter;
|
||||
}
|
||||
|
||||
protected function printNamespaceToFile(Namespace_ $namespace, string $targetFilePath): void
|
||||
{
|
||||
$fileContent = $this->betterStandardPrinter->prettyPrintFile([$namespace]);
|
||||
|
||||
FileSystem::write($targetFilePath, $fileContent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Utils\PHPStanAttributeTypeSyncer\Generator;
|
||||
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\ClassNaming\AttributeClassNaming;
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\NodeFactory\AttributeAwareClassFactoryFactory;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
final class AttributeAwareNodeFactoryGenerator extends AbstractAttributeAwareNodeGenerator
|
||||
{
|
||||
/**
|
||||
* @var AttributeClassNaming
|
||||
*/
|
||||
private $attributeClassNaming;
|
||||
|
||||
/**
|
||||
* @var SymfonyStyle
|
||||
*/
|
||||
private $symfonyStyle;
|
||||
|
||||
/**
|
||||
* @var AttributeAwareClassFactoryFactory
|
||||
*/
|
||||
private $attributeAwareClassFactoryFactory;
|
||||
|
||||
public function __construct(
|
||||
AttributeClassNaming $attributeClassNaming,
|
||||
SymfonyStyle $symfonyStyle,
|
||||
AttributeAwareClassFactoryFactory $attributeAwareClassFactoryFactory
|
||||
) {
|
||||
$this->attributeClassNaming = $attributeClassNaming;
|
||||
$this->symfonyStyle = $symfonyStyle;
|
||||
$this->attributeAwareClassFactoryFactory = $attributeAwareClassFactoryFactory;
|
||||
}
|
||||
|
||||
public function generateFromPhpDocParserNodeClass(string $phpDocParserNodeClass): void
|
||||
{
|
||||
$shortClassName = $this->attributeClassNaming->createAttributeAwareFactoryShortClassName(
|
||||
$phpDocParserNodeClass
|
||||
);
|
||||
|
||||
// write file
|
||||
$targetFilePath = __DIR__ . '/../../../../packages/AttributeAwarePhpDoc/src/AttributeAwareNodeFactory/' . $shortClassName . '.php';
|
||||
|
||||
// prevent file override
|
||||
if (file_exists($targetFilePath)) {
|
||||
$realTargetFilePath = realpath($targetFilePath);
|
||||
$this->symfonyStyle->note(sprintf('File "%s" already exists, skipping', $realTargetFilePath));
|
||||
return;
|
||||
}
|
||||
|
||||
$namespace = $this->attributeAwareClassFactoryFactory->createFromPhpDocParserNodeClass($phpDocParserNodeClass);
|
||||
$this->printNamespaceToFile($namespace, $targetFilePath);
|
||||
|
||||
$this->reportGeneratedAttributeAwareNode($phpDocParserNodeClass, $targetFilePath);
|
||||
}
|
||||
|
||||
private function reportGeneratedAttributeAwareNode(string $missingNodeClass, string $filePath): void
|
||||
{
|
||||
$attributeAwareFullyQualifiedClassName = $this->attributeClassNaming->createAttributeAwareClassName(
|
||||
$missingNodeClass
|
||||
);
|
||||
|
||||
$this->symfonyStyle->note(sprintf(
|
||||
'Class "%s" now has freshly generated "%s" in "%s"',
|
||||
$missingNodeClass,
|
||||
$attributeAwareFullyQualifiedClassName,
|
||||
$filePath
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Utils\PHPStanAttributeTypeSyncer\Generator;
|
||||
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\ClassNaming\AttributeClassNaming;
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\NodeFactory\AttributeAwareClassFactory;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
final class AttributeAwareNodeGenerator extends AbstractAttributeAwareNodeGenerator
|
||||
{
|
||||
/**
|
||||
* @var AttributeClassNaming
|
||||
*/
|
||||
private $attributeClassNaming;
|
||||
|
||||
/**
|
||||
* @var SymfonyStyle
|
||||
*/
|
||||
private $symfonyStyle;
|
||||
|
||||
/**
|
||||
* @var AttributeAwareClassFactory
|
||||
*/
|
||||
private $attributeAwareClassFactory;
|
||||
|
||||
public function __construct(
|
||||
AttributeClassNaming $attributeClassNaming,
|
||||
SymfonyStyle $symfonyStyle,
|
||||
AttributeAwareClassFactory $attributeAwareClassFactory
|
||||
) {
|
||||
$this->attributeClassNaming = $attributeClassNaming;
|
||||
$this->symfonyStyle = $symfonyStyle;
|
||||
$this->attributeAwareClassFactory = $attributeAwareClassFactory;
|
||||
}
|
||||
|
||||
public function generateFromPhpDocParserNodeClass(string $phpDocParserNodeClass): void
|
||||
{
|
||||
$shortClassName = $this->attributeClassNaming->createAttributeAwareShortClassName($phpDocParserNodeClass);
|
||||
|
||||
// write file
|
||||
$targetFilePath = __DIR__ . '/../../../../packages/AttributeAwarePhpDoc/src/Ast/PhpDoc/' . $shortClassName . '.php';
|
||||
|
||||
// prevent file override
|
||||
if (file_exists($targetFilePath)) {
|
||||
$realTargetFilePath = realpath($targetFilePath);
|
||||
$this->symfonyStyle->note(sprintf('File "%s" already exists, skipping', $realTargetFilePath));
|
||||
return;
|
||||
}
|
||||
|
||||
$namespace = $this->attributeAwareClassFactory->createFromPhpDocParserNodeClass($phpDocParserNodeClass);
|
||||
|
||||
$this->printNamespaceToFile($namespace, $targetFilePath);
|
||||
|
||||
$this->reportGeneratedAttributeAwareNode($phpDocParserNodeClass);
|
||||
}
|
||||
|
||||
private function reportGeneratedAttributeAwareNode(string $missingNodeClass): void
|
||||
{
|
||||
$attributeAwareFullyQualifiedClassName = $this->attributeClassNaming->createAttributeAwareClassName(
|
||||
$missingNodeClass
|
||||
);
|
||||
|
||||
$this->symfonyStyle->note(sprintf(
|
||||
'Class "%s" now has freshly generated "%s"',
|
||||
$missingNodeClass,
|
||||
$attributeAwareFullyQualifiedClassName
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Utils\PHPStanAttributeTypeSyncer\NodeFactory;
|
||||
|
||||
use PhpParser\Builder\Class_;
|
||||
use PhpParser\BuilderFactory;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use Rector\BetterPhpDocParser\Attributes\Attribute\AttributeTrait;
|
||||
use Rector\BetterPhpDocParser\Contract\PhpDocNode\AttributeAwareNodeInterface;
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\ClassNaming\AttributeClassNaming;
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\ValueObject\Paths;
|
||||
|
||||
final class AttributeAwareClassFactory
|
||||
{
|
||||
/**
|
||||
* @var BuilderFactory
|
||||
*/
|
||||
private $builderFactory;
|
||||
|
||||
/**
|
||||
* @var AttributeClassNaming
|
||||
*/
|
||||
private $attributeClassNaming;
|
||||
|
||||
public function __construct(BuilderFactory $builderFactory, AttributeClassNaming $attributeClassNaming)
|
||||
{
|
||||
$this->builderFactory = $builderFactory;
|
||||
$this->attributeClassNaming = $attributeClassNaming;
|
||||
}
|
||||
|
||||
public function createFromPhpDocParserNodeClass(string $nodeClass): Namespace_
|
||||
{
|
||||
$namespaceBuilder = $this->builderFactory->namespace(Paths::NAMESPACE_PHPDOC_NODE);
|
||||
|
||||
$shortClassName = $this->attributeClassNaming->createAttributeAwareShortClassName($nodeClass);
|
||||
$classBuilder = $this->createClassBuilder($nodeClass, $shortClassName);
|
||||
|
||||
$useTrait = $this->builderFactory->useTrait(new FullyQualified(AttributeTrait::class));
|
||||
$classBuilder->addStmt($useTrait);
|
||||
|
||||
$namespaceBuilder->addStmt($classBuilder->getNode());
|
||||
|
||||
return $namespaceBuilder->getNode();
|
||||
}
|
||||
|
||||
private function createClassBuilder(string $nodeClass, string $shortClassName): Class_
|
||||
{
|
||||
$classBuilder = $this->builderFactory->class($shortClassName);
|
||||
$classBuilder->makeFinal();
|
||||
$classBuilder->extend(new FullyQualified($nodeClass));
|
||||
$classBuilder->implement(new FullyQualified(AttributeAwareNodeInterface::class));
|
||||
|
||||
return $classBuilder;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Utils\PHPStanAttributeTypeSyncer\NodeFactory;
|
||||
|
||||
use PhpParser\Builder\Param;
|
||||
use PhpParser\BuilderFactory;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use PHPStan\PhpDocParser\Ast\Node;
|
||||
use Rector\AttributeAwarePhpDoc\Contract\AttributeNodeAwareFactory\AttributeNodeAwareFactoryInterface;
|
||||
use Rector\BetterPhpDocParser\Contract\PhpDocNode\AttributeAwareNodeInterface;
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\ClassNaming\AttributeClassNaming;
|
||||
use Rector\Utils\PHPStanAttributeTypeSyncer\ValueObject\Paths;
|
||||
use ReflectionClass;
|
||||
|
||||
final class AttributeAwareClassFactoryFactory
|
||||
{
|
||||
/**
|
||||
* @var BuilderFactory
|
||||
*/
|
||||
private $builderFactory;
|
||||
|
||||
/**
|
||||
* @var AttributeClassNaming
|
||||
*/
|
||||
private $attributeClassNaming;
|
||||
|
||||
public function __construct(BuilderFactory $builderFactory, AttributeClassNaming $attributeClassNaming)
|
||||
{
|
||||
$this->builderFactory = $builderFactory;
|
||||
$this->attributeClassNaming = $attributeClassNaming;
|
||||
}
|
||||
|
||||
public function createFromPhpDocParserNodeClass(string $nodeClass): Namespace_
|
||||
{
|
||||
$namespaceBuilder = $this->builderFactory->namespace(Paths::NAMESPACE_NODE_FACTORY);
|
||||
|
||||
$shortClassName = $this->attributeClassNaming->createAttributeAwareFactoryShortClassName($nodeClass);
|
||||
|
||||
$classBuilder = $this->builderFactory->class($shortClassName);
|
||||
$classBuilder->makeFinal();
|
||||
$classBuilder->implement(new FullyQualified(AttributeNodeAwareFactoryInterface::class));
|
||||
|
||||
$classMethods = $this->createClassMethods($nodeClass);
|
||||
$classBuilder->addStmts($classMethods);
|
||||
|
||||
$namespaceBuilder->addStmt($classBuilder->getNode());
|
||||
|
||||
return $namespaceBuilder->getNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassMethod[]
|
||||
*/
|
||||
private function createClassMethods(string $nodeClass): array
|
||||
{
|
||||
$classMethods = [];
|
||||
|
||||
$classMethods[] = $this->createGetOriginalNodeClass($nodeClass);
|
||||
|
||||
$nodeParam = $this->builderFactory->param('node');
|
||||
$nodeParam->setType(new FullyQualified(Node::class));
|
||||
|
||||
$classMethods[] = $this->createIsMatchClassMethod($nodeClass, $nodeParam);
|
||||
|
||||
$classMethods[] = $this->createCreateClassMethod($nodeClass, $nodeParam);
|
||||
|
||||
return $classMethods;
|
||||
}
|
||||
|
||||
private function createIsAFuncCall(string $nodeClass): FuncCall
|
||||
{
|
||||
return new FuncCall(new Name('is_a'), [
|
||||
new Variable('node'),
|
||||
$this->createClassReference($nodeClass),
|
||||
new ConstFetch(new Name('true')),
|
||||
]);
|
||||
}
|
||||
|
||||
private function createClassReference(string $nodeClass): ClassConstFetch
|
||||
{
|
||||
return new ClassConstFetch(new FullyQualified($nodeClass), 'class');
|
||||
}
|
||||
|
||||
private function createIsMatchClassMethod(string $nodeClass, Param $param): ClassMethod
|
||||
{
|
||||
$isMatchClassMethod = $this->builderFactory->method('isMatch');
|
||||
|
||||
$isMatchClassMethod->addParam($param);
|
||||
$isMatchClassMethod->makePublic();
|
||||
$isMatchClassMethod->setReturnType('bool');
|
||||
|
||||
$isAFuncCall = $this->createIsAFuncCall($nodeClass);
|
||||
$isMatchClassMethod->addStmt(new Return_($isAFuncCall));
|
||||
|
||||
return $isMatchClassMethod->getNode();
|
||||
}
|
||||
|
||||
private function createGetOriginalNodeClass(string $nodeClass): ClassMethod
|
||||
{
|
||||
$getOriginalNodeClassClassMethod = $this->builderFactory->method('getOriginalNodeClass');
|
||||
$getOriginalNodeClassClassMethod->makePublic();
|
||||
$getOriginalNodeClassClassMethod->setReturnType('string');
|
||||
|
||||
$classReference = $this->createClassReference($nodeClass);
|
||||
$getOriginalNodeClassClassMethod->addStmt(new Return_($classReference));
|
||||
|
||||
return $getOriginalNodeClassClassMethod->getNode();
|
||||
}
|
||||
|
||||
private function createCreateClassMethod(string $nodeClass, Param $nodeParam): ClassMethod
|
||||
{
|
||||
$createClassMethod = $this->builderFactory->method('create');
|
||||
|
||||
$createClassMethod->addParam($nodeParam);
|
||||
$createClassMethod->makePublic();
|
||||
$createClassMethod->setReturnType(new FullyQualified(AttributeAwareNodeInterface::class));
|
||||
|
||||
// add @param doc with more precise type
|
||||
$paramDocBlock = sprintf('/**%s * @param \\%s%s */', PHP_EOL, $nodeClass, PHP_EOL);
|
||||
$createClassMethod->setDocComment($paramDocBlock);
|
||||
|
||||
$attributeAwareClassName = $this->attributeClassNaming->createAttributeAwareClassName($nodeClass);
|
||||
|
||||
$new = new New_(new FullyQualified($attributeAwareClassName));
|
||||
|
||||
// complete new args
|
||||
$this->completeNewArgs($new, $nodeClass);
|
||||
|
||||
$createClassMethod->addStmt(new Return_($new));
|
||||
|
||||
return $createClassMethod->getNode();
|
||||
}
|
||||
|
||||
private function completeNewArgs(New_ $new, string $phpDocParserNodeClass): void
|
||||
{
|
||||
// ...
|
||||
$reflectionClass = new ReflectionClass($phpDocParserNodeClass);
|
||||
$constructorReflectionMethod = $reflectionClass->getConstructor();
|
||||
|
||||
// no constructor → no params to add
|
||||
if ($constructorReflectionMethod === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$phpDocParserNodeVariable = new Variable('node');
|
||||
|
||||
foreach ($constructorReflectionMethod->getParameters() as $parameter) {
|
||||
$parameterName = $parameter->getName();
|
||||
|
||||
$new->args[] = new Arg(new PropertyFetch($phpDocParserNodeVariable, $parameterName));
|
||||
}
|
||||
}
|
||||
}
|
18
utils/PHPStanAttributeTypeSyncer/src/ValueObject/Paths.php
Normal file
18
utils/PHPStanAttributeTypeSyncer/src/ValueObject/Paths.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Utils\PHPStanAttributeTypeSyncer\ValueObject;
|
||||
|
||||
final class Paths
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const NAMESPACE_PHPDOC_NODE = 'Rector\AttributeAwarePhpDoc\Ast\PhpDoc';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const NAMESPACE_NODE_FACTORY = 'Rector\AttributeAwarePhpDoc\AttributeAwareNodeFactory';
|
||||
}
|
Loading…
Reference in New Issue
Block a user