make tag value matching more precise

This commit is contained in:
Tomas Votruba 2019-10-04 14:55:26 +02:00
parent 7e16b2f477
commit 8f5ac69e2f
44 changed files with 440 additions and 107 deletions

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\BetterPhpDocParser\Contract;
interface ClassAwarePhpDocNodeFactoryInterface extends PhpDocNodeFactoryInterface
{
public function getClass(): string;
}

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\BetterPhpDocParser\Contract;
interface NameAwarePhpDocNodeFactoryInterface extends PhpDocNodeFactoryInterface
{
public function getName(): string;
}

View File

@ -8,7 +8,5 @@ use PHPStan\PhpDocParser\Parser\TokenIterator;
interface PhpDocNodeFactoryInterface
{
public function getName(): string;
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode;
}

View File

@ -5,11 +5,11 @@ namespace Rector\BetterPhpDocParser\PhpDocNodeFactory;
use PhpParser\Node;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use Rector\BetterPhpDocParser\AnnotationReader\NodeAnnotationReader;
use Rector\BetterPhpDocParser\Contract\PhpDocNodeFactoryInterface;
use Rector\BetterPhpDocParser\Contract\ClassAwarePhpDocNodeFactoryInterface;
use Rector\BetterPhpDocParser\PhpDocParser\AnnotationContentResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
abstract class AbstractPhpDocNodeFactory implements PhpDocNodeFactoryInterface
abstract class AbstractPhpDocNodeFactory implements ClassAwarePhpDocNodeFactoryInterface
{
/**
* @var NodeAnnotationReader

View File

@ -13,9 +13,9 @@ use Rector\Exception\ShouldNotHappenException;
final class EntityPhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return EntityTagValueNode::SHORT_NAME;
return Entity::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
@ -25,7 +25,7 @@ final class EntityPhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var Entity|null $entity */
$entity = $this->nodeAnnotationReader->readClassAnnotation($node, Entity::class);
$entity = $this->nodeAnnotationReader->readClassAnnotation($node, $this->getClass());
if ($entity === null) {
return null;
}

View File

@ -13,9 +13,9 @@ use Rector\Exception\ShouldNotHappenException;
final class InheritanceTypePhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return InheritanceTypeTagValueNode::SHORT_NAME;
return InheritanceType::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
@ -25,7 +25,7 @@ final class InheritanceTypePhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var InheritanceType|null $inheritanceType */
$inheritanceType = $this->nodeAnnotationReader->readClassAnnotation($node, InheritanceType::class);
$inheritanceType = $this->nodeAnnotationReader->readClassAnnotation($node, $this->getClass());
if ($inheritanceType === null) {
return null;
}

View File

@ -32,9 +32,9 @@ final class TablePhpDocNodeFactory extends AbstractPhpDocNodeFactory
$this->uniqueConstraintPhpDocNodeFactory = $uniqueConstraintPhpDocNodeFactory;
}
public function getName(): string
public function getClass(): string
{
return TableTagValueNode::SHORT_NAME;
return Table::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
@ -44,7 +44,7 @@ final class TablePhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var Table|null $table */
$table = $this->nodeAnnotationReader->readClassAnnotation($node, Table::class);
$table = $this->nodeAnnotationReader->readClassAnnotation($node, $this->getClass());
if ($table === null) {
return null;
}

View File

@ -13,9 +13,9 @@ use Rector\Exception\ShouldNotHappenException;
final class ColumnPhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return ColumnTagValueNode::SHORT_NAME;
return Column::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
@ -25,7 +25,7 @@ final class ColumnPhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var Column|null $column */
$column = $this->nodeAnnotationReader->readPropertyAnnotation($node, Column::class);
$column = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($column === null) {
return null;
}

View File

@ -13,9 +13,9 @@ use Rector\Exception\ShouldNotHappenException;
final class CustomIdGeneratorPhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return CustomIdGeneratorTagValueNode::SHORT_NAME;
return CustomIdGenerator::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
@ -27,7 +27,7 @@ final class CustomIdGeneratorPhpDocNodeFactory extends AbstractPhpDocNodeFactory
$annotationContent = $this->resolveContentFromTokenIterator($tokenIterator);
/** @var CustomIdGenerator|null $customIdGenerator */
$customIdGenerator = $this->nodeAnnotationReader->readPropertyAnnotation($node, CustomIdGenerator::class);
$customIdGenerator = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($customIdGenerator === null) {
return null;
}

View File

@ -13,9 +13,9 @@ use Rector\Exception\ShouldNotHappenException;
final class GeneratedValuePhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return GeneratedValueTagValueNode::SHORT_NAME;
return GeneratedValue::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
@ -25,7 +25,7 @@ final class GeneratedValuePhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var GeneratedValue|null $generatedValue */
$generatedValue = $this->nodeAnnotationReader->readPropertyAnnotation($node, GeneratedValue::class);
$generatedValue = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($generatedValue === null) {
return null;
}

View File

@ -2,6 +2,7 @@
namespace Rector\BetterPhpDocParser\PhpDocNodeFactory\Doctrine\Property_;
use Doctrine\ORM\Mapping\Id;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Parser\TokenIterator;
@ -10,9 +11,9 @@ use Rector\BetterPhpDocParser\PhpDocNodeFactory\AbstractPhpDocNodeFactory;
final class IdPhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return IdTagValueNode::SHORT_NAME;
return Id::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode

View File

@ -13,9 +13,9 @@ use Rector\Exception\ShouldNotHappenException;
final class JoinColumnPhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return JoinColumnTagValueNode::SHORT_NAME;
return JoinColumn::class;
}
/**
@ -28,7 +28,7 @@ final class JoinColumnPhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var JoinColumn|null $joinColumn */
$joinColumn = $this->nodeAnnotationReader->readPropertyAnnotation($node, JoinColumn::class);
$joinColumn = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($joinColumn === null) {
return null;
}

