Skip to content

Commit a84ba9d

Browse files
Add DefineType and ImportType attributes
1 parent 19cdb95 commit a84ba9d

File tree

44 files changed

+457
-239
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+457
-239
lines changed

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,10 @@ use PhpStaticAnalysis\RectorRule\Set\PhpStaticAnalysisSetList;
8686
return RectorConfig::configure()
8787
->withSets([
8888
PhpStaticAnalysisSetList::ANNOTATIONS_TO_ATTRIBUTES
89-
]);
89+
])
90+
->withImportNames();
9091
```
92+
(We recommend that you add the `withImportNames()` option so that attributes are not added with their fully qualified name)
9193

9294
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:
9395

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

130132
| Attribute | PHPDoc Annotations |
131133
|-------------------------------------------------------------------------------------------------------------------|--------------------|
134+
| [DefineType](https://github.com/php-static-analysis/attributes/blob/main/doc/DefineType.md) | `@type` |
132135
| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` |
133136
| [Immmutable](https://github.com/php-static-analysis/attributes/blob/main/doc/Immmutable.md) | `@immmutable` |
137+
| [ImportType](https://github.com/php-static-analysis/attributes/blob/main/doc/ImportType.md) | `@import-type` |
134138
| [Impure](https://github.com/php-static-analysis/attributes/blob/main/doc/Impure.md) | `@impure` |
135139
| [Internal](https://github.com/php-static-analysis/attributes/blob/main/doc/Internal.md) | `@internal` |
136140
| [IsReadOnly](https://github.com/php-static-analysis/attributes/blob/main/doc/IsReadOnly.md) | `@readonly` |

composer.json

+2-15
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727
"prefer-stable": true,
2828
"require": {
2929
"php": ">=8.0",
30-
"cweagans/composer-patches": "^1.7",
31-
"php-static-analysis/attributes": "^0.1.17 || dev-main",
30+
"php-static-analysis/attributes": "^0.2.2 || dev-main",
3231
"rector/rector": "^0.19 || ^1.0"
3332
},
3433
"require-dev": {
@@ -38,7 +37,6 @@
3837
"phpstan/phpstan": "^1.8",
3938
"phpunit/phpunit": "^9.0",
4039
"symplify/easy-coding-standard": "^12.1",
41-
"symplify/vendor-patches": "^11.3",
4240
"vimeo/psalm": "^5",
4341
"webmozart/assert": "^1.11"
4442
},
@@ -61,19 +59,8 @@
6159
},
6260
"config": {
6361
"allow-plugins": {
64-
"phpstan/extension-installer": true,
65-
"cweagans/composer-patches": true
62+
"phpstan/extension-installer": true
6663
},
6764
"sort-packages": true
68-
},
69-
"extra": {
70-
"patches": {
71-
"rector/rector": [
72-
"patches/rector-rector-src-phpparser-printer-betterstandardprinter-php.patch",
73-
"patches/rector-rector-src-nodetyperesolver-node-attributekey-php.patch"
74-
]
75-
},
76-
"composer-exit-on-patch-failure": true,
77-
"enable-patching": true
7865
}
7966
}

config/sets/php-static-analysis-annotations-to-attributes.php

+9-4
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
declare(strict_types=1);
44

5+
use PhpStaticAnalysis\Attributes\DefineType;
56
use PhpStaticAnalysis\Attributes\Deprecated;
67
use PhpStaticAnalysis\Attributes\Immutable;
8+
use PhpStaticAnalysis\Attributes\ImportType;
79
use PhpStaticAnalysis\Attributes\Impure;
810
use PhpStaticAnalysis\Attributes\Internal;
911
use PhpStaticAnalysis\Attributes\Method;
@@ -31,13 +33,14 @@
3133
use PhpStaticAnalysis\Attributes\Type;
3234
use PhpStaticAnalysis\RectorRule\AnnotationsToAttributesRector;
3335

34-
return static function (RectorConfig $rectorConfig): void {
35-
$rectorConfig->ruleWithConfiguration(
36+
return RectorConfig::configure()
37+
->withConfiguredRule(
3638
AnnotationsToAttributesRector::class,
3739
[
3840
new AnnotationToAttribute('deprecated', Deprecated::class),
3941
new AnnotationToAttribute('extends', TemplateExtends::class),
4042
new AnnotationToAttribute('immutable', Immutable::class),
43+
new AnnotationToAttribute('import_type', ImportType::class),
4144
new AnnotationToAttribute('impure', Impure::class),
4245
new AnnotationToAttribute('implements', TemplateImplements::class),
4346
new AnnotationToAttribute('internal', Internal::class),
@@ -62,12 +65,14 @@
6265
new AnnotationToAttribute('template_use', TemplateUse::class),
6366
new AnnotationToAttribute('this_out', SelfOut::class),
6467
new AnnotationToAttribute('throws', Throws::class),
68+
new AnnotationToAttribute('type', DefineType::class),
6569
new AnnotationToAttribute('use', TemplateUse::class),
6670
new AnnotationToAttribute('var', Type::class),
6771
'addParamAttributeOnParameters' => false,
6872
'useTypeAttributeForReturnAnnotation' => false,
6973
'usePropertyAttributeForVarAnnotation' => false,
7074
'excludeAnnotations' => [],
75+
'useTypeAttributeForTypeClassAnnotation' => false,
7176
]
72-
);
73-
};
77+
)
78+
->withImportNames();

src/AnnotationsToAttributesRector.php

+62-20
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@
55
namespace PhpStaticAnalysis\RectorRule;
66

77
use PhpParser\Node;
8+
use PhpParser\Node\Arg;
89
use PhpParser\Node\Attribute;
910
use PhpParser\Node\AttributeGroup;
1011
use PhpParser\Node\Expr\Variable;
12+
use PhpParser\Node\Identifier;
1113
use PhpParser\Node\Name\FullyQualified;
1214
use PhpParser\Node\Scalar;
15+
use PhpParser\Node\Scalar\String_;
1316
use PhpParser\Node\Stmt;
17+
use PhpParser\Node\Stmt\Class_;
18+
use PhpParser\Node\Stmt\ClassConst;
19+
use PhpParser\Node\Stmt\ClassMethod;
20+
use PhpParser\Node\Stmt\Function_;
21+
use PhpParser\Node\Stmt\Interface_;
22+
use PhpParser\Node\Stmt\Trait_;
23+
use PhpParser\Node\Stmt\TraitUse;
1424
use PHPStan\PhpDocParser\Ast\PhpDoc\DeprecatedTagValueNode;
1525
use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode;
1626
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
@@ -27,6 +37,8 @@
2737
use PHPStan\PhpDocParser\Ast\PhpDoc\SelfOutTagValueNode;
2838
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
2939
use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
40+
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode;
41+
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode;
3042
use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode;
3143
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
3244
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
@@ -61,6 +73,8 @@ final class AnnotationsToAttributesRector extends AbstractRector implements Conf
6173

6274
private bool $usePropertyAttributeForVarAnnotation = false;
6375

76+
private bool $useTypeAttributeForTypeClassAnnotation = false;
77+
6478
public function __construct(
6579
private PhpDocTagRemover $phpDocTagRemover,
6680
private AttributeGroupNamedArgumentManipulator $attributeGroupNamedArgumentManipulator,
@@ -138,6 +152,8 @@ public function configure(array $configuration): void
138152
$this->useTypeAttributeForReturnAnnotation = $value;
139153
} elseif (is_bool($value) && $key == 'usePropertyAttributeForVarAnnotation') {
140154
$this->usePropertyAttributeForVarAnnotation = $value;
155+
} elseif (is_bool($value) && $key == 'useTypeAttributeForTypeClassAnnotation') {
156+
$this->useTypeAttributeForTypeClassAnnotation = $value;
141157
} elseif (is_array($value) && $key == 'excludeAnnotations') {
142158
$excludedAnnotations = $value;
143159
}
@@ -165,13 +181,13 @@ public function configure(array $configuration): void
165181
public function getNodeTypes(): array
166182
{
167183
return [
168-
Stmt\Class_::class,
169-
Stmt\ClassConst::class,
170-
Stmt\ClassMethod::class,
171-
Stmt\Function_::class,
172-
Stmt\Interface_::class,
184+
Class_::class,
185+
ClassConst::class,
186+
ClassMethod::class,
187+
Function_::class,
188+
Interface_::class,
173189
Stmt\Property::class,
174-
Stmt\Trait_::class
190+
Trait_::class
175191
];
176192
}
177193

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

197213
if ($this->addParamAttributeOnParameters &&
198-
($node instanceof Stmt\ClassMethod || $node instanceof Stmt\Function_)) {
214+
($node instanceof ClassMethod || $node instanceof Function_)) {
199215
foreach ($attributeGroups as $attrKey => $attributeGroup) {
200216
foreach ($attributeGroup->attrs as $key => $attribute) {
201217
$attributeName = (string)$attribute->name;
@@ -223,9 +239,9 @@ public function refactor(Node $node): ?Node
223239
}
224240
}
225241

226-
if ($node instanceof Stmt\Class_) {
242+
if ($node instanceof Class_) {
227243
foreach ($node->stmts as $stmt) {
228-
if ($stmt instanceof Stmt\TraitUse) {
244+
if ($stmt instanceof TraitUse) {
229245
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($stmt);
230246
if ($phpDocInfo instanceof PhpDocInfo) {
231247
$useAttributeGroups = $this->processAnnotations($phpDocInfo);
@@ -273,24 +289,24 @@ private function processAnnotations(PhpDocInfo $phpDocInfo): array
273289
$methodSignature = substr($methodSignature, 0, -(strlen($attributeComment) + 1));
274290
}
275291
$args = [
276-
new Node\Arg(new Scalar\String_($methodSignature))
292+
new Arg(new String_($methodSignature))
277293
];
278294
break;
279295
case $tagValueNode instanceof ParamOutTagValueNode:
280296
case $tagValueNode instanceof ParamTagValueNode:
281297
$args = [
282-
new Node\Arg(
283-
value: new Scalar\String_((string)($tagValueNode->type)),
284-
name: new Node\Identifier(substr($tagValueNode->parameterName, 1))
298+
new Arg(
299+
value: new String_((string)($tagValueNode->type)),
300+
name: new Identifier(substr($tagValueNode->parameterName, 1))
285301
)
286302
];
287303
$attributeComment = $tagValueNode->description;
288304
break;
289305
case $tagValueNode instanceof PropertyTagValueNode:
290306
$args = [
291-
new Node\Arg(
292-
value: new Scalar\String_((string)($tagValueNode->type)),
293-
name: new Node\Identifier(substr($tagValueNode->propertyName, 1))
307+
new Arg(
308+
value: new String_((string)($tagValueNode->type)),
309+
name: new Identifier(substr($tagValueNode->propertyName, 1))
294310
)
295311
];
296312
$attributeComment = $tagValueNode->description;
@@ -306,16 +322,16 @@ private function processAnnotations(PhpDocInfo $phpDocInfo): array
306322
case $tagValueNode instanceof UsesTagValueNode:
307323
case $tagValueNode instanceof VarTagValueNode:
308324
$args = [
309-
new Node\Arg(new Scalar\String_((string)($tagValueNode->type)))
325+
new Arg(new String_((string)($tagValueNode->type)))
310326
];
311327
$attributeComment = $tagValueNode->description;
312328
break;
313329
case $tagValueNode instanceof TemplateTagValueNode:
314330
$args = [
315-
new Node\Arg(new Scalar\String_($tagValueNode->name))
331+
new Arg(new String_($tagValueNode->name))
316332
];
317333
if ($tagValueNode->bound instanceof IdentifierTypeNode) {
318-
$args[] = new Node\Arg(new Scalar\String_($tagValueNode->bound->name));
334+
$args[] = new Arg(new String_($tagValueNode->bound->name));
319335
}
320336
$attributeComment = $tagValueNode->description;
321337
break;
@@ -327,13 +343,36 @@ private function processAnnotations(PhpDocInfo $phpDocInfo): array
327343
$parts = explode(' ', $remainingText);
328344
$namespace = array_shift($parts);
329345
if ($namespace) {
330-
$args[] = new Node\Arg(new Scalar\String_($namespace));
346+
$args[] = new Arg(new String_($namespace));
331347
$attributeComment = implode(' ', $parts);
332348
}
333349
} else {
334350
$attributeComment = (string)$tagValueNode;
335351
}
336352
break;
353+
case $tagValueNode instanceof TypeAliasTagValueNode:
354+
if ($this->useTypeAttributeForTypeClassAnnotation) {
355+
$alias = (string)($tagValueNode);
356+
$args = [
357+
new Arg(new String_($alias))
358+
];
359+
} else {
360+
$args = [
361+
new Arg(
362+
value: new String_((string)($tagValueNode->type)),
363+
name: new Identifier($tagValueNode->alias)
364+
)
365+
];
366+
}
367+
break;
368+
case $tagValueNode instanceof TypeAliasImportTagValueNode:
369+
$args = [
370+
new Arg(
371+
value: new String_((string)($tagValueNode->importedFrom)),
372+
name: new Identifier($tagValueNode->importedAlias)
373+
)
374+
];
375+
break;
337376
default:
338377
continue 2;
339378
}
@@ -376,6 +415,9 @@ private function matchAnnotationToAttribute(
376415
} elseif (str_starts_with($tagName, 'phpstan-')) {
377416
$tagName = substr($tagName, 8);
378417
}
418+
if ($this->useTypeAttributeForTypeClassAnnotation && $tagName == 'type') {
419+
$tagName = 'var';
420+
}
379421
if (isset($this->annotationsToAttributes[$tagName])) {
380422
return $this->annotationsToAttributes[$tagName];
381423
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace test\PhpStaticAnalysis\RectorRule;
6+
7+
use Iterator;
8+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
9+
10+
final class AnnotationsToAttributesWithTypeAttributeForTypeClassRectorTest extends AbstractRectorTestCase
11+
{
12+
/**
13+
* @dataProvider provideData()
14+
*/
15+
public function test(string $filePath): void
16+
{
17+
$this->doTestFile($filePath);
18+
}
19+
20+
public static function provideData(): Iterator
21+
{
22+
yield [__DIR__ . '/SpecialFixture/TypeAttributeTestForTypeClassAnnotation.php.inc'];
23+
}
24+
25+
public function provideConfigFilePath(): string
26+
{
27+
return __DIR__ . '/config/configured-rule-with-type-attribute-for-type-class.php';
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\RectorRule\Fixture;
4+
5+
use PhpStaticAnalysis\Attributes\DefineType;
6+
7+
/**
8+
* @codeCoverageIgnore
9+
* @phpstan-type StringArray string[]
10+
* @psalm-type IntArray int[]
11+
*/
12+
#[DefineType(FloatArray: 'float[]')]
13+
class DefineTypeAttributeTest
14+
{
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace test\PhpStaticAnalysis\RectorRule\Fixture;
22+
23+
use PhpStaticAnalysis\Attributes\DefineType;
24+
25+
/**
26+
* @codeCoverageIgnore
27+
*/
28+
#[DefineType(FloatArray: 'float[]')]
29+
#[DefineType(StringArray: 'string[]')]
30+
#[DefineType(IntArray: 'int[]')]
31+
class DefineTypeAttributeTest
32+
{
33+
}
34+
35+
?>

0 commit comments

Comments
 (0)