Skip to content

Commit 20d2715

Browse files
Boegiemglaman
andauthored
TestClassSuffixNameRule (#609)
* First stab * PHPStan fixes * TLC * Comment improvement * Rename rule to explain suffix, add behind feature flag. * remove TestClassNameRule from rules.neon * fix service definition --------- Co-authored-by: Matt Glaman <[email protected]>
1 parent f17c857 commit 20d2715

File tree

5 files changed

+194
-21
lines changed

5 files changed

+194
-21
lines changed

extension.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ parameters:
1919
drupal_root: '%currentWorkingDirectory%'
2020
bleedingEdge:
2121
checkDeprecatedHooksInApiFiles: false
22+
rules:
23+
testClassSuffixNameRule: false
2224
entityMapping:
2325
aggregator_feed:
2426
class: Drupal\aggregator\Entity\Feed
@@ -238,6 +240,9 @@ parametersSchema:
238240
bleedingEdge: structure([
239241
checkDeprecatedHooksInApiFiles: boolean()
240242
])
243+
rules: structure([
244+
testClassSuffixNameRule: boolean()
245+
])
241246
entityMapping: arrayOf(anyOf(
242247
structure([
243248
class: string()

rules.neon

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
1-
rules:
2-
- mglaman\PHPStanDrupal\Rules\Drupal\Coder\DiscouragedFunctionsRule
3-
- mglaman\PHPStanDrupal\Rules\Drupal\GlobalDrupalDependencyInjectionRule
4-
- mglaman\PHPStanDrupal\Rules\Drupal\PluginManager\PluginManagerSetsCacheBackendRule
5-
- mglaman\PHPStanDrupal\Rules\Deprecations\AccessDeprecatedConstant
6-
- mglaman\PHPStanDrupal\Rules\Classes\ClassExtendsInternalClassRule
7-
- mglaman\PHPStanDrupal\Rules\Classes\PluginManagerInspectionRule
8-
- mglaman\PHPStanDrupal\Rules\Deprecations\ConditionManagerCreateInstanceContextConfigurationRule
9-
- mglaman\PHPStanDrupal\Rules\Drupal\RenderCallbackRule
10-
- mglaman\PHPStanDrupal\Rules\Deprecations\StaticServiceDeprecatedServiceRule
11-
- mglaman\PHPStanDrupal\Rules\Deprecations\GetDeprecatedServiceRule
12-
- mglaman\PHPStanDrupal\Rules\Drupal\Tests\BrowserTestBaseDefaultThemeRule
13-
- mglaman\PHPStanDrupal\Rules\Deprecations\ConfigEntityConfigExportRule
14-
- mglaman\PHPStanDrupal\Rules\Deprecations\PluginAnnotationContextDefinitionsRule
15-
- mglaman\PHPStanDrupal\Rules\Drupal\ModuleLoadInclude
16-
- mglaman\PHPStanDrupal\Rules\Drupal\LoadIncludes
17-
- mglaman\PHPStanDrupal\Rules\Drupal\EntityQuery\EntityQueryHasAccessCheckRule
18-
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRouteObjectInterfaceConstantsRule
19-
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRoutingInClassMethodSignatureRule
20-
- mglaman\PHPStanDrupal\Rules\Drupal\RequestStackGetMainRequestRule
21-
- mglaman\PHPStanDrupal\Rules\Drupal\TestClassesProtectedPropertyModulesRule
1+
rules:
2+
- mglaman\PHPStanDrupal\Rules\Drupal\Coder\DiscouragedFunctionsRule
3+
- mglaman\PHPStanDrupal\Rules\Drupal\GlobalDrupalDependencyInjectionRule
4+
- mglaman\PHPStanDrupal\Rules\Drupal\PluginManager\PluginManagerSetsCacheBackendRule
5+
- mglaman\PHPStanDrupal\Rules\Deprecations\AccessDeprecatedConstant
6+
- mglaman\PHPStanDrupal\Rules\Classes\ClassExtendsInternalClassRule
7+
- mglaman\PHPStanDrupal\Rules\Classes\PluginManagerInspectionRule
8+
- mglaman\PHPStanDrupal\Rules\Deprecations\ConditionManagerCreateInstanceContextConfigurationRule
9+
- mglaman\PHPStanDrupal\Rules\Drupal\RenderCallbackRule
10+
- mglaman\PHPStanDrupal\Rules\Deprecations\StaticServiceDeprecatedServiceRule
11+
- mglaman\PHPStanDrupal\Rules\Deprecations\GetDeprecatedServiceRule
12+
- mglaman\PHPStanDrupal\Rules\Drupal\Tests\BrowserTestBaseDefaultThemeRule
13+
- mglaman\PHPStanDrupal\Rules\Deprecations\ConfigEntityConfigExportRule
14+
- mglaman\PHPStanDrupal\Rules\Deprecations\PluginAnnotationContextDefinitionsRule
15+
- mglaman\PHPStanDrupal\Rules\Drupal\ModuleLoadInclude
16+
- mglaman\PHPStanDrupal\Rules\Drupal\LoadIncludes
17+
- mglaman\PHPStanDrupal\Rules\Drupal\EntityQuery\EntityQueryHasAccessCheckRule
18+
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRouteObjectInterfaceConstantsRule
19+
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRoutingInClassMethodSignatureRule
20+
- mglaman\PHPStanDrupal\Rules\Drupal\RequestStackGetMainRequestRule
21+
- mglaman\PHPStanDrupal\Rules\Drupal\TestClassesProtectedPropertyModulesRule
22+
23+
conditionalTags:
24+
mglaman\PHPStanDrupal\Rules\Drupal\Tests\TestClassSuffixNameRule:
25+
phpstan.rules.rule: %drupal.rules.testClassSuffixNameRule%
26+
27+
services:
28+
-
29+
class: mglaman\PHPStanDrupal\Rules\Drupal\Tests\TestClassSuffixNameRule
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Rules\Drupal\Tests;
6+
7+
use PhpParser\Node;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPStan\Type\ObjectType;
12+
use PHPUnit\Framework\TestCase;
13+
14+
/**
15+
* Implements rule that all non-abstract test classes name should end with "Test".
16+
*
17+
* @implements Rule<Node\Stmt\Class_>
18+
*/
19+
final class TestClassSuffixNameRule implements Rule
20+
{
21+
22+
public function getNodeType(): string
23+
{
24+
return Node\Stmt\Class_::class;
25+
}
26+
27+
public function processNode(Node $node, Scope $scope): array
28+
{
29+
// We're not interested in non-extending classes.
30+
if ($node->extends === null) {
31+
return [];
32+
}
33+
34+
// We're not interested in abstract classes.
35+
if ($node->isAbstract()) {
36+
return [];
37+
}
38+
39+
// We need a namespaced class name.
40+
if ($node->namespacedName === null) {
41+
return [];
42+
}
43+
44+
// We're only interested in \PHPUnit\Framework\TestCase subtype classes.
45+
$classType = $scope->resolveTypeByName($node->namespacedName);
46+
$phpUnitFrameworkTestCaseType = new ObjectType(TestCase::class);
47+
if (!$phpUnitFrameworkTestCaseType->isSuperTypeOf($classType)->yes()) {
48+
return [];
49+
}
50+
51+
// Check class name has suffix "Test".
52+
// @todo replace this str_ends_with() when php 8 is required.
53+
if (substr_compare($node->namespacedName->getLast(), 'Test', -4) === 0) {
54+
return [];
55+
}
56+
57+
return [
58+
RuleErrorBuilder::message(
59+
sprintf(
60+
'Non-abstract test classes names should always have the suffix "Test", found incorrect class name "%s".',
61+
$node->name,
62+
)
63+
)
64+
->line($node->getStartLine())
65+
->tip('See https://www.drupal.org/docs/develop/standards/php/object-oriented-code#naming')
66+
->build()
67+
];
68+
}
69+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Tests\Rules;
6+
7+
use mglaman\PHPStanDrupal\Rules\Drupal\Tests\TestClassSuffixNameRule;
8+
use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;
9+
use PHPStan\Rules\Rule;
10+
11+
class TestClassSuffixNameRuleTest extends DrupalRuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new TestClassSuffixNameRule();
17+
}
18+
19+
/**
20+
* @dataProvider fileData
21+
*/
22+
public function testRule(string $path, array $errorMessages): void
23+
{
24+
$this->analyse(
25+
[$path],
26+
$errorMessages
27+
);
28+
}
29+
30+
public function fileData(): \Generator
31+
{
32+
yield [
33+
__DIR__ . '/data/test-cases-TestClassSuffixNameRule.php',
34+
[
35+
[
36+
'Non-abstract test classes names should always have the suffix "Test", found incorrect class name "IncorrectlyNamedDirectlyExtendingPHPUnitFrameworkTestCaseClass".',
37+
29,
38+
'See https://www.drupal.org/docs/develop/standards/php/object-oriented-code#naming'
39+
],
40+
[
41+
'Non-abstract test classes names should always have the suffix "Test", found incorrect class name "IncorrectlyNamedIndirectlyExtendingPHPUnitFrameworkTestCaseClass".',
42+
39,
43+
'See https://www.drupal.org/docs/develop/standards/php/object-oriented-code#naming'
44+
],
45+
]
46+
];
47+
}
48+
49+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace TestCasesTestClassSuffixNameRule;
4+
5+
use Drupal\node\Entity\Node;
6+
use Drupal\Tests\views\Functional\ViewTestBase;
7+
use PHPUnit\Framework\TestCase;
8+
9+
class IgnoreNonExtendingClasses
10+
{
11+
12+
}
13+
14+
abstract class IgnoreAbstractClasses extends TestCase
15+
{
16+
17+
}
18+
19+
class IgnoreNonPHPUnitFrameworkTestCaseExtendingClasses extends Node
20+
{
21+
22+
}
23+
24+
class CorrectlyNamedDirectlyExtendingPHPUnitFrameworkTestCaseTest extends TestCase
25+
{
26+
27+
}
28+
29+
class IncorrectlyNamedDirectlyExtendingPHPUnitFrameworkTestCaseClass extends TestCase
30+
{
31+
32+
}
33+
34+
class CorrectlyNamedIndirectlyExtendingPHPUnitFrameworkTestCaseTest extends ViewTestBase
35+
{
36+
37+
}
38+
39+
class IncorrectlyNamedIndirectlyExtendingPHPUnitFrameworkTestCaseClass extends ViewTestBase
40+
{
41+
42+
}

0 commit comments

Comments
 (0)