Skip to content

Commit

Permalink
Keep comments in annotations when converting them to attributes (#5650)
Browse files Browse the repository at this point in the history
* Keep comments in annotations when converting them to attributes

* Also split comment using windows line ending

* Better expression for end of line

* Call parent function in function that prints attribute group

* Better check for existence of comment and update of regex used to find the end of the values

* Better check for existence of comment
  • Loading branch information
Carlos Granados committed Feb 21, 2024
1 parent a4445df commit 7e741fe
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

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

use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;

final class WithDescription
{
/**
* This comment is before the annotations
* @GenericAnnotation this is a simple annotation
* @GenericAnnotation(key="value") this annotation has parameters
* @GenericAnnotation(
* "some" = "item",
* "summary" = "item",
* ) this annotation is multi-line
* @GenericAnnotation(key="value") (this comment is within parentheses)
* @GenericAnnotation(key="value") "this comment is within quotes"
* This comment does not belong to an annotation and will be ignored
*/
protected $someColumn;
}

?>
-----
<?php

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

use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;

final class WithDescription
{
/**
* This comment is before the annotations
*/
#[GenericAnnotation] // this is a simple annotation
#[GenericAnnotation(key: 'value')] // this annotation has parameters
#[GenericAnnotation(some: 'item', summary: 'item')] // this annotation is multi-line
#[GenericAnnotation(key: 'value')] // (this comment is within parentheses)
#[GenericAnnotation(key: 'value')] // "this comment is within quotes"
protected $someColumn;
}

?>
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@ private function shouldSkipClass(Class_ $class): bool
return true;
}

