Skip to content

Commit

Permalink
Add DefineType and ImportType attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-granados committed Mar 5, 2024
1 parent 19cdb95 commit a84ba9d
Show file tree
Hide file tree
Showing 44 changed files with 457 additions and 239 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,10 @@ use PhpStaticAnalysis\RectorRule\Set\PhpStaticAnalysisSetList;
return RectorConfig::configure()
->withSets([
PhpStaticAnalysisSetList::ANNOTATIONS_TO_ATTRIBUTES
]);
])
->withImportNames();
```
(We recommend that you add the `withImportNames()` option so that attributes are not added with their fully qualified name)

If you only want to replace some annotations and leave the others as they are, use the rule configured with the annotations that you need. For example, if you only want to replace the `@return` and `@param` annotations, use this configuration:

Expand Down Expand Up @@ -129,8 +131,10 @@ These are the available attributes and their corresponding PHPDoc annotations:

| Attribute | PHPDoc Annotations |
|-------------------------------------------------------------------------------------------------------------------|--------------------|
| [DefineType](https://github.com/php-static-analysis/attributes/blob/main/doc/DefineType.md) | `@type` |
| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` |
| [Immmutable](https://github.com/php-static-analysis/attributes/blob/main/doc/Immmutable.md) | `@immmutable` |
| [ImportType](https://github.com/php-static-analysis/attributes/blob/main/doc/ImportType.md) | `@import-type` |
| [Impure](https://github.com/php-static-analysis/attributes/blob/main/doc/Impure.md) | `@impure` |
| [Internal](https://github.com/php-static-analysis/attributes/blob/main/doc/Internal.md) | `@internal` |
| [IsReadOnly](https://github.com/php-static-analysis/attributes/blob/main/doc/IsReadOnly.md) | `@readonly` |
Expand Down
17 changes: 2 additions & 15 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@
"prefer-stable": true,
"require": {
"php": ">=8.0",
"cweagans/composer-patches": "^1.7",
"php-static-analysis/attributes": "^0.1.17 || dev-main",
"php-static-analysis/attributes": "^0.2.2 || dev-main",
"rector/rector": "^0.19 || ^1.0"
},
"require-dev": {
Expand All @@ -38,7 +37,6 @@
"phpstan/phpstan": "^1.8",
"phpunit/phpunit": "^9.0",
"symplify/easy-coding-standard": "^12.1",
"symplify/vendor-patches": "^11.3",
"vimeo/psalm": "^5",
"webmozart/assert": "^1.11"
},
Expand All @@ -61,19 +59,8 @@
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true,
"cweagans/composer-patches": true
"phpstan/extension-installer": true
},
"sort-packages": true
},
"extra": {
"patches": {
"rector/rector": [
"patches/rector-rector-src-phpparser-printer-betterstandardprinter-php.patch",
"patches/rector-rector-src-nodetyperesolver-node-attributekey-php.patch"
]
},
"composer-exit-on-patch-failure": true,
"enable-patching": true
}
}
13 changes: 9 additions & 4 deletions config/sets/php-static-analysis-annotations-to-attributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

declare(strict_types=1);

use PhpStaticAnalysis\Attributes\DefineType;
use PhpStaticAnalysis\Attributes\Deprecated;
use PhpStaticAnalysis\Attributes\Immutable;
use PhpStaticAnalysis\Attributes\ImportType;
use PhpStaticAnalysis\Attributes\Impure;
use PhpStaticAnalysis\Attributes\Internal;
use PhpStaticAnalysis\Attributes\Method;
Expand Down Expand Up @@ -31,13 +33,14 @@
use PhpStaticAnalysis\Attributes\Type;
use PhpStaticAnalysis\RectorRule\AnnotationsToAttributesRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->ruleWithConfiguration(
return RectorConfig::configure()
->withConfiguredRule(
AnnotationsToAttributesRector::class,
[
new AnnotationToAttribute('deprecated', Deprecated::class),
new AnnotationToAttribute('extends', TemplateExtends::class),
new AnnotationToAttribute('immutable', Immutable::class),
new AnnotationToAttribute('import_type', ImportType::class),
new AnnotationToAttribute('impure', Impure::class),
new AnnotationToAttribute('implements', TemplateImplements::class),
new AnnotationToAttribute('internal', Internal::class),
Expand All @@ -62,12 +65,14 @@
new AnnotationToAttribute('template_use', TemplateUse::class),
new AnnotationToAttribute('this_out', SelfOut::class),
new AnnotationToAttribute('throws', Throws::class),
new AnnotationToAttribute('type', DefineType::class),
new AnnotationToAttribute('use', TemplateUse::class),
new AnnotationToAttribute('var', Type::class),
'addParamAttributeOnParameters' => false,
'useTypeAttributeForReturnAnnotation' => false,
'usePropertyAttributeForVarAnnotation' => false,
'excludeAnnotations' => [],
'useTypeAttributeForTypeClassAnnotation' => false,
]
);
};
)
->withImportNames();
82 changes: 62 additions & 20 deletions src/AnnotationsToAttributesRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@
namespace PhpStaticAnalysis\RectorRule;

use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\Stmt\TraitUse;
use PHPStan\PhpDocParser\Ast\PhpDoc\DeprecatedTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
Expand All @@ -27,6 +37,8 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\SelfOutTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
Expand Down Expand Up @@ -61,6 +73,8 @@ final class AnnotationsToAttributesRector extends AbstractRector implements Conf

private bool $usePropertyAttributeForVarAnnotation = false;

private bool $useTypeAttributeForTypeClassAnnotation = false;

public function __construct(
private PhpDocTagRemover $phpDocTagRemover,
private AttributeGroupNamedArgumentManipulator $attributeGroupNamedArgumentManipulator,
Expand Down Expand Up @@ -138,6 +152,8 @@ public function configure(array $configuration): void
$this->useTypeAttributeForReturnAnnotation = $value;
} elseif (is_bool($value) && $key == 'usePropertyAttributeForVarAnnotation') {
$this->usePropertyAttributeForVarAnnotation = $value;
} elseif (is_bool($value) && $key == 'useTypeAttributeForTypeClassAnnotation') {
$this->useTypeAttributeForTypeClassAnnotation = $value;
} elseif (is_array($value) && $key == 'excludeAnnotations') {
$excludedAnnotations = $value;
}
Expand Down Expand Up @@ -165,13 +181,13 @@ public function configure(array $configuration): void
public function getNodeTypes(): array
{
return [
Stmt\Class_::class,
Stmt\ClassConst::class,
Stmt\ClassMethod::class,
Stmt\Function_::class,
Stmt\Interface_::class,
Class_::class,
ClassConst::class,
ClassMethod::class,
Function_::class,
Interface_::class,
Stmt\Property::class,
Stmt\Trait_::class
Trait_::class
];
}

Expand All @@ -195,7 +211,7 @@ public function refactor(Node $node): ?Node
}