View File

@ -30,9 +30,9 @@ final class JoinTablePhpDocNodeFactory extends AbstractPhpDocNodeFactory
$this->joinColumnPhpDocNodeFactory = $joinColumnPhpDocNodeFactory;
}
public function getName(): string
public function getClass(): string
{
return JoinTableTagValueNode::SHORT_NAME;
return JoinTable::class;
}
/**
@ -45,7 +45,7 @@ final class JoinTablePhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var JoinTable|null $joinTable */
$joinTable = $this->nodeAnnotationReader->readPropertyAnnotation($node, JoinTable::class);
$joinTable = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($joinTable === null) {
return null;
}

View File

@ -13,9 +13,9 @@ use Rector\Exception\ShouldNotHappenException;
final class ManyToManyPhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return ManyToManyTagValueNode::SHORT_NAME;
return ManyToMany::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
@ -27,7 +27,7 @@ final class ManyToManyPhpDocNodeFactory extends AbstractPhpDocNodeFactory
$annotationContent = $this->resolveContentFromTokenIterator($tokenIterator);
/** @var ManyToMany|null $manyToMany */
$manyToMany = $this->nodeAnnotationReader->readPropertyAnnotation($node, ManyToMany::class);
$manyToMany = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($manyToMany === null) {
return null;
}

View File

@ -13,9 +13,9 @@ use Rector\Exception\ShouldNotHappenException;
final class ManyToOnePhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return ManyToOneTagValueNode::SHORT_NAME;
return ManyToOne::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
@ -27,7 +27,7 @@ final class ManyToOnePhpDocNodeFactory extends AbstractPhpDocNodeFactory
$annotationContent = $this->resolveContentFromTokenIterator($tokenIterator);
/** @var ManyToOne|null $manyToOne */
$manyToOne = $this->nodeAnnotationReader->readPropertyAnnotation($node, ManyToOne::class);
$manyToOne = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($manyToOne === null) {
return null;
}

