Skip to content

Commit dbfe30d

Browse files
committed
Add rule that enforces PHPUnit naming conventions
1 parent 4b6ad7f commit dbfe30d

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

src/Rules/PHPUnit/ClassNamingRule.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PHPUnit;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\ReflectionProvider;
8+
use PHPStan\Rules\IdentifierRuleError;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPUnit\Framework\TestCase;
12+
use function sprintf;
13+
use function str_ends_with;
14+
15+
/**
16+
* @implements Rule<Node\Stmt\Class_>
17+
*/
18+
class ClassNamingRule implements Rule
19+
{
20+
21+
private ReflectionProvider $reflectionProvider;
22+
23+
public function __construct(ReflectionProvider $reflectionProvider)
24+
{
25+
$this->reflectionProvider = $reflectionProvider;
26+
}
27+
28+
public function getNodeType(): string
29+
{
30+
return Node\Stmt\Class_::class;
31+
}
32+
33+
public function processNode(Node $node, Scope $scope): array
34+
{
35+
if (!isset($node->namespacedName)) {
36+
return [];
37+
}
38+
39+
$className = $node->namespacedName->name;
40+
$class = $this->reflectionProvider->getClass($className);
41+
42+
if (!$class->isSubclassOf(TestCase::class)) {
43+
return [];
44+
}
45+
46+
$errors = [];
47+
48+
if ($class->isAbstract()) {
49+
$this->requireSuffix(
50+
$errors,
51+
$className,
52+
'TestCase',
53+
'Abstract test case class, \'%s\', should be named ending in \'%s\'.',
54+
);
55+
56+
return $errors;
57+
}
58+
59+
$this->requireSuffix(
60+
$errors,
61+
$className,
62+
'Test',
63+
'Concrete test class, \'%s\', should be named ending in \'%s\'.',
64+
);
65+
66+
if (!$class->isFinal()) {
67+
$errors[] = RuleErrorBuilder::message(sprintf(
68+
'Concrete test class, \'%s\', should be declared final.',
69+
$className,
70+
))->identifier('phpunit.naming')->build();
71+
}
72+
73+
return $errors;
74+
}
75+
76+
/**
77+
* @param list<IdentifierRuleError> $errors
78+
*/
79+
private function requireSuffix(array &$errors, string $className, string $suffix, string $messageFormat): void
80+
{
81+
if (str_ends_with($className, $suffix)) {
82+
return;
83+
}
84+
85+
// @todo Case sensitivity??
86+
$errors[] = RuleErrorBuilder::message(sprintf(
87+
$messageFormat,
88+
$className,
89+
$suffix,
90+
))->identifier('phpunit.naming')->build();
91+
}
92+
93+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PHPUnit;
4+
5+
use PHPStan\Reflection\ReflectionProvider;
6+
use PHPStan\Rules\Rule;
7+
use PHPStan\Testing\RuleTestCase;
8+
9+
/**
10+
* @extends RuleTestCase<ClassNamingRule>
11+
*/
12+
class ClassNamingRuleTest extends RuleTestCase
13+
{
14+
15+
protected function getRule(): Rule
16+
{
17+
return new ClassNamingRule(self::getContainer()->getByType(ReflectionProvider::class));
18+
}
19+
20+
public function testRule(): void
21+
{
22+
$this->analyse([__DIR__ . '/data/class-naming.php'], [
23+
['Concrete test class, \'ExampleTestCase\\IncorrectlyNamedTst\', should be named ending in \'Test\'.', 5],
24+
['Abstract test case class, \'ExampleTestCase\\IncorrectlyNamedTestCse\', should be named ending in \'TestCase\'.', 10],
25+
['Concrete test class, \'ExampleTestCase\\NotFinalTest\', should be declared final.', 15],
26+
['Concrete test class, \'ExampleTestCase\\NotFinalOrNamedCorrectly\', should be named ending in \'Test\'.', 20],
27+
['Concrete test class, \'ExampleTestCase\\NotFinalOrNamedCorrectly\', should be declared final.', 20],
28+
]);
29+
}
30+
31+
/**
32+
* @return string[]
33+
*/
34+
public static function getAdditionalConfigFiles(): array
35+
{
36+
return [
37+
__DIR__ . '/../../../extension.neon',
38+
];
39+
}
40+
41+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace ExampleTestCase;
4+
5+
final class IncorrectlyNamedTst extends \PHPUnit\Framework\TestCase
6+
{
7+
8+
}
9+
10+
abstract class IncorrectlyNamedTestCse extends \PHPUnit\Framework\TestCase
11+
{
12+
13+
}
14+
15+
class NotFinalTest extends \PHPUnit\Framework\TestCase
16+
{
17+
18+
}
19+
20+
class NotFinalOrNamedCorrectly extends \PHPUnit\Framework\TestCase
21+
{
22+
23+
}
24+
25+
new class() extends \PHPUnit\Framework\TestCase {
26+
27+
};
28+
29+
final class CorrectlyNamedAndFinalTest extends \PHPUnit\Framework\TestCase
30+
{
31+
32+
}
33+
34+
abstract class CorrectlyNamedTestCase extends \PHPUnit\Framework\TestCase
35+
{
36+
37+
}

0 commit comments

Comments
 (0)