diff --git a/README.md b/README.md index e715077..3f79c4e 100644 --- a/README.md +++ b/README.md @@ -137,3 +137,22 @@ return RectorConfig::configure() ); ``` +### Attribute to use for the return type of methods and functions + +By default `Returns` attributes are added to define the return type of methods/functions. It is possible to use the `Type` attribute instead. To activate this option, add this code to your configuration: + +```php +use PhpStaticAnalysis\RectorRule\AnnotationsToAttributesRector; +use Rector\Config\RectorConfig; +... + +return RectorConfig::configure() + ... + ->withConfiguredRule( + AnnotationsToAttributesRector::class, + [ + 'useTypeAttributeForReturnAnnotation' => true, + ] + ); +``` + diff --git a/composer.json b/composer.json index d55e060..7e3a7d7 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "prefer-stable": true, "require": { "php": ">=8.0", - "php-static-analysis/attributes": "^0.1.1 || dev-main", + "php-static-analysis/attributes": "^0.1.2 || dev-main", "rector/rector": "^0.19 || ^1.0" }, "require-dev": { diff --git a/config/sets/php-static-analysis-annotations-to-attributes.php b/config/sets/php-static-analysis-annotations-to-attributes.php index 76c3bad..c4694ef 100644 --- a/config/sets/php-static-analysis-annotations-to-attributes.php +++ b/config/sets/php-static-analysis-annotations-to-attributes.php @@ -21,6 +21,7 @@ new AnnotationToAttribute('template', Template::class), new AnnotationToAttribute('var', Type::class), 'addParamAttributeOnParameters' => false, + 'useTypeAttributeForReturnAnnotation' => false, ] ); }; diff --git a/src/AnnotationsToAttributesRector.php b/src/AnnotationsToAttributesRector.php index 306c87f..825213c 100644 --- a/src/AnnotationsToAttributesRector.php +++ b/src/AnnotationsToAttributesRector.php @@ -42,6 +42,8 @@ final class AnnotationsToAttributesRector extends AbstractRector implements Conf private bool $addParamAttributeOnParameters = false; + private bool $useTypeAttributeForReturnAnnotation = false; + public function __construct( private PhpDocTagRemover $phpDocTagRemover, private AttributeGroupNamedArgumentManipulator $attributeGroupNamedArgumentManipulator, @@ -110,9 +112,17 @@ public function configure(array $configuration): void { foreach ($configuration as $key => $value) { if ($value instanceof AnnotationToAttribute) { - $this->annotationsToAttributes[] = $value; + $this->annotationsToAttributes[$value->getTag()] = $value; } elseif ($key == 'addParamAttributeOnParameters') { $this->addParamAttributeOnParameters = $value; + } elseif ($key == 'useTypeAttributeForReturnAnnotation') { + $this->useTypeAttributeForReturnAnnotation = $value; + } + } + if ($this->useTypeAttributeForReturnAnnotation) { + if (isset($this->annotationsToAttributes['return'])) { + $this->annotationsToAttributes['return'] = + new AnnotationToAttribute('return', Type::class); } } } @@ -258,9 +268,15 @@ public function provideMinPhpVersion(): int private function matchAnnotationToAttribute( string $tagName ): AnnotationToAttribute|null { - foreach ($this->annotationsToAttributes as $annotationToAttribute) { - if ($tagName == '@' . $annotationToAttribute->getTag()) { - return $annotationToAttribute; + if (str_starts_with($tagName, '@')) { + $tagName = substr($tagName, 1); + if (str_starts_with($tagName, 'psalm-')) { + $tagName = substr($tagName, 6); + } elseif (str_starts_with($tagName, 'phpstan-')) { + $tagName = substr($tagName, 8); + } + if (isset($this->annotationsToAttributes[$tagName])) { + return $this->annotationsToAttributes[$tagName]; } } diff --git a/tests/AnnotationsToAttributesWithTypeAttributeForReturnRectorTest.php b/tests/AnnotationsToAttributesWithTypeAttributeForReturnRectorTest.php new file mode 100644 index 0000000..7673ee6 --- /dev/null +++ b/tests/AnnotationsToAttributesWithTypeAttributeForReturnRectorTest.php @@ -0,0 +1,29 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + yield [__DIR__ . '/SpecialFixture/TypeAttributeTestForReturnAnnotation.php.inc']; + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured-rule-with-type-attribute-for-return.php'; + } +} diff --git a/tests/Fixture/IsReadOnlyAttributeTest.php.inc b/tests/Fixture/IsReadOnlyAttributeTest.php.inc index 5a4a4be..9e7b35c 100644 --- a/tests/Fixture/IsReadOnlyAttributeTest.php.inc +++ b/tests/Fixture/IsReadOnlyAttributeTest.php.inc @@ -22,6 +22,16 @@ class IsReadOnlyAttributeTest */ #[Type('string')] public $otherName; + + /** + * @psalm-readonly + */ + public $psalmName; + + /** + * @phpstan-readonly + */ + public $phpstanName; } ?> @@ -46,6 +56,12 @@ class IsReadOnlyAttributeTest #[Type('string')] #[\PhpStaticAnalysis\Attributes\IsReadOnly] public $otherName; + + #[\PhpStaticAnalysis\Attributes\IsReadOnly] + public $psalmName; + + #[\PhpStaticAnalysis\Attributes\IsReadOnly] + public $phpstanName; } ?> diff --git a/tests/Fixture/ParamAttributeTest.php.inc b/tests/Fixture/ParamAttributeTest.php.inc index 6dbb74b..3fadcc7 100644 --- a/tests/Fixture/ParamAttributeTest.php.inc +++ b/tests/Fixture/ParamAttributeTest.php.inc @@ -50,6 +50,22 @@ class ParamAttributeTest { return $name1 . $name2; } + + /** + * @psalm-param string $name + */ + public function getPsalmName($name) + { + return $name; + } + + /** + * @phpstan-param string $name + */ + public function getPHPStanName($name) + { + return $name; + } } /** @@ -106,6 +122,18 @@ class ParamAttributeTest { return $name1 . $name2; } + + #[\PhpStaticAnalysis\Attributes\Param(name: 'string')] + public function getPsalmName($name) + { + return $name; + } + + #[\PhpStaticAnalysis\Attributes\Param(name: 'string')] + public function getPHPStanName($name) + { + return $name; + } } #[\PhpStaticAnalysis\Attributes\Param(name: 'string')] diff --git a/tests/Fixture/ReturnsAttributeTest.php.inc b/tests/Fixture/ReturnsAttributeTest.php.inc index 60e1ebc..eb95340 100644 --- a/tests/Fixture/ReturnsAttributeTest.php.inc +++ b/tests/Fixture/ReturnsAttributeTest.php.inc @@ -31,6 +31,22 @@ class ReturnsAttributeTest { return "Hello " . $name; } + + /** + * @psalm-return string + */ + public function getPsalmName() + { + return 'John'; + } + + /** + * @phpstan-return string + */ + public function getPHPStanName() + { + return 'John'; + } } /** @@ -72,6 +88,18 @@ class ReturnsAttributeTest { return "Hello " . $name; } + + #[\PhpStaticAnalysis\Attributes\Returns('string')] + public function getPsalmName() + { + return 'John'; + } + + #[\PhpStaticAnalysis\Attributes\Returns('string')] + public function getPHPStanName() + { + return 'John'; + } } #[\PhpStaticAnalysis\Attributes\Returns('string')] diff --git a/tests/Fixture/TemplateAttributeTest.php.inc b/tests/Fixture/TemplateAttributeTest.php.inc index e92aa39..6a5e189 100644 --- a/tests/Fixture/TemplateAttributeTest.php.inc +++ b/tests/Fixture/TemplateAttributeTest.php.inc @@ -42,6 +42,22 @@ class TemplateAttributeTest { return "Hello " . $name; } + + /** + * @psalm-template T + */ + public function getPsalmName() + { + return 'John'; + } + + /** + * @phpstan-template T + */ + public function getPHPStanName() + { + return 'John'; + } } /** @@ -90,6 +106,18 @@ class TemplateAttributeTest { return "Hello " . $name; } + + #[\PhpStaticAnalysis\Attributes\Template('T')] + public function getPsalmName() + { + return 'John'; + } + + #[\PhpStaticAnalysis\Attributes\Template('T')] + public function getPHPStanName() + { + return 'John'; + } } #[\PhpStaticAnalysis\Attributes\Template('T')] diff --git a/tests/Fixture/TypeAttributeTest.php.inc b/tests/Fixture/TypeAttributeTest.php.inc index aef1745..d7ebe97 100644 --- a/tests/Fixture/TypeAttributeTest.php.inc +++ b/tests/Fixture/TypeAttributeTest.php.inc @@ -22,6 +22,16 @@ class TypeAttributeTest */ #[IsReadOnly] public $otherName; + + /** + * @psalm-var string + */ + public $psalmName; + + /** + * @phpstan-var string + */ + public $phpstanName; } ?> @@ -46,6 +56,12 @@ class TypeAttributeTest #[IsReadOnly] #[\PhpStaticAnalysis\Attributes\Type('string')] public $otherName; + + #[\PhpStaticAnalysis\Attributes\Type('string')] + public $psalmName; + + #[\PhpStaticAnalysis\Attributes\Type('string')] + public $phpstanName; } ?> diff --git a/tests/SpecialFixture/TypeAttributeTestForReturnAnnotation.php.inc b/tests/SpecialFixture/TypeAttributeTestForReturnAnnotation.php.inc new file mode 100644 index 0000000..334c2a8 --- /dev/null +++ b/tests/SpecialFixture/TypeAttributeTestForReturnAnnotation.php.inc @@ -0,0 +1,111 @@ + +----- + diff --git a/tests/config/configured-rule-with-type-attribute-for-return.php b/tests/config/configured-rule-with-type-attribute-for-return.php new file mode 100644 index 0000000..65e026b --- /dev/null +++ b/tests/config/configured-rule-with-type-attribute-for-return.php @@ -0,0 +1,19 @@ +sets([ + PhpStaticAnalysisSetList::ANNOTATIONS_TO_ATTRIBUTES + ]); + $rectorConfig->ruleWithConfiguration( + AnnotationsToAttributesRector::class, + [ + 'useTypeAttributeForReturnAnnotation' => true, + ] + ); +};