View File

@ -13,9 +13,9 @@ use Rector\Exception\ShouldNotHappenException;
final class OneToManyPhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return OneToManyTagValueNode::SHORT_NAME;
return OneToMany::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
@ -27,7 +27,7 @@ final class OneToManyPhpDocNodeFactory extends AbstractPhpDocNodeFactory
$annotationContent = $this->resolveContentFromTokenIterator($tokenIterator);
/** @var OneToMany|null $oneToMany */
$oneToMany = $this->nodeAnnotationReader->readPropertyAnnotation($node, OneToMany::class);
$oneToMany = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($oneToMany === null) {
return null;
}

View File

@ -13,9 +13,9 @@ use Rector\Exception\ShouldNotHappenException;
final class OneToOnePhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return OneToOneTagValueNode::SHORT_NAME;
return OneToOne::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
@ -25,7 +25,7 @@ final class OneToOnePhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var OneToOne|null $oneToOne */
$oneToOne = $this->nodeAnnotationReader->readPropertyAnnotation($node, OneToOne::class);
$oneToOne = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($oneToOne === null) {
return null;
}

View File

@ -23,9 +23,9 @@ final class JMSInjectPhpDocNodeFactory extends AbstractPhpDocNodeFactory
$this->nameResolver = $nameResolver;
}
public function getName(): string
public function getClass(): string
{
return JMSInjectTagValueNode::SHORT_NAME;
return Inject::class;
}
/**
@ -38,7 +38,7 @@ final class JMSInjectPhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var Inject|null $inject */
$inject = $this->nodeAnnotationReader->readPropertyAnnotation($node, JMSInjectTagValueNode::CLASS_NAME);
$inject = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($inject === null) {
return null;
}

View File

@ -13,9 +13,9 @@ use Rector\Exception\ShouldNotHappenException;
final class SerializerTypePhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return SerializerTypeTagValueNode::SHORT_NAME;
return Type::class;
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
@ -25,7 +25,7 @@ final class SerializerTypePhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var Type|null $type */
$type = $this->nodeAnnotationReader->readPropertyAnnotation($node, SerializerTypeTagValueNode::CLASS_NAME);
$type = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($type === null) {
return null;
}

View File

@ -12,9 +12,9 @@ use Rector\BetterPhpDocParser\PhpDocNodeFactory\AbstractPhpDocNodeFactory;
final class PHPDIInjectPhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return PHPDIInjectTagValueNode::SHORT_NAME;
return Inject::class;
}
/**
@ -27,7 +27,7 @@ final class PHPDIInjectPhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var Inject|null $inject */
$inject = $this->nodeAnnotationReader->readPropertyAnnotation($node, PHPDIInjectTagValueNode::CLASS_NAME);
$inject = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($inject === null) {
return null;
}

View File