if ($this->addParamAttributeOnParameters &&
($node instanceof Stmt\ClassMethod || $node instanceof Stmt\Function_)) {
($node instanceof ClassMethod || $node instanceof Function_)) {
foreach ($attributeGroups as $attrKey => $attributeGroup) {
foreach ($attributeGroup->attrs as $key => $attribute) {
$attributeName = (string)$attribute->name;
Expand Down Expand Up @@ -223,9 +239,9 @@ public function refactor(Node $node): ?Node
}
}

if ($node instanceof Stmt\Class_) {
if ($node instanceof Class_) {
foreach ($node->stmts as $stmt) {
if ($stmt instanceof Stmt\TraitUse) {
if ($stmt instanceof TraitUse) {
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($stmt);
if ($phpDocInfo instanceof PhpDocInfo) {
$useAttributeGroups = $this->processAnnotations($phpDocInfo);
Expand Down Expand Up @@ -273,24 +289,24 @@ private function processAnnotations(PhpDocInfo $phpDocInfo): array
$methodSignature = substr($methodSignature, 0, -(strlen($attributeComment) + 1));
}
$args = [
new Node\Arg(new Scalar\String_($methodSignature))
new Arg(new String_($methodSignature))
];
break;
case $tagValueNode instanceof ParamOutTagValueNode:
case $tagValueNode instanceof ParamTagValueNode:
$args = [
new Node\Arg(
value: new Scalar\String_((string)($tagValueNode->type)),
name: new Node\Identifier(substr($tagValueNode->parameterName, 1))
new Arg(
value: new String_((string)($tagValueNode->type)),
name: new Identifier(substr($tagValueNode->parameterName, 1))
)
];
$attributeComment = $tagValueNode->description;
break;
case $tagValueNode instanceof PropertyTagValueNode:
$args = [
new Node\Arg(
value: new Scalar\String_((string)($tagValueNode->type)),
name: new Node\Identifier(substr($tagValueNode->propertyName, 1))
new Arg(
value: new String_((string)($tagValueNode->type)),
name: new Identifier(substr($tagValueNode->propertyName, 1))
)
];
$attributeComment = $tagValueNode->description;
Expand All @@ -306,16 +322,16 @@ private function processAnnotations(PhpDocInfo $phpDocInfo): array
case $tagValueNode instanceof UsesTagValueNode:
case $tagValueNode instanceof VarTagValueNode:
$args = [
new Node\Arg(new Scalar\String_((string)($tagValueNode->type)))
new Arg(new String_((string)($tagValueNode->type)))
];
$attributeComment = $tagValueNode->description;
break;
case $tagValueNode instanceof TemplateTagValueNode:
$args = [
new Node\Arg(new Scalar\String_($tagValueNode->name))
new Arg(new String_($tagValueNode->name))
];
if ($tagValueNode->bound instanceof IdentifierTypeNode) {
$args[] = new Node\Arg(new Scalar\String_($tagValueNode->bound->name));
$args[] = new Arg(new String_($tagValueNode->bound->name));
}
$attributeComment = $tagValueNode->description;
break;
Expand All @@ -327,13 +343,36 @@ private function processAnnotations(PhpDocInfo $phpDocInfo): array
$parts = explode(' ', $remainingText);
$namespace = array_shift($parts);
if ($namespace) {
$args[] = new Node\Arg(new Scalar\String_($namespace));
$args[] = new Arg(new String_($namespace));
$attributeComment = implode(' ', $parts);
}
} else {
$attributeComment = (string)$tagValueNode;
}
break;
case $tagValueNode instanceof TypeAliasTagValueNode:
if ($this->useTypeAttributeForTypeClassAnnotation) {
$alias = (string)($tagValueNode);
$args = [
new Arg(new String_($alias))
];
} else {
$args = [
new Arg(
value: new String_((string)($tagValueNode->type)),
name: new Identifier($tagValueNode->alias)
)
];
}
break;
case $tagValueNode instanceof TypeAliasImportTagValueNode:
$args = [
new Arg(
value: new String_((string)($tagValueNode->importedFrom)),
name: new Identifier($tagValueNode->importedAlias)
)
];
break;
default:
continue 2;
}
Expand Down Expand Up @@ -376,6 +415,9 @@ private function matchAnnotationToAttribute(
} elseif (str_starts_with($tagName, 'phpstan-')) {
$tagName = substr($tagName, 8);
}
if ($this->useTypeAttributeForTypeClassAnnotation && $tagName == 'type') {
$tagName = 'var';
}
if (isset($this->annotationsToAttributes[$tagName])) {
return $this->annotationsToAttributes[$tagName];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace test\PhpStaticAnalysis\RectorRule;

use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class AnnotationsToAttributesWithTypeAttributeForTypeClassRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
yield [__DIR__ . '/SpecialFixture/TypeAttributeTestForTypeClassAnnotation.php.inc'];
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured-rule-with-type-attribute-for-type-class.php';
}
}
35 changes: 35 additions & 0 deletions tests/Fixture/DefineTypeAttributeTest.php.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace test\PhpStaticAnalysis\RectorRule\Fixture;

use PhpStaticAnalysis\Attributes\DefineType;

/**
* @codeCoverageIgnore
* @phpstan-type StringArray string[]
* @psalm-type IntArray int[]
*/
#[DefineType(FloatArray: 'float[]')]
class DefineTypeAttributeTest
{
}

?>
-----
<?php

namespace test\PhpStaticAnalysis\RectorRule\Fixture;

use PhpStaticAnalysis\Attributes\DefineType;

/**
* @codeCoverageIgnore
*/
#[DefineType(FloatArray: 'float[]')]
#[DefineType(StringArray: 'string[]')]
#[DefineType(IntArray: 'int[]')]
class DefineTypeAttributeTest
{
}

?>
Loading

0 comments on commit a84ba9d

Please sign in to comment.