Skip to content

Commit

Permalink
Add PHPStanAttributeTypeSyncer
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Jan 12, 2020
1 parent f586d42 commit 2ad18be
Show file tree
Hide file tree
Showing 17 changed files with 724 additions and 3 deletions.
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions config/set/doctrine/doctrine-common-20.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions ecs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ services:
parameters:
sets:
- 'psr12'
- 'php70'
- 'php71'
- 'symplify'
- 'common'
Expand Down
9 changes: 9 additions & 0 deletions packages/AttributeAwarePhpDoc/config/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
_defaults:
autowire: true
public: true

Rector\AttributeAwarePhpDoc\:
resource: '../src'
exclude:
- '../src/Ast/*'
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
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;
Expand Down Expand Up @@ -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
{
Expand All @@ -95,6 +104,14 @@ public function createFromNode(Node $node): AttributeAwareNodeInterface
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);
}
Expand Down
10 changes: 10 additions & 0 deletions utils/PHPStanAttributeTypeSyncer/config/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
_defaults:
public: true
autowire: true
autoconfigure: true

Rector\Utils\PHPStanAttributeTypeSyncer\:
resource: '../src'
exclude:
- '../src/ValueObject/*'
Original file line number Diff line number Diff line change
@@ -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);
}
}
101 changes: 101 additions & 0 deletions utils/PHPStanAttributeTypeSyncer/src/Command/SyncTypesCommand.php
Original file line number Diff line number Diff line change
@@ -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);
}
}
58 changes: 58 additions & 0 deletions utils/PHPStanAttributeTypeSyncer/src/Finder/NodeClassFinder.php
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit 2ad18be

Please sign in to comment.