From 08c41c13fcf47c40385153f6347d6ce9bc1f1970 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 9 Jul 2021 20:40:44 +0200 Subject: [PATCH 1/3] [CodingStandard] Add DoctrineAnnotationNestedBracketsFixer --- packages/coding-standard/config/config.php | 2 + .../DoctrineAnnotationNestedBracketsFixer.php | 165 ++++++++++++++++++ .../Commenting/RemoveCommentedCodeFixer.php | 3 +- .../LineLength/DocBlockLineLengthFixer.php | 3 + .../DoctrineAnnotationElementAnalyzer.php | 76 ++++++++ .../DoctrineAnnotationNameResolver.php | 45 +++++ ...trineAnnotationNestedBracketsFixerTest.php | 34 ++++ .../skip_not_configured_annotation.php.inc | 16 ++ ...nested_annotation_without_brackets.php.inc | 37 ++++ .../Source/MissingYou.php | 9 + .../Source/SomeAnnotation.php | 9 + .../Source/SomeAnnotations.php | 9 + .../config/configured_rule.php | 15 ++ phpstan.neon | 3 + 14 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 packages/coding-standard/src/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer.php create mode 100644 packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationElementAnalyzer.php create mode 100644 packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationNameResolver.php create mode 100644 packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/DoctrineAnnotationNestedBracketsFixerTest.php create mode 100644 packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Fixture/skip_not_configured_annotation.php.inc create mode 100644 packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Fixture/some_nested_annotation_without_brackets.php.inc create mode 100644 packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Source/MissingYou.php create mode 100644 packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Source/SomeAnnotation.php create mode 100644 packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Source/SomeAnnotations.php create mode 100644 packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/config/configured_rule.php diff --git a/packages/coding-standard/config/config.php b/packages/coding-standard/config/config.php index 0e99225f44..bbdf4bf508 100644 --- a/packages/coding-standard/config/config.php +++ b/packages/coding-standard/config/config.php @@ -3,6 +3,7 @@ declare(strict_types=1); use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symplify\EasyCodingStandard\Caching\ChangedFilesDetector; use Symplify\PackageBuilder\Reflection\PrivatesAccessor; @@ -24,6 +25,7 @@ __DIR__ . '/../src/ValueObject', ]); + $services->set(NamespaceUsesAnalyzer::class); $services->set(FunctionsAnalyzer::class); $services->set(PrivatesAccessor::class); diff --git a/packages/coding-standard/src/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer.php b/packages/coding-standard/src/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer.php new file mode 100644 index 0000000000..ee7038f049 --- /dev/null +++ b/packages/coding-standard/src/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer.php @@ -0,0 +1,165 @@ + ['MainAnnotation'], + ] + ), + ]); + } + + /** + * @param array $configuration + */ + public function configure(array $configuration): void + { + $annotationsClasses = $configuration[self::ANNOTATION_CLASSES] ?? []; + Assert::isArray($annotationsClasses); + Assert::allString($annotationsClasses); + + $this->annotationClasses = $annotationsClasses; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + public function fix(\SplFileInfo $fileInfo, Tokens $tokens): void + { + $useDeclarations = $this->namespaceUsesAnalyzer->getDeclarationsFromTokens($tokens); + + // fetch indexes one time, this is safe as we never add or remove a token during fixing + + /** @var \PhpCsFixer\Tokenizer\Token[] $docCommentTokens */ + $docCommentTokens = $tokens->findGivenKind(T_DOC_COMMENT); + foreach ($docCommentTokens as $index => $docCommentToken) { + if (! $this->doctrineAnnotationElementAnalyzer->detect($tokens, $index)) { + continue; + } + + $doctrineAnnotationTokens = DoctrineAnnotationTokens::createFromDocComment($docCommentToken, []); + $this->fixAnnotations($doctrineAnnotationTokens, $useDeclarations); + + $tokens[$index] = new \PhpCsFixer\Tokenizer\Token([T_DOC_COMMENT, $doctrineAnnotationTokens->getCode()]); + } + } + + /** + * @param DoctrineAnnotationTokens $tokens + */ + private function fixAnnotations(DoctrineAnnotationTokens $tokens, $useDeclarations): void + { + foreach ($tokens as $index => $token) { + $isAtToken = $tokens[$index]->isType(DocLexer::T_AT); + if (! $isAtToken) { + continue; + } + + $annotationName = $this->annotationNameResolver->resolveName($tokens, $index, $useDeclarations); + if ($annotationName === null) { + continue; + } + + if (! in_array($annotationName, $this->annotationClasses, true)) { + continue; + } + + $closingBraceIndex = $tokens->getAnnotationEnd($index); + if ($closingBraceIndex === null) { + continue; + } + + $braceIndex = $tokens->getNextMeaningfulToken($index + 1); + if ($braceIndex === null) { + continue; + } + + /** @var Token $braceToken */ + $braceToken = $tokens[$braceIndex]; + if (! $this->doctrineAnnotationElementAnalyzer->isOpeningBracketFollowedByAnnotation( + $braceToken, + $tokens, + $braceIndex + )) { + continue; + } + + // add closing brace + $tokens->insertAt($closingBraceIndex, new Token(DocLexer::T_OPEN_CURLY_BRACES, '}')); + + // add opening brace + $tokens->insertAt($braceIndex + 1, new Token(DocLexer::T_OPEN_CURLY_BRACES, '{')); + } + } +} diff --git a/packages/coding-standard/src/Fixer/Commenting/RemoveCommentedCodeFixer.php b/packages/coding-standard/src/Fixer/Commenting/RemoveCommentedCodeFixer.php index b49599accc..9e3f68e9d0 100644 --- a/packages/coding-standard/src/Fixer/Commenting/RemoveCommentedCodeFixer.php +++ b/packages/coding-standard/src/Fixer/Commenting/RemoveCommentedCodeFixer.php @@ -19,7 +19,8 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** - * @see https://softwareengineering.stackexchange.com/a/394288/148956 + * @deprecated This rule is seriously buggy. Don't use it. We're working on a better replacement + * @see https://github.com/symplify/symplify/issues/3395 * * @see \Symplify\CodingStandard\Tests\Fixer\Commenting\RemoveCommentedCodeFixer\RemoveCommentedCodeFixerTest */ diff --git a/packages/coding-standard/src/Fixer/LineLength/DocBlockLineLengthFixer.php b/packages/coding-standard/src/Fixer/LineLength/DocBlockLineLengthFixer.php index b4ee86f629..fdf96103ee 100644 --- a/packages/coding-standard/src/Fixer/LineLength/DocBlockLineLengthFixer.php +++ b/packages/coding-standard/src/Fixer/LineLength/DocBlockLineLengthFixer.php @@ -122,6 +122,9 @@ public function fix(SplFileInfo $file, Tokens $tokens): void } } + /** + * @param array $configuration + */ public function configure(array $configuration): void { $this->lineLength = $configuration[self::LINE_LENGTH] ?? self::DEFAULT_LINE_LENGHT; diff --git a/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationElementAnalyzer.php b/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationElementAnalyzer.php new file mode 100644 index 0000000000..6d51b46ed2 --- /dev/null +++ b/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationElementAnalyzer.php @@ -0,0 +1,76 @@ +getClassyElements(); + + do { + $index = $tokens->getNextMeaningfulToken($index); + + if ($index === null) { + return false; + } + } while ($tokens[$index]->isGivenKind([T_ABSTRACT, T_FINAL])); + + if ($tokens[$index]->isClassy()) { + return true; + } + + while ($tokens[$index]->isGivenKind( + [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_NS_SEPARATOR, T_STRING, CT::T_NULLABLE_TYPE] + )) { + $index = $tokens->getNextMeaningfulToken($index); + if (! is_int($index)) { + return false; + } + } + + return isset($classyElements[$index]); + } + + /** + * We look for "(@SomeAnnotation" + * + * @param DoctrineAnnotationTokens $tokens + */ + public function isOpeningBracketFollowedByAnnotation( + Token $token, + DoctrineAnnotationTokens $tokens, + int $braceIndex + ): bool { + // should be "(" + $isNextOpenParenthesis = $token->isType(DocLexer::T_OPEN_PARENTHESIS); + if (! $isNextOpenParenthesis) { + return false; + } + + $nextTokenIndex = $tokens->getNextMeaningfulToken($braceIndex + 1); + if ($nextTokenIndex === null) { + return false; + } + + /** @var Token $nextToken */ + $nextToken = $tokens[$nextTokenIndex]; + + // next token must be nested annotation, we don't care otherwise + return $nextToken->isType(DocLexer::T_AT); + } +} diff --git a/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationNameResolver.php b/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationNameResolver.php new file mode 100644 index 0000000000..d3adec27d3 --- /dev/null +++ b/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationNameResolver.php @@ -0,0 +1,45 @@ + $tokens + * @param NamespaceUseAnalysis[] $namespaceUseAnalyses + */ + public function resolveName(Tokens $tokens, int $index, array $namespaceUseAnalyses): ?string + { + $openParenthesisPosition = $tokens->getNextTokenOfType(DocLexer::T_OPEN_PARENTHESIS, $index); + if ($openParenthesisPosition === null) { + return null; + } + + $annotationShortName = ''; + + for ($i = $index + 1; $i < $openParenthesisPosition; ++$i) { + /** @var Token $currentToken */ + $currentToken = $tokens[$i]; + $annotationShortName .= $currentToken->getContent(); + } + + if ($annotationShortName) { + foreach ($namespaceUseAnalyses as $namespaceUseAnalysis) { + if ($namespaceUseAnalysis->getShortName() === $annotationShortName) { + return $namespaceUseAnalysis->getFullName(); + } + } + + return $annotationShortName; + } + + return null; + } +} diff --git a/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/DoctrineAnnotationNestedBracketsFixerTest.php b/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/DoctrineAnnotationNestedBracketsFixerTest.php new file mode 100644 index 0000000000..3c7cd23e65 --- /dev/null +++ b/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/DoctrineAnnotationNestedBracketsFixerTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return StaticFixtureFinder::yieldDirectoryExclusively(__DIR__ . '/Fixture'); + } + + public function provideConfig(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Fixture/skip_not_configured_annotation.php.inc b/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Fixture/skip_not_configured_annotation.php.inc new file mode 100644 index 0000000000..57ef797528 --- /dev/null +++ b/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Fixture/skip_not_configured_annotation.php.inc @@ -0,0 +1,16 @@ + +----- + diff --git a/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Source/MissingYou.php b/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Source/MissingYou.php new file mode 100644 index 0000000000..10ef8634ab --- /dev/null +++ b/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Source/MissingYou.php @@ -0,0 +1,9 @@ +services(); + $services->set(DoctrineAnnotationNestedBracketsFixer::class) + ->call('configure', [[ + DoctrineAnnotationNestedBracketsFixer::ANNOTATION_CLASSES => [SomeAnnotations::class], + ]]); +}; diff --git a/phpstan.neon b/phpstan.neon index ddd46aa9e7..d5f5234a19 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -524,3 +524,6 @@ parameters: - message: '#Cognitive complexity for "Symplify\\SimplePhpDocParser\\PhpDocNodeTraverser\:\:(.*?)\(\)" is \d+, keep it under \d+#' path: packages/simple-php-doc-parser/src/PhpDocNodeTraverser.php + + # wrong php-cs-fixer doc types + - '#Parameter \#1 \$type of method PhpCsFixer\\Doctrine\\Annotation\\Tokens\:\:getNextTokenOfType\(\) expects array\|string, int given#' From e11f76bec914c6ea54f2727db7a2a2c3b0c27881 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 10 Jul 2021 00:55:30 +0200 Subject: [PATCH 2/3] bump rector --- composer.json | 2 +- ecs.php | 6 ++++++ .../SimpleCallableNodeTraverser.php | 2 +- .../Fixture/skip_already_wrapped.php.inc | 18 ++++++++++++++++++ .../src/Bundle/SimplePhpDocParserBundle.php | 3 +-- .../CloningPhpDocNodeVisitor.php | 5 +---- .../ParentConnectingPhpDocNodeVisitor.php | 5 +---- 7 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Fixture/skip_already_wrapped.php.inc diff --git a/composer.json b/composer.json index 656a3fa188..3ec3d0a8ea 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,7 @@ "ondram/ci-detector": "^4.1", "phpunit/phpunit": "^9.5", "psr/log": "^1.1", - "rector/rector": "dev-main#ecda10a", + "rector/rector": "dev-main#e219401", "symfony/doctrine-bridge": "^5.3", "symfony/framework-bundle": "^5.3", "symfony/security-bundle": "^5.3", diff --git a/ecs.php b/ecs.php index 31c24a1720..99ac3fd711 100644 --- a/ecs.php +++ b/ecs.php @@ -4,6 +4,7 @@ use PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symplify\CodingStandard\Fixer\Annotation\DoctrineAnnotationNestedBracketsFixer; use Symplify\CodingStandard\Fixer\LineLength\LineLengthFixer; use Symplify\EasyCodingStandard\ValueObject\Option; use Symplify\EasyCodingStandard\ValueObject\Set\SetList; @@ -12,6 +13,11 @@ $services = $containerConfigurator->services(); $services->set(LineLengthFixer::class); + $services->set(DoctrineAnnotationNestedBracketsFixer::class) + ->call('configure', [[ + DoctrineAnnotationNestedBracketsFixer::ANNOTATION_CLASSES => ['Doctrine\ORM\JoinColumns'], + ]]); + $containerConfigurator->import(SetList::CLEAN_CODE); $containerConfigurator->import(SetList::SYMPLIFY); $containerConfigurator->import(SetList::COMMON); diff --git a/packages/astral/src/NodeTraverser/SimpleCallableNodeTraverser.php b/packages/astral/src/NodeTraverser/SimpleCallableNodeTraverser.php index 779f9e9597..fd8470d9ee 100644 --- a/packages/astral/src/NodeTraverser/SimpleCallableNodeTraverser.php +++ b/packages/astral/src/NodeTraverser/SimpleCallableNodeTraverser.php @@ -17,7 +17,7 @@ final class SimpleCallableNodeTraverser /** * @param Node|Node[]|null $nodes */ - public function traverseNodesWithCallable($nodes, callable $callable): void + public function traverseNodesWithCallable(Node | array | null $nodes, callable $callable): void { if ($nodes === null) { return; diff --git a/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Fixture/skip_already_wrapped.php.inc b/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Fixture/skip_already_wrapped.php.inc new file mode 100644 index 0000000000..44fd38ba91 --- /dev/null +++ b/packages/coding-standard/tests/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer/Fixture/skip_already_wrapped.php.inc @@ -0,0 +1,18 @@ +setAttribute(PhpDocAttributeKey::ORIG_NODE, $origNode); diff --git a/packages/simple-php-doc-parser/src/PhpDocNodeVisitor/ParentConnectingPhpDocNodeVisitor.php b/packages/simple-php-doc-parser/src/PhpDocNodeVisitor/ParentConnectingPhpDocNodeVisitor.php index 7ee5127de9..c391a51706 100644 --- a/packages/simple-php-doc-parser/src/PhpDocNodeVisitor/ParentConnectingPhpDocNodeVisitor.php +++ b/packages/simple-php-doc-parser/src/PhpDocNodeVisitor/ParentConnectingPhpDocNodeVisitor.php @@ -24,10 +24,7 @@ public function beforeTraverse(Node $node): void $this->stack = [$node]; } - /** - * @return int|Node|null - */ - public function enterNode(Node $node) + public function enterNode(Node $node): Node { if ($this->stack !== []) { $parentNode = $this->stack[count($this->stack) - 1]; From edc3dbfb34b1eaf1f9daea507660c2a194aacb40 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 10 Jul 2021 01:11:10 +0200 Subject: [PATCH 3/3] skip already added --- composer.json | 2 +- .../DoctrineAnnotationNestedBracketsFixer.php | 52 +++++++++++++------ .../DoctrineAnnotationElementAnalyzer.php | 15 +++--- .../DoctrineAnnotationNameResolver.php | 16 +++--- .../ClassReferencePhpDocNodeVisitor.php | 5 +- rector.php | 5 ++ 6 files changed, 59 insertions(+), 36 deletions(-) diff --git a/composer.json b/composer.json index 3ec3d0a8ea..fcd54589a8 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,7 @@ "ondram/ci-detector": "^4.1", "phpunit/phpunit": "^9.5", "psr/log": "^1.1", - "rector/rector": "dev-main#e219401", + "rector/rector": "dev-main#54dcc7b", "symfony/doctrine-bridge": "^5.3", "symfony/framework-bundle": "^5.3", "symfony/security-bundle": "^5.3", diff --git a/packages/coding-standard/src/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer.php b/packages/coding-standard/src/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer.php index ee7038f049..6d08574474 100644 --- a/packages/coding-standard/src/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer.php +++ b/packages/coding-standard/src/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer.php @@ -5,12 +5,14 @@ namespace Symplify\CodingStandard\Fixer\Annotation; use Doctrine\Common\Annotations\DocLexer; -use PhpCsFixer\Doctrine\Annotation\Token; +use PhpCsFixer\Doctrine\Annotation\Token as DoctrineAnnotationToken; use PhpCsFixer\Doctrine\Annotation\Tokens as DoctrineAnnotationTokens; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +use SplFileInfo; use Symplify\CodingStandard\Fixer\AbstractSymplifyFixer; use Symplify\CodingStandard\TokenAnalyzer\DoctrineAnnotationElementAnalyzer; use Symplify\CodingStandard\TokenAnalyzer\DoctrineAnnotationNameResolver; @@ -39,7 +41,7 @@ final class DoctrineAnnotationNestedBracketsFixer extends AbstractSymplifyFixer public function __construct( private DoctrineAnnotationElementAnalyzer $doctrineAnnotationElementAnalyzer, - private DoctrineAnnotationNameResolver $annotationNameResolver, + private DoctrineAnnotationNameResolver $doctrineAnnotationNameResolver, private NamespaceUsesAnalyzer $namespaceUsesAnalyzer ) { } @@ -90,18 +92,24 @@ public function configure(array $configuration): void $this->annotationClasses = $annotationsClasses; } + /** + * @param Tokens $tokens + */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - public function fix(\SplFileInfo $fileInfo, Tokens $tokens): void + /** + * @param Tokens $tokens + */ + public function fix(SplFileInfo $fileInfo, Tokens $tokens): void { $useDeclarations = $this->namespaceUsesAnalyzer->getDeclarationsFromTokens($tokens); // fetch indexes one time, this is safe as we never add or remove a token during fixing - /** @var \PhpCsFixer\Tokenizer\Token[] $docCommentTokens */ + /** @var Token[] $docCommentTokens */ $docCommentTokens = $tokens->findGivenKind(T_DOC_COMMENT); foreach ($docCommentTokens as $index => $docCommentToken) { if (! $this->doctrineAnnotationElementAnalyzer->detect($tokens, $index)) { @@ -111,22 +119,26 @@ public function fix(\SplFileInfo $fileInfo, Tokens $tokens): void $doctrineAnnotationTokens = DoctrineAnnotationTokens::createFromDocComment($docCommentToken, []); $this->fixAnnotations($doctrineAnnotationTokens, $useDeclarations); - $tokens[$index] = new \PhpCsFixer\Tokenizer\Token([T_DOC_COMMENT, $doctrineAnnotationTokens->getCode()]); + $tokens[$index] = new Token([T_DOC_COMMENT, $doctrineAnnotationTokens->getCode()]); } } /** - * @param DoctrineAnnotationTokens $tokens + * @param DoctrineAnnotationTokens $doctrineAnnotationTokens */ - private function fixAnnotations(DoctrineAnnotationTokens $tokens, $useDeclarations): void + private function fixAnnotations(DoctrineAnnotationTokens $doctrineAnnotationTokens, $useDeclarations): void { - foreach ($tokens as $index => $token) { - $isAtToken = $tokens[$index]->isType(DocLexer::T_AT); + foreach ($doctrineAnnotationTokens as $index => $token) { + $isAtToken = $doctrineAnnotationTokens[$index]->isType(DocLexer::T_AT); if (! $isAtToken) { continue; } - $annotationName = $this->annotationNameResolver->resolveName($tokens, $index, $useDeclarations); + $annotationName = $this->doctrineAnnotationNameResolver->resolveName( + $doctrineAnnotationTokens, + $index, + $useDeclarations + ); if ($annotationName === null) { continue; } @@ -135,31 +147,37 @@ private function fixAnnotations(DoctrineAnnotationTokens $tokens, $useDeclaratio continue; } - $closingBraceIndex = $tokens->getAnnotationEnd($index); + $closingBraceIndex = $doctrineAnnotationTokens->getAnnotationEnd($index); if ($closingBraceIndex === null) { continue; } - $braceIndex = $tokens->getNextMeaningfulToken($index + 1); + $braceIndex = $doctrineAnnotationTokens->getNextMeaningfulToken($index + 1); if ($braceIndex === null) { continue; } - /** @var Token $braceToken */ - $braceToken = $tokens[$braceIndex]; + /** @var DoctrineAnnotationToken $braceToken */ + $braceToken = $doctrineAnnotationTokens[$braceIndex]; if (! $this->doctrineAnnotationElementAnalyzer->isOpeningBracketFollowedByAnnotation( $braceToken, - $tokens, + $doctrineAnnotationTokens, $braceIndex )) { continue; } // add closing brace - $tokens->insertAt($closingBraceIndex, new Token(DocLexer::T_OPEN_CURLY_BRACES, '}')); + $doctrineAnnotationTokens->insertAt( + $closingBraceIndex, + new DoctrineAnnotationToken(DocLexer::T_OPEN_CURLY_BRACES, '}') + ); // add opening brace - $tokens->insertAt($braceIndex + 1, new Token(DocLexer::T_OPEN_CURLY_BRACES, '{')); + $doctrineAnnotationTokens->insertAt( + $braceIndex + 1, + new DoctrineAnnotationToken(DocLexer::T_OPEN_CURLY_BRACES, '{') + ); } } } diff --git a/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationElementAnalyzer.php b/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationElementAnalyzer.php index 6d51b46ed2..754d498494 100644 --- a/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationElementAnalyzer.php +++ b/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationElementAnalyzer.php @@ -17,10 +17,13 @@ */ final class DoctrineAnnotationElementAnalyzer { + /** + * @param Tokens<\PhpCsFixer\Tokenizer\Token> $tokens + */ public function detect(Tokens $tokens, int $index): bool { - $analyzer = new TokensAnalyzer($tokens); - $classyElements = $analyzer->getClassyElements(); + $tokensAnalyzer = new TokensAnalyzer($tokens); + $classyElements = $tokensAnalyzer->getClassyElements(); do { $index = $tokens->getNextMeaningfulToken($index); @@ -49,11 +52,11 @@ public function detect(Tokens $tokens, int $index): bool /** * We look for "(@SomeAnnotation" * - * @param DoctrineAnnotationTokens $tokens + * @param DoctrineAnnotationTokens $doctrineAnnotationTokens */ public function isOpeningBracketFollowedByAnnotation( Token $token, - DoctrineAnnotationTokens $tokens, + DoctrineAnnotationTokens $doctrineAnnotationTokens, int $braceIndex ): bool { // should be "(" @@ -62,13 +65,13 @@ public function isOpeningBracketFollowedByAnnotation( return false; } - $nextTokenIndex = $tokens->getNextMeaningfulToken($braceIndex + 1); + $nextTokenIndex = $doctrineAnnotationTokens->getNextMeaningfulToken($braceIndex); if ($nextTokenIndex === null) { return false; } /** @var Token $nextToken */ - $nextToken = $tokens[$nextTokenIndex]; + $nextToken = $doctrineAnnotationTokens[$nextTokenIndex]; // next token must be nested annotation, we don't care otherwise return $nextToken->isType(DocLexer::T_AT); diff --git a/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationNameResolver.php b/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationNameResolver.php index d3adec27d3..cfef4a9edd 100644 --- a/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationNameResolver.php +++ b/packages/coding-standard/src/TokenAnalyzer/DoctrineAnnotationNameResolver.php @@ -30,16 +30,16 @@ public function resolveName(Tokens $tokens, int $index, array $namespaceUseAnaly $annotationShortName .= $currentToken->getContent(); } - if ($annotationShortName) { - foreach ($namespaceUseAnalyses as $namespaceUseAnalysis) { - if ($namespaceUseAnalysis->getShortName() === $annotationShortName) { - return $namespaceUseAnalysis->getFullName(); - } - } + if ($annotationShortName === '') { + return null; + } - return $annotationShortName; + foreach ($namespaceUseAnalyses as $namespaceUseAnalysis) { + if ($namespaceUseAnalysis->getShortName() === $annotationShortName) { + return $namespaceUseAnalysis->getFullName(); + } } - return null; + return $annotationShortName; } } diff --git a/packages/phpstan-rules/src/PhpDoc/ClassReferencePhpDocNodeVisitor.php b/packages/phpstan-rules/src/PhpDoc/ClassReferencePhpDocNodeVisitor.php index 86f0f4ac3a..5fec81c748 100644 --- a/packages/phpstan-rules/src/PhpDoc/ClassReferencePhpDocNodeVisitor.php +++ b/packages/phpstan-rules/src/PhpDoc/ClassReferencePhpDocNodeVisitor.php @@ -46,10 +46,7 @@ public function configureClassName(string $className): void $this->className = $className; } - /** - * @return int|Node|null - */ - public function enterNode(Node $node) + public function enterNode(Node $node): Node { if ($node instanceof PhpDocTagNode) { $this->processPhpDocTagNode($node); diff --git a/rector.php b/rector.php index 74e1fe4aa5..57ba63e88b 100644 --- a/rector.php +++ b/rector.php @@ -98,6 +98,11 @@ // many false postivies RenameForeachValueVariableToMatchExprVariableRector::class, + // buggy on array access object + \Rector\CodeQuality\Rector\Foreach_\UnusedForeachValueToArrayKeysRector::class => [ + __DIR__ . '/packages/coding-standard/src/Fixer/Annotation/DoctrineAnnotationNestedBracketsFixer.php', + ], + // buggy with parent interface contract ParamTypeDeclarationRector::class => [__DIR__ . '/packages/skipper/src/SkipVoter/*SkipVoter.php'], UnSpreadOperatorRector::class => [__DIR__ . '/packages/git-wrapper'],