diff --git a/src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php b/src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php index df31fe464a9..af0da1bc084 100644 --- a/src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php +++ b/src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php @@ -11,6 +11,8 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface; use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey; +use Rector\Configuration\Option; +use Rector\Configuration\Parameter\SimpleParameterProvider; use Rector\PhpDocParser\PhpDocParser\PhpDocNodeTraverser; use Rector\StaticTypeMapper\Naming\NameScopeFactory; @@ -46,7 +48,7 @@ public function decorate(PhpDocNode $phpDocNode, PhpNode $phpNode): void return null; } - if (! in_array($node->name, ['@uses', '@used-by', '@see'], true)) { + if (! in_array($node->name, $this->resolveTagNames(), true)) { return null; } @@ -67,6 +69,24 @@ public function decorate(PhpDocNode $phpDocNode, PhpNode $phpNode): void }); } + /** + * @return string[] + */ + private function resolveTagNames(): array + { + $defaultTags = ['@uses', '@used-by', '@see']; + + $additionalTags = SimpleParameterProvider::provideArrayParameter( + Option::PHPDOC_TAGS_WITH_CLASS_REFERENCE + ); + + if ($additionalTags === []) { + return $defaultTags; + } + + return array_merge($defaultTags, $additionalTags); + } + private function resolveFullyQualifiedClass(string $classValue, PhpNode $phpNode): string { $nameScope = $this->nameScopeFactory->createNameScopeFromNodeWithoutTemplateTypes($phpNode); diff --git a/src/Config/RectorConfig.php b/src/Config/RectorConfig.php index 39ea996da58..0c8b3e95ae9 100644 --- a/src/Config/RectorConfig.php +++ b/src/Config/RectorConfig.php @@ -133,6 +133,14 @@ public function removeUnusedImports(bool $removeUnusedImports = true): void SimpleParameterProvider::setParameter(Option::REMOVE_UNUSED_IMPORTS, $removeUnusedImports); } + /** + * @param string[] $tags + */ + public function phpDocTagsWithClassReference(array $tags): void + { + SimpleParameterProvider::addParameter(Option::PHPDOC_TAGS_WITH_CLASS_REFERENCE, $tags); + } + public function importNames(bool $importNames = true, bool $importDocBlockNames = true): void { SimpleParameterProvider::setParameter(Option::AUTO_IMPORT_NAMES, $importNames); diff --git a/src/Configuration/Option.php b/src/Configuration/Option.php index c815eeaa88e..f8314d6579a 100644 --- a/src/Configuration/Option.php +++ b/src/Configuration/Option.php @@ -260,4 +260,10 @@ final class Option * @internal to allow process file without extension if explicitly registered */ public const string FILES_WITHOUT_EXTENSION = 'files_without_extension'; + + /** + * @internal Use @see \Rector\Config\RectorConfig::phpDocTagsWithClassReference() method + * @var string + */ + public const string PHPDOC_TAGS_WITH_CLASS_REFERENCE = 'phpdoc_tags_with_class_reference'; } diff --git a/src/Configuration/RectorConfigBuilder.php b/src/Configuration/RectorConfigBuilder.php index 6a5474b1f9b..d9d24866083 100644 --- a/src/Configuration/RectorConfigBuilder.php +++ b/src/Configuration/RectorConfigBuilder.php @@ -107,6 +107,11 @@ final class RectorConfigBuilder private bool $removeUnusedImports = false; + /** + * @var string[] + */ + private array $phpDocTagsWithClassReference = []; + private bool $noDiffs = false; private ?string $memoryLimit = null; @@ -325,6 +330,10 @@ public function __invoke(RectorConfig $rectorConfig): void $rectorConfig->removeUnusedImports($this->removeUnusedImports); } + if ($this->phpDocTagsWithClassReference !== []) { + $rectorConfig->phpDocTagsWithClassReference($this->phpDocTagsWithClassReference); + } + if ($this->noDiffs) { $rectorConfig->noDiffs(); } @@ -933,6 +942,15 @@ public function withImportNames( return $this; } + /** + * @param string[] $tags + */ + public function withPhpDocTagsWithClassReference(array $tags): self + { + $this->phpDocTagsWithClassReference = $tags; + return $this; + } + public function withNoDiffs(): self { $this->noDiffs = true; diff --git a/src/Testing/PHPUnit/AbstractRectorTestCase.php b/src/Testing/PHPUnit/AbstractRectorTestCase.php index f0477faa32e..475aef04ddd 100644 --- a/src/Testing/PHPUnit/AbstractRectorTestCase.php +++ b/src/Testing/PHPUnit/AbstractRectorTestCase.php @@ -53,6 +53,7 @@ public static function tearDownAfterClass(): void SimpleParameterProvider::setParameter(Option::AUTO_IMPORT_NAMES, false); SimpleParameterProvider::setParameter(Option::AUTO_IMPORT_DOC_BLOCK_NAMES, false); SimpleParameterProvider::setParameter(Option::REMOVE_UNUSED_IMPORTS, false); + SimpleParameterProvider::setParameter(Option::PHPDOC_TAGS_WITH_CLASS_REFERENCE, []); SimpleParameterProvider::setParameter(Option::IMPORT_SHORT_CLASSES, true); SimpleParameterProvider::setParameter(Option::INDENT_CHAR, ' '); diff --git a/tests/Issues/CustomPhpDocTagClassReference/CustomPhpDocTagClassReferenceTest.php b/tests/Issues/CustomPhpDocTagClassReference/CustomPhpDocTagClassReferenceTest.php new file mode 100644 index 00000000000..4c32f332d2b --- /dev/null +++ b/tests/Issues/CustomPhpDocTagClassReference/CustomPhpDocTagClassReferenceTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Issues/CustomPhpDocTagClassReference/Fixture/skip_custom_tag_with_class_reference.php.inc b/tests/Issues/CustomPhpDocTagClassReference/Fixture/skip_custom_tag_with_class_reference.php.inc new file mode 100644 index 00000000000..3f320f1f055 --- /dev/null +++ b/tests/Issues/CustomPhpDocTagClassReference/Fixture/skip_custom_tag_with_class_reference.php.inc @@ -0,0 +1,12 @@ +removeUnusedImports(); + $rectorConfig->phpDocTagsWithClassReference(['@throwable-from']); +};