Skip to content

Commit 6095601

Browse files
committed
PHPUnit attributes
2 parents b0c71c4 + 4b46256 commit 6095601

File tree

324 files changed

+1599
-3431
lines changed

Some content is hidden

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

324 files changed

+1599
-3431
lines changed

build/PHPStan/Build/AttributeNamedArgumentsRule.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ public function processNode(Node $node, Scope $scope): array
3939
return [];
4040
}
4141
$constructor = $attributeReflection->getConstructor();
42+
if (!$constructor->acceptsNamedArguments()->yes()) {
43+
return [];
44+
}
45+
4246
$variants = $constructor->getVariants();
4347
if (count($variants) !== 1) {
4448
return [];
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Build;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Attribute;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Node\InClassMethodNode;
9+
use PHPStan\Php\PhpVersion;
10+
use PHPStan\Rules\Rule;
11+
use PHPStan\Rules\RuleErrorBuilder;
12+
use PHPStan\ShouldNotHappenException;
13+
use PHPUnit\Framework\TestCase;
14+
use function array_values;
15+
use function count;
16+
use function get_class;
17+
use function sprintf;
18+
19+
/**
20+
* @implements Rule<InClassMethodNode>
21+
*/
22+
final class SkipTestsWithRequiresPhpAttributeRule implements Rule
23+
{
24+
25+
public function getNodeType(): string
26+
{
27+
return InClassMethodNode::class;
28+
}
29+
30+
public function processNode(Node $node, Scope $scope): array
31+
{
32+
$methodReflection = $node->getMethodReflection();
33+
if (!$methodReflection->getDeclaringClass()->is(TestCase::class)) {
34+
return [];
35+
}
36+
37+
$originalNode = $node->getOriginalNode();
38+
if ($originalNode->stmts === null) {
39+
return [];
40+
}
41+
42+
if (count($originalNode->stmts) === 0) {
43+
return [];
44+
}
45+
46+
$firstStmt = $originalNode->stmts[0];
47+
if (!$firstStmt instanceof Node\Stmt\If_) {
48+
return [];
49+
}
50+
51+
if (!$firstStmt->cond instanceof Node\Expr\BinaryOp) {
52+
return [];
53+
}
54+
55+
switch (get_class($firstStmt->cond)) {
56+
case Node\Expr\BinaryOp\SmallerOrEqual::class:
57+
$inverseBinaryOpSigil = '>';
58+
break;
59+
case Node\Expr\BinaryOp\Smaller::class:
60+
$inverseBinaryOpSigil = '>=';
61+
break;
62+
case Node\Expr\BinaryOp\GreaterOrEqual::class:
63+
$inverseBinaryOpSigil = '<';
64+
break;
65+
case Node\Expr\BinaryOp\Greater::class:
66+
$inverseBinaryOpSigil = '<=';
67+
break;
68+
case Node\Expr\BinaryOp\Identical::class:
69+
$inverseBinaryOpSigil = '!==';
70+
break;
71+
case Node\Expr\BinaryOp\NotIdentical::class:
72+
$inverseBinaryOpSigil = '===';
73+
break;
74+
default:
75+
throw new ShouldNotHappenException('No inverse comparison specified for ' . get_class($firstStmt->cond));
76+
}
77+
78+
if (!$firstStmt->cond->left instanceof Node\Expr\ConstFetch || $firstStmt->cond->left->name->toString() !== 'PHP_VERSION_ID') {
79+
return [];
80+
}
81+
82+
if (!$firstStmt->cond->right instanceof Node\Scalar\Int_) {
83+
return [];
84+
}
85+
86+
if (count($firstStmt->stmts) !== 1) {
87+
return [];
88+
}
89+
90+
$ifStmt = $firstStmt->stmts[0];
91+
if (!$ifStmt instanceof Node\Stmt\Expression) {
92+
return [];
93+
}
94+
95+
if (!$ifStmt->expr instanceof Node\Expr\StaticCall && !$ifStmt->expr instanceof Node\Expr\MethodCall) {
96+
return [];
97+
}
98+
99+
if (!$ifStmt->expr->name instanceof Node\Identifier || $ifStmt->expr->name->toLowerString() !== 'marktestskipped') {
100+
return [];
101+
}
102+
103+
$phpVersion = new PhpVersion($firstStmt->cond->right->value);
104+
105+
return [
106+
RuleErrorBuilder::message('Skip tests with #[RequiresPhp] attribute intead.')
107+
->identifier('phpstan.skipTestsRequiresPhp')
108+
->line($firstStmt->getStartLine())
109+
->fixNode($originalNode, static function (Node\Stmt\ClassMethod $node) use ($phpVersion, $inverseBinaryOpSigil) {
110+
$stmts = $node->stmts;
111+
if ($stmts === null) {
112+
return $node;
113+
}
114+
115+
unset($stmts[0]);
116+
$node->stmts = array_values($stmts);
117+
$node->attrGroups[] = new Node\AttributeGroup([
118+
new Attribute(new Node\Name\FullyQualified('PHPUnit\\Framework\\Attributes\\RequiresPhp'), [
119+
new Node\Arg(new Node\Scalar\String_(sprintf('%s %s', $inverseBinaryOpSigil, $phpVersion->getVersionString()))),
120+
]),
121+
]);
122+
123+
return $node;
124+
})
125+
->build(),
126+
];
127+
}
128+
129+
130+
}

build/phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ rules:
122122
- PHPStan\Build\AttributeNamedArgumentsRule
123123
- PHPStan\Build\NamedArgumentsRule
124124
- PHPStan\Build\OverrideAttributeThirdPartyMethodRule
125+
- PHPStan\Build\SkipTestsWithRequiresPhpAttributeRule
125126

126127
services:
127128
-

compiler/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"require": {
77
"php": "^8.0",
88
"nette/neon": "^3.0.0",
9-
"ondrejmirtes/simple-downgrader": "^2.1.7",
9+
"ondrejmirtes/simple-downgrader": "^2.1.8",
1010
"seld/phar-utils": "^1.2",
1111
"symfony/console": "^5.4.43",
1212
"symfony/filesystem": "^5.4.43",

compiler/composer.lock

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)