return $class->extends instanceof FullyQualified && ! $this->reflectionProvider->hasClass($class->extends->toString());
return $class->extends instanceof FullyQualified && ! $this->reflectionProvider->hasClass(
$class->extends->toString()
);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions rules/Php80/Rector/Class_/AnnotationToAttributeRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public function getRuleDefinition(): RuleDefinition
class SymfonyRoute
{
/**
* @Route("/path", name="action")
* @Route("/path", name="action") api route
*/
public function action()
{
Expand All @@ -89,7 +89,7 @@ public function action()
class SymfonyRoute
{
#[Route(path: '/path', name: 'action')]
#[Route(path: '/path', name: 'action')] // api route
public function action()
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

namespace Rector\Transform\Rector\ConstFetch;

use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\ConstFetch;
use Rector\Contract\Rector\ConfigurableRectorInterface;
use Rector\Rector\AbstractRector;
use Rector\Transform\ValueObject\ConstFetchToClassConstFetch;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,7 @@ public function refactor(Node $node): ?Node

if ($name === 'fileExtensions') {
Assert::isAOf($value, Array_::class);
$newExpr = $this->nodeFactory->createMethodCall(
$newExpr,
'withFileExtensions',
[$value]
);
$newExpr = $this->nodeFactory->createMethodCall($newExpr, 'withFileExtensions', [$value]);
$hasChanged = true;

continue;
Expand Down
7 changes: 5 additions & 2 deletions rules/Transform/ValueObject/ConstFetchToClassConstFetch.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@

final readonly class ConstFetchToClassConstFetch
{
public function __construct(private string $oldConstName, private string $newClassName, private string $newConstName)
{
public function __construct(
private string $oldConstName,
private string $newClassName,
private string $newConstName
) {
RectorAssert::constantName($this->oldConstName);
RectorAssert::className($this->newClassName);
RectorAssert::constantName($this->newConstName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use Rector\BetterPhpDocParser\ValueObject\PhpDoc\DoctrineAnnotation\AbstractValuesAwareNode;
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Stringable;

final class DoctrineAnnotationTagValueNode extends AbstractValuesAwareNode implements Stringable
Expand All @@ -18,11 +19,16 @@ public function __construct(
public IdentifierTypeNode $identifierTypeNode,
?string $originalContent = null,
array $values = [],
?string $silentKey = null
?string $silentKey = null,
?string $comment = null
) {
$this->hasChanged = true;

parent::__construct($values, $originalContent, $silentKey);

if (! in_array($comment, ['', null], true)) {
$this->setAttribute(AttributeKey::ATTRIBUTE_COMMENT, $comment);
}
}

public function __toString(): string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,14 +409,20 @@ private function createDoctrineSpacelessPhpDocTagNode(
$currentPhpNode
);

$comment = $this->staticDoctrineAnnotationParser->getCommentFromRestOfAnnotation(
$nestedTokenIterator,
$annotationContent
);

$identifierTypeNode = new IdentifierTypeNode($tagName);
$identifierTypeNode->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $fullyQualifiedAnnotationClass);

$doctrineAnnotationTagValueNode = new DoctrineAnnotationTagValueNode(
$identifierTypeNode,
$annotationContent,
$values,
SilentKeyMap::CLASS_NAMES_TO_SILENT_KEYS[$fullyQualifiedAnnotationClass] ?? null
SilentKeyMap::CLASS_NAMES_TO_SILENT_KEYS[$fullyQualifiedAnnotationClass] ?? null,
$comment
);

$doctrineAnnotationTagValueNode->setAttribute(PhpDocAttributeKey::START_AND_END, $startAndEnd);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Rector\BetterPhpDocParser\PhpDocParser;

use Nette\Utils\Strings;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
Expand All @@ -21,6 +22,18 @@
*/
final readonly class StaticDoctrineAnnotationParser
{
/**
* @var string
* @see https://regex101.com/r/aU2knc/1
*/
private const NEWLINES_REGEX = "#\r?\n#";

/**
* @var string
* @see https://regex101.com/r/Pthg5d/1
*/
private const END_OF_VALUE_CHARACTERS_REGEX = '/^[)} \r\n"\']+$/i';

public function __construct(
private PlainValueParser $plainValueParser,
private ArrayParser $arrayParser
Expand Down Expand Up @@ -80,6 +93,24 @@ public function resolveAnnotationValue(
];
}

public function getCommentFromRestOfAnnotation(
BetterTokenIterator $tokenIterator,
string $annotationContent
): string {
// we skip all the remaining tokens from the end of the declaration of values
while (
preg_match(self::END_OF_VALUE_CHARACTERS_REGEX, $tokenIterator->currentTokenValue())
) {
$tokenIterator->next();
}

// the remaining of the annotation content is the comment
$comment = substr($annotationContent, $tokenIterator->currentTokenOffset());
// we only keep the first line as this will be added as a line comment at the end of the attribute
$commentLines = Strings::split($comment, self::NEWLINES_REGEX);
return $commentLines[0];
}

/**
* @see https://github.com/doctrine/annotations/blob/c66f06b7c83e9a2a7523351a9d5a4b55f885e574/lib/Doctrine/Common/Annotations/DocParser.php#L1051-L1079
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Rector\NodeTypeResolver\DependencyInjection;

use Throwable;
use PhpParser\Lexer;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\ScopeFactory;
Expand All @@ -20,6 +19,7 @@
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Style\SymfonyStyle;
use Throwable;
use Webmozart\Assert\Assert;

/**
Expand All @@ -29,8 +29,6 @@
*/
final readonly class PHPStanServicesFactory
{
private Container $container;

/**
* @var string
*/
Expand All @@ -44,6 +42,8 @@
MESSAGE_ERROR;

private Container $container;

public function __construct()
{
$containerFactory = new ContainerFactory(getcwd());
Expand All @@ -57,7 +57,6 @@ public function __construct()
);
} catch (Throwable $throwable) {
if ($throwable->getMessage() === "File 'phar://phpstan.phar/conf/bleedingEdge.neon' is missing or is not readable.") {

$symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput());
$symfonyStyle->error(sprintf(self::INVALID_BLEEDING_EDGE_PATH_MESSAGE, $throwable->getMessage()));

Expand Down
5 changes: 5 additions & 0 deletions src/NodeTypeResolver/Node/AttributeKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -301,4 +301,9 @@ final class AttributeKey
* @var string
*/
public const IS_USED_AS_ARG_BY_REF_VALUE = 'is_used_as_arg_by_ref_value';

/**
* @var string
*/
public const ATTRIBUTE_COMMENT = 'attribute_comment';
}
7 changes: 6 additions & 1 deletion src/PhpAttribute/NodeFactory/PhpAttributeGroupFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ public function create(
$attributeName->setAttribute(AttributeKey::PHP_ATTRIBUTE_NAME, $annotationToAttribute->getAttributeClass());

$attribute = new Attribute($attributeName, $args);
return new AttributeGroup([$attribute]);
$attributeGroup = new AttributeGroup([$attribute]);
$comment = $doctrineAnnotationTagValueNode->getAttribute(AttributeKey::ATTRIBUTE_COMMENT);
if ($comment) {
$attributeGroup->setAttribute(AttributeKey::ATTRIBUTE_COMMENT, $comment);
}
return $attributeGroup;
}

/**
Expand Down
10 changes: 10 additions & 0 deletions src/PhpParser/Printer/BetterStandardPrinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@ protected function p(Node $node, $parentFormatPreserved = false): string
: $content;
}

protected function pAttributeGroup(Node\AttributeGroup $node): string
{
$ret = parent::pAttributeGroup($node);
$comment = $node->getAttribute(AttributeKey::ATTRIBUTE_COMMENT);
if (! in_array($comment, ['', null], true)) {
$ret .= ' // ' . $comment;
}
return $ret;
}

protected function pExpr_ArrowFunction(ArrowFunction $arrowFunction): string
{
if (! $arrowFunction->hasAttribute(AttributeKey::COMMENT_CLOSURE_RETURN_MIRRORED)) {
Expand Down

0 comments on commit 7e741fe

Please sign in to comment.