Skip to content

Commit

Permalink
add ConstExprClassNameDecorator, add PhpDocNodeDecoratorInterface
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Jun 2, 2022
1 parent 160034e commit 5f87472
Show file tree
Hide file tree
Showing 12 changed files with 214 additions and 61 deletions.
2 changes: 2 additions & 0 deletions easy-ci.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use PHPStan\PhpDocParser\Parser\TypeParser;
use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface;
use Rector\CodingStyle\Contract\ClassNameImport\ClassNameImportSkipVoterInterface;
use Rector\Core\Contract\Console\OutputStyleInterface;
use Rector\Core\Contract\PhpParser\Node\StmtsAwareInterface;
Expand Down Expand Up @@ -40,6 +41,7 @@
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::TYPES_TO_SKIP, [
PhpDocNodeDecoratorInterface::class,
Command::class,
Application::class,
RectorInterface::class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Rector\BetterPhpDocParser\Contract\PhpDocParser;

use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;

interface PhpDocNodeDecoratorInterface
{
public function decorate(PhpDocNode $phpDocNode, Node $phpNode): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function __construct(
private readonly StaticTypeMapper $staticTypeMapper,
private readonly AnnotationNaming $annotationNaming,
private readonly RectorChangeCollector $rectorChangeCollector,
private readonly PhpDocNodeByTypeFinder $phpDocNodeByTypeFinder
private readonly PhpDocNodeByTypeFinder $phpDocNodeByTypeFinder,
) {
}

Expand Down
29 changes: 20 additions & 9 deletions packages/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Rector\BetterPhpDocParser\PhpDocParser;

use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
Expand All @@ -14,10 +15,12 @@
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface;
use Rector\BetterPhpDocParser\PhpDocInfo\TokenIteratorFactory;
use Rector\BetterPhpDocParser\ValueObject\Parser\BetterTokenIterator;
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
use Rector\BetterPhpDocParser\ValueObject\StartAndEnd;
use Rector\Core\Configuration\CurrentNodeProvider;
use Rector\Core\Exception\ShouldNotHappenException;
use Symplify\PackageBuilder\Reflection\PrivatesCaller;

Expand All @@ -26,18 +29,18 @@
*/
final class BetterPhpDocParser extends PhpDocParser
{
private readonly PrivatesCaller $privatesCaller;

/**
* @param PhpDocNodeDecoratorInterface[] $phpDocNodeDecorators
*/
public function __construct(
TypeParser $typeParser,
ConstExprParser $constExprParser,
private readonly CurrentNodeProvider $currentNodeProvider,
private readonly TokenIteratorFactory $tokenIteratorFactory,
private readonly DoctrineAnnotationDecorator $doctrineAnnotationDecorator,
private readonly ConstExprClassNameDecorator $constExprClassNameDecorator,
private readonly array $phpDocNodeDecorators,
private readonly PrivatesCaller $privatesCaller = new PrivatesCaller(),
) {
parent::__construct($typeParser, $constExprParser);

$this->privatesCaller = new PrivatesCaller();
}

public function parse(TokenIterator $tokenIterator): PhpDocNode
Expand All @@ -60,15 +63,23 @@ public function parse(TokenIterator $tokenIterator): PhpDocNode
$tokenIterator->tryConsumeTokenType(Lexer::TOKEN_CLOSE_PHPDOC);

$phpDocNode = new PhpDocNode($children);
// replace generic nodes with DoctrineAnnotations
$this->doctrineAnnotationDecorator->decorate($phpDocNode);
$this->constExprClassNameDecorator->decorate($phpDocNode);

// decorate FQN classes etc.
$node = $this->currentNodeProvider->getNode();
if (! $node instanceof Node) {
throw new ShouldNotHappenException();
}

foreach ($this->phpDocNodeDecorators as $phpDocNodeDecorator) {
$phpDocNodeDecorator->decorate($phpDocNode, $node);
}

return $phpDocNode;
}

public function parseTag(TokenIterator $tokenIterator): PhpDocTagNode
{
// replace generic nodes with DoctrineAnnotations
if (! $tokenIterator instanceof BetterTokenIterator) {
throw new ShouldNotHappenException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,28 @@
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface;
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
use Rector\Core\Configuration\CurrentNodeProvider;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\StaticTypeMapper\Naming\NameScopeFactory;
use Symplify\Astral\PhpDocParser\PhpDocNodeTraverser;

/**
* Decorate node with fully qualified class name for const epxr,
* e.g. Direction::*
*/
final class ConstExprClassNameDecorator
final class ConstExprClassNameDecorator implements PhpDocNodeDecoratorInterface
{
public function __construct(
private CurrentNodeProvider $currentNodeProvider,
private NameScopeFactory $nameScopeFactory,
private PhpDocNodeTraverser $phpDocNodeTraverser
private readonly NameScopeFactory $nameScopeFactory,
private readonly PhpDocNodeTraverser $phpDocNodeTraverser
) {
}

public function decorate(PhpDocNode $phpDocNode): void
public function decorate(PhpDocNode $phpDocNode, PhpNode $phpNode): void
{
$phpNode = $this->currentNodeProvider->getNode();

if (! $phpNode instanceof PhpNode) {
throw new ShouldNotHappenException();
}

$this->phpDocNodeTraverser->traverseWithCallable($phpDocNode, '', function (Node $node) use (
$phpNode
): int|Node|null {
): Node|null {
if (! $node instanceof ConstExprNode) {
return null;
}
Expand All @@ -53,13 +45,13 @@ public function decorate(PhpDocNode $phpDocNode): void
});
}

private function resolveFullyQualifiedClass(ConstExprNode $constExprNode, PhpNode $node): ?string
private function resolveFullyQualifiedClass(ConstExprNode $constExprNode, PhpNode $phpNode): ?string
{
if (! $constExprNode instanceof ConstFetchNode) {
return null;
}

$nameScope = $this->nameScopeFactory->createNameScopeFromNodeWithoutTemplateTypes($node);
$nameScope = $this->nameScopeFactory->createNameScopeFromNodeWithoutTemplateTypes($phpNode);
return $nameScope->resolveStringName($constExprNode->className);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,16 @@
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use Rector\BetterPhpDocParser\Attributes\AttributeMirrorer;
use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface;
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
use Rector\BetterPhpDocParser\PhpDoc\SpacelessPhpDocTagNode;
use Rector\BetterPhpDocParser\PhpDocInfo\TokenIteratorFactory;
use Rector\BetterPhpDocParser\ValueObject\DoctrineAnnotation\SilentKeyMap;
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
use Rector\BetterPhpDocParser\ValueObject\StartAndEnd;
use Rector\Core\Configuration\CurrentNodeProvider;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Util\StringUtils;

final class DoctrineAnnotationDecorator
final class DoctrineAnnotationDecorator implements PhpDocNodeDecoratorInterface
{
/**
* Special short annotations, that are resolved as FQN by Doctrine annotation parser
Expand All @@ -45,24 +44,18 @@ final class DoctrineAnnotationDecorator
private const NESTED_ANNOTATION_END_REGEX = '#(\s+)?\}\)(\s+)?#';

public function __construct(
private readonly CurrentNodeProvider $currentNodeProvider,
private readonly ClassAnnotationMatcher $classAnnotationMatcher,
private readonly StaticDoctrineAnnotationParser $staticDoctrineAnnotationParser,
private readonly TokenIteratorFactory $tokenIteratorFactory,
private readonly AttributeMirrorer $attributeMirrorer
) {
}

public function decorate(PhpDocNode $phpDocNode): void
public function decorate(PhpDocNode $phpDocNode, Node $phpNode): void
{
$currentPhpNode = $this->currentNodeProvider->getNode();
if (! $currentPhpNode instanceof Node) {
throw new ShouldNotHappenException();
}

// merge split doctrine nested tags
$this->mergeNestedDoctrineAnnotations($phpDocNode);
$this->transformGenericTagValueNodesToDoctrineAnnotationTagValueNodes($phpDocNode, $currentPhpNode);
$this->transformGenericTagValueNodesToDoctrineAnnotationTagValueNodes($phpDocNode, $phpNode);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ use Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear;

final class ChangeParamType
{
public function changeGear(Gear $gear)
public function changeGear(\Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear $gear)
{
}
}

?>

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;

use Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear;

final class MultipleParamsChange
{
/**
* @param string $carType
* @param Gear::* $gear
* @param int $speed
*/
public function changeGear($carType, string $gear, $speed)
{
}
}

?>
-----
<?php

namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;

use Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear;

final class MultipleParamsChange
{
/**
* @param string $carType
* @param int $speed
*/
public function changeGear($carType, \Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear $gear, $speed)
{
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;

final class SkipUnknownClass
{
/**
* @param AnythingNonExisting::* $gear
*/
public function changeGear(string $gear)
{
}
}
24 changes: 18 additions & 6 deletions rules/Php80/NodeAnalyzer/EnumConstListClassDetector.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,8 @@ public function detect(Class_ $class): bool
}

// all constant must be public
foreach ($classConstants as $classConstant) {
if (! $classConstant->isPublic()) {
return false;
}
if (! $this->hasExclusivelyPublicClassConsts($classConstants)) {
return false;
}

// all constants must have exactly 1 value
Expand All @@ -61,7 +59,7 @@ public function detect(Class_ $class): bool

/**
* @param ClassConst[] $classConsts
* @return string[]
* @return array<class-string<Type>>
*/
private function resolveClassConstTypes(array $classConsts): array
{
Expand All @@ -71,9 +69,23 @@ private function resolveClassConstTypes(array $classConsts): array
foreach ($classConsts as $classConst) {
$const = $classConst->consts[0];
$type = $this->nodeTypeResolver->getType($const->value);
$typeClasses[] = get_class($type);
$typeClasses[] = $type::class;
}

return array_unique($typeClasses);
}

/**
* @param ClassConst[] $classConsts
*/
private function hasExclusivelyPublicClassConsts(array $classConsts): bool
{
foreach ($classConsts as $classConst) {
if (! $classConst->isPublic()) {
return false;
}
}

return true;
}
}
56 changes: 56 additions & 0 deletions rules/Php80/NodeAnalyzer/EnumParamAnalyzer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Rector\Php80\NodeAnalyzer;

use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\Php\PhpParameterReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;

/**
* Detects enum-like params, e.g.
* Direction::*
*/
final class EnumParamAnalyzer
{
public function __construct(
private readonly ReflectionProvider $reflectionProvider,
) {
}

public function matchClassName(ParameterReflection $parameterReflection, PhpDocInfo $phpDocInfo): ?string
{
if (! $parameterReflection instanceof PhpParameterReflection) {
return null;
}

$paramTagValueNode = $phpDocInfo->getParamTagValueByName($parameterReflection->getName());
if (! $paramTagValueNode instanceof ParamTagValueNode) {
return null;
}

if (! $paramTagValueNode->type instanceof ConstTypeNode) {
return null;
}

$constTypeNode = $paramTagValueNode->type;
if (! $constTypeNode->constExpr instanceof ConstFetchNode) {
return null;
}

$constExpr = $constTypeNode->constExpr;
$className = $constExpr->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS);

if (! $this->reflectionProvider->hasClass($className)) {
return null;
}

return $className;
}
}
Loading

0 comments on commit 5f87472

Please sign in to comment.