@ -11,12 +11,12 @@ use PHPStan\PhpDocParser\Parser\ParserException;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareParamTagValueNode;
use Rector\BetterPhpDocParser\Contract\PhpDocNodeFactoryInterface;
use Rector\BetterPhpDocParser\Contract\NameAwarePhpDocNodeFactoryInterface;
use Rector\BetterPhpDocParser\Contract\PhpDocParserAwareInterface;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
use Symplify\PackageBuilder\Reflection\PrivatesCaller;
final class ParamPhpDocNodeFactory implements PhpDocNodeFactoryInterface, PhpDocParserAwareInterface
final class ParamPhpDocNodeFactory implements NameAwarePhpDocNodeFactoryInterface, PhpDocParserAwareInterface
{
/**
* @var PrivatesAccessor
@ -41,7 +41,7 @@ final class ParamPhpDocNodeFactory implements PhpDocNodeFactoryInterface, PhpDoc
public function getName(): string
{
return '@param';
return 'param';
}
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode

View File

@ -12,9 +12,9 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
final class SensioMethodPhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return SensioMethodTagValueNode::SHORT_NAME;
return Method::class;
}
/**
@ -27,7 +27,7 @@ final class SensioMethodPhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var Method|null $method */
$method = $this->nodeAnnotationReader->readMethodAnnotation($node, SensioMethodTagValueNode::CLASS_NAME);
$method = $this->nodeAnnotationReader->readMethodAnnotation($node, $this->getClass());
if ($method === null) {
return null;
}

View File

@ -12,9 +12,9 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
final class SensioTemplatePhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return SensioTemplateTagValueNode::SHORT_NAME;
return Template::class;
}
/**
@ -27,7 +27,7 @@ final class SensioTemplatePhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var Template|null $template */
$template = $this->nodeAnnotationReader->readMethodAnnotation($node, SensioTemplateTagValueNode::CLASS_NAME);
$template = $this->nodeAnnotationReader->readMethodAnnotation($node, $this->getClass());
if ($template === null) {
return null;
}

View File

@ -12,9 +12,9 @@ use Symfony\Component\Routing\Annotation\Route;
final class SymfonyRoutePhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return SymfonyRouteTagValueNode::SHORT_NAME;
return Route::class;
}
/**
@ -27,7 +27,7 @@ final class SymfonyRoutePhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var Route|null $route */
$route = $this->nodeAnnotationReader->readMethodAnnotation($node, SymfonyRouteTagValueNode::CLASS_NAME);
$route = $this->nodeAnnotationReader->readMethodAnnotation($node, $this->getClass());
if ($route === null) {
return null;
}

View File

@ -12,9 +12,9 @@ use Symfony\Component\Validator\Constraints\Choice;
final class AssertChoicePhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return AssertChoiceTagValueNode::SHORT_NAME;
return Choice::class;
}
/**
@ -27,7 +27,7 @@ final class AssertChoicePhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var Choice|null $choice */
$choice = $this->nodeAnnotationReader->readPropertyAnnotation($node, AssertChoiceTagValueNode::CLASS_NAME);
$choice = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($choice === null) {
return null;
}

View File

@ -12,9 +12,9 @@ use Symfony\Component\Validator\Constraints\Type;
final class AssertTypePhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getName(): string
public function getClass(): string
{
return AssertTypeTagValueNode::SHORT_NAME;
return Type::class;
}
/**
@ -27,7 +27,7 @@ final class AssertTypePhpDocNodeFactory extends AbstractPhpDocNodeFactory
}
/** @var Type|null $type */
$type = $this->nodeAnnotationReader->readPropertyAnnotation($node, AssertTypeTagValueNode::CLASS_NAME);
$type = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($type === null) {
return null;
}

View File

@ -3,6 +3,7 @@
namespace Rector\BetterPhpDocParser\PhpDocParser;
use Nette\Utils\Strings;
use PhpParser\Node as PhpNode;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
@ -16,6 +17,8 @@ use PHPStan\PhpDocParser\Parser\TypeParser;
use Rector\BetterPhpDocParser\Attributes\Ast\AttributeAwareNodeFactory;
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocNode;
use Rector\BetterPhpDocParser\Attributes\Attribute\Attribute;
use Rector\BetterPhpDocParser\Contract\ClassAwarePhpDocNodeFactoryInterface;
use Rector\BetterPhpDocParser\Contract\NameAwarePhpDocNodeFactoryInterface;
use Rector\BetterPhpDocParser\Contract\PhpDocNodeFactoryInterface;
use Rector\BetterPhpDocParser\Contract\PhpDocParserAwareInterface;
use Rector\BetterPhpDocParser\Printer\MultilineSpaceFormatPreserver;
@ -65,6 +68,11 @@ final class BetterPhpDocParser extends PhpDocParser
*/
private $currentNodeProvider;
/**
* @var ClassAnnotationMatcher
*/
private $classAnnotationMatcher;
/**
* @param PhpDocNodeFactoryInterface[] $phpDocNodeFactories
*/
@ -74,6 +82,7 @@ final class BetterPhpDocParser extends PhpDocParser
AttributeAwareNodeFactory $attributeAwareNodeFactory,
MultilineSpaceFormatPreserver $multilineSpaceFormatPreserver,
CurrentNodeProvider $currentNodeProvider,
ClassAnnotationMatcher $classAnnotationMatcher,
array $phpDocNodeFactories = []
) {
parent::__construct($typeParser, $constExprParser);
@ -84,6 +93,7 @@ final class BetterPhpDocParser extends PhpDocParser
$this->multilineSpaceFormatPreserver = $multilineSpaceFormatPreserver;
$this->phpDocNodeFactories = $phpDocNodeFactories;
$this->currentNodeProvider = $currentNodeProvider;
$this->classAnnotationMatcher = $classAnnotationMatcher;
}
/**
@ -143,9 +153,12 @@ final class BetterPhpDocParser extends PhpDocParser
}
// compare regardless sensitivity
if ($this->isTagMatchingPhpDocNodeFactory($tag, $phpDocNodeFactory)) {
$currentNode = $this->currentNodeProvider->getNode();
$tagValueNode = $phpDocNodeFactory->createFromNodeAndTokens($currentNode, $tokenIterator);
$currentPhpNode = $this->currentNodeProvider->getNode();
if ($this->isTagMatchingPhpDocNodeFactory($tag, $phpDocNodeFactory, $currentPhpNode)) {
$tagValueNode = $phpDocNodeFactory->createFromNodeAndTokens($currentPhpNode, $tokenIterator);
if ($tagValueNode !== null) {
break;
}
}
}
@ -220,33 +233,17 @@ final class BetterPhpDocParser extends PhpDocParser
return (int) $this->privatesAccessor->getPrivateProperty($tokenIterator, 'index');
}
private function isTagMatchingPhpDocNodeFactory(string $tag, PhpDocNodeFactoryInterface $phpDocNodeFactory): bool
{
if (Strings::lower($phpDocNodeFactory->getName()) === Strings::lower($tag)) {
return true;
}
if (in_array($tag, ['@param'], true)) {
return false;
}
// possible short import
if (Strings::contains($phpDocNodeFactory->getName(), '\\')) {
$lastNamePart = Strings::after($phpDocNodeFactory->getName(), '\\', -1);
if (Strings::lower('@' . $lastNamePart) === Strings::lower($tag)) {
return true;
}
}
return false;
}
private function resolveTag(TokenIterator $tokenIterator): string
{
$tag = $tokenIterator->currentTokenValue();
$tokenIterator->next();
// basic annotation
if (Strings::match($tag, '#@(var|param|return|throws)#')) {
return $tag;
}
// is not e.g "@var "
// join tags like "@ORM\Column" etc.
if ($tokenIterator->currentTokenType() !== Lexer::TOKEN_IDENTIFIER) {
@ -268,12 +265,40 @@ final class BetterPhpDocParser extends PhpDocParser
private function isTagMatchedByFactories(string $tag): bool
{
$currentPhpNode = $this->currentNodeProvider->getNode();
foreach ($this->phpDocNodeFactories as $phpDocNodeFactory) {
if ($this->isTagMatchingPhpDocNodeFactory($tag, $phpDocNodeFactory)) {
if ($this->isTagMatchingPhpDocNodeFactory($tag, $phpDocNodeFactory, $currentPhpNode)) {
return true;
}
}
return false;
}
private function isTagMatchingPhpDocNodeFactory(
string $tag,
PhpDocNodeFactoryInterface $phpDocNodeFactory,
PhpNode $phpNode
): bool {
// normalize
$tag = ltrim($tag, '@');
if ($phpDocNodeFactory instanceof NameAwarePhpDocNodeFactoryInterface) {
if (Strings::lower($phpDocNodeFactory->getName()) === Strings::lower($tag)) {
return true;
}
return false;
}
if ($phpDocNodeFactory instanceof ClassAwarePhpDocNodeFactoryInterface) {
return $this->classAnnotationMatcher->isTagMatchToNodeAndClass(
$tag,
$phpNode,
$phpDocNodeFactory->getClass()
);
}
return false;
}
}

View File

@ -0,0 +1,68 @@
<?php declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocParser;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* Matches "@ORM\Entity" to FQN names based on use imports in the file
*/
final class ClassAnnotationMatcher
{
public function isTagMatchToNodeAndClass(string $tag, Node $node, string $matchingClass): bool
{
$tag = ltrim($tag, '@');
$useNodes = $node->getAttribute(AttributeKey::USE_NODES);
if ($useNodes === null) {
return $matchingClass === $tag;
}
$fullyQualifiedClassNode = $this->matchFullAnnotationClassWithUses($tag, $useNodes);
if ($fullyQualifiedClassNode === null) {
return false;
}
return Strings::lower($fullyQualifiedClassNode) === Strings::lower($matchingClass);
}
private function isUseMatchingName(string $tag, UseUse $useUse): bool
{
$shortName = $useUse->alias ? $useUse->alias->name : $useUse->name->getLast();
$shortNamePattern = preg_quote($shortName);
return (bool) Strings::match($tag, '#' . $shortNamePattern . '(\\\\[\w]+)?#i');
}
/**
* @param Use_[] $uses
*/
private function matchFullAnnotationClassWithUses(string $tag, array $uses): ?string
{
foreach ($uses as $use) {
foreach ($use->uses as $useUse) {
if (! $this->isUseMatchingName($tag, $useUse)) {
continue;
}
if ($useUse->alias) {
$unaliasedShortClass = Strings::substring($tag, Strings::length($useUse->alias->toString()));
if (Strings::startsWith($unaliasedShortClass, '\\')) {
return $useUse->name . $unaliasedShortClass;
}
return $useUse->name . '\\' . $unaliasedShortClass;
}
return $useUse->name->toString();
}
}
return null;
}
}

View File

@ -1,5 +1,7 @@
/**
* @Assert\Type("bool")
* @Assert\Type(
* "bool"
* )
* @Assert\NotNull()
* @Serializer\Type("boolean")
* @var bool

View File

@ -1,4 +1,6 @@
/**
* @Assert\Type("bool")
* @Assert\Type(
* "bool"
* )
*/
public $anotherSerializeSingleLine;

View File

@ -11,7 +11,7 @@ use Rector\PhpParser\Node\BetterNodeFinder;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;
abstract class AbstractOrmTagParserTest extends AbstractKernelTestCase
abstract class AbstractPhpDocInfoTest extends AbstractKernelTestCase
{
/**
* @var PhpDocInfoFactory

View File

@ -4,16 +4,17 @@ namespace Rector\BetterPhpDocParser\Tests\PhpDocParser\OrmTagParser\Class_;
use Iterator;
use PhpParser\Node\Stmt\Class_;
use Rector\BetterPhpDocParser\Tests\PhpDocParser\OrmTagParser\AbstractOrmTagParserTest;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\BetterPhpDocParser\Tests\PhpDocParser\OrmTagParser\AbstractPhpDocInfoTest;
final class DoctrinePhpDocParserTest extends AbstractOrmTagParserTest
final class DoctrinePhpDocParserTest extends AbstractPhpDocInfoTest
{
/**
* @dataProvider provideData()
*/
public function test(string $filePath, string $expectedPrintedPhpDoc): void
public function test(string $filePath, string $expectedPrintedPhpDoc, string $type): void
{
$class = $this->parseFileAndGetFirstNodeOfType($filePath, Class_::class);
$class = $this->parseFileAndGetFirstNodeOfType($filePath, $type);
$printedPhpDocInfo = $this->createPhpDocInfoFromNodeAndPrintBackToString($class);
$this->assertStringEqualsFile($expectedPrintedPhpDoc, $printedPhpDocInfo);
@ -21,6 +22,11 @@ final class DoctrinePhpDocParserTest extends AbstractOrmTagParserTest
public function provideData(): Iterator
{
yield [__DIR__ . '/Fixture/SomeEntity.php', __DIR__ . '/Fixture/expected_some_entity.txt'];
yield [__DIR__ . '/Fixture/SomeEntity.php', __DIR__ . '/Fixture/expected_some_entity.txt', Class_::class];
yield [
__DIR__ . '/Fixture/SkipNonDoctrineEntity.php',
__DIR__ . '/Fixture/expected_skip_non_doctrine_entity.txt',
ClassMethod::class,
];
}
}

View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace Rector\BetterPhpDocParser\Tests\PhpDocParser\OrmTagParser\Class_\Fixture;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
final class SkipNonDoctrineEntity
{
/**
* @Entity("user", expr="repository.findOneBy({'legalInformation.payment.uboDeclarationId': uboDeclarationId})")
*/
public function run()
{
}
}

View File

@ -0,0 +1,3 @@
/**
* @Entity("user", expr="repository.findOneBy({'legalInformation.payment.uboDeclarationId': uboDeclarationId})")
*/

View File

@ -4,9 +4,9 @@ namespace Rector\BetterPhpDocParser\Tests\PhpDocParser\OrmTagParser\Property_;
use Iterator;
use PhpParser\Node\Stmt\Property;
use Rector\BetterPhpDocParser\Tests\PhpDocParser\OrmTagParser\AbstractOrmTagParserTest;
use Rector\BetterPhpDocParser\Tests\PhpDocParser\OrmTagParser\AbstractPhpDocInfoTest;
final class OrmTagParserPropertyTest extends AbstractOrmTagParserTest
final class OrmTagParserPropertyTest extends AbstractPhpDocInfoTest
{
/**
* @dataProvider provideData()

View File

@ -210,3 +210,4 @@ parameters:
- '#In method "Rector\\FileSystemRector\\Rector\\AbstractFileSystemRector\:\:moveFile", parameter \$nodes type is "array"\. Please provide a @param annotation to further specify the type of the array\. For instance\: @param int\[\] \$nodes\. More info\: http\://bit\.ly/typehintarray#'
- '#Parameter \#1 \$items of class PhpParser\\Node\\Expr\\Array_ constructor expects array<PhpParser\\Node\\Expr\\ArrayItem\>, array<PhpParser\\Node\\Expr\\ArrayItem\|null\> given#'
- '#Parameter \#1 \$sprintfFuncCall of method Rector\\PhpParser\\NodeTransformer\:\:transformSprintfToArray\(\) expects PhpParser\\Node\\Expr\\FuncCall, PhpParser\\Node given#'
- '#Method Rector\\BetterPhpDocParser\\Tests\\PhpDocParser\\OrmTagParser\\AbstractPhpDocInfoTest\:\:parseFileAndGetFirstNodeOfType\(\) should return PhpParser\\Node but returns PhpParser\\Node\|null#'

View File

@ -133,7 +133,6 @@ PHP
$tagClass = $this->annotationToTagClass[$annotationClass];
$injectTagValueNode = $phpDocInfo->getByType($tagClass);
if ($injectTagValueNode === null) {
continue;
}

View File

@ -25,7 +25,7 @@ final class StubLoader
$robotLoader = new RobotLoader();
$robotLoader->addDirectory($stubDirectory);
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/rector_stubs');
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/_rector_stubs');
$robotLoader->register();
$this->areStubsLoaded = true;

View File

@ -0,0 +1,20 @@
<?php declare(strict_types=1);
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
if (class_exists('Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity')) {
return;
}
/**
* @Annotation
*/
class Entity extends ParamConverter
{
public function setExpr($expr)
{
$options = $this->getOptions();
$options['expr'] = $expr;
$this->setOptions($options);
}
}

View File

@ -0,0 +1,165 @@
<?php declare(strict_types=1);
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
if (class_exists('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter')) {
return;
}
/**
* @Annotation
*/
class ParamConverter extends ConfigurationAnnotation
{
/**
* The parameter name.
*
* @var string
*/
private $name;
/**
* The parameter class.
*
* @var string
*/
private $class;
/**
* An array of options.
*
* @var array
*/
private $options = [];
/**
* Whether or not the parameter is optional.
*
* @var bool
*/
private $isOptional = false;
/**
* Use explicitly named converter instead of iterating by priorities.
*
* @var string
*/
private $converter;
/**
* Returns the parameter name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Sets the parameter name.
*
* @param string $name The parameter name
*/
public function setValue($name)
{
$this->setName($name);
}
/**
* Sets the parameter name.
*
* @param string $name The parameter name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Returns the parameter class name.
*
* @return string $name
*/
public function getClass()
{
return $this->class;
}
/**
* Sets the parameter class name.
*
* @param string $class The parameter class name
*/
public function setClass($class)
{
$this->class = $class;
}
/**
* Returns an array of options.
*
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* Sets an array of options.
*
* @param array $options An array of options
*/
public function setOptions($options)
{
$this->options = $options;
}
/**
* Sets whether or not the parameter is optional.
*
* @param bool $optional Whether the parameter is optional
*/
public function setIsOptional($optional)
{
$this->isOptional = (bool) $optional;
}
/**
* Returns whether or not the parameter is optional.
*
* @return bool
*/
public function isOptional()
{
return $this->isOptional;
}
/**
* Get explicit converter name.
*
* @return string
*/
public function getConverter()
{
return $this->converter;
}
/**
* Set explicit converter name.
*
* @param string $converter
*/
public function setConverter($converter)
{
$this->converter = $converter;
}
/**
* Returns the annotation alias name.
*
* @return string
*
* @see ConfigurationInterface
*/
public function getAliasName()
{
return 'converters';
}
/**
* Multiple ParamConverters are allowed.
*
* @return bool
*
* @see ConfigurationInterface
*/
public function allowArray()
{
return true;
}
}

View File

@ -2,6 +2,10 @@
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
if (class_exists('Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationAnnotation')) {
return;
}
abstract class ConfigurationAnnotation
{
public function __construct(array $values)

View File

@ -2,7 +2,9 @@
namespace Rector\Tests\Rector\Property\InjectAnnotationClassRector;
use DI\Annotation\Inject as PHPDIInject;
use Iterator;
use JMS\DiExtraBundle\Annotation\Inject;
use Rector\Configuration\Option;
use Rector\Rector\Property\InjectAnnotationClassRector;
use Rector\Symfony\Tests\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeKernelClass;
@ -44,7 +46,7 @@ final class InjectAnnotationClassRectorTest extends AbstractRectorTestCase
{
return [
InjectAnnotationClassRector::class => [
'$annotationClasses' => ['JMS\DiExtraBundle\Annotation\Inject', 'DI\Annotation\Inject'],
'$annotationClasses' => [Inject::class, PHPDIInject::class],
],
];
}

View File

@ -2,6 +2,8 @@
declare(strict_types=1);
use Rector\Stubs\StubLoader;
require_once __DIR__ . '/../vendor/autoload.php';
// silent deprecations, since we test them
@ -9,3 +11,7 @@ error_reporting(E_ALL ^ E_DEPRECATED);
// performance boost
gc_disable();
// load stubs
$stubLoader = new StubLoader();
$stubLoader->loadStubs();