Skip to content

Commit

Permalink
TestClassSuffixNameRule (#609)
Browse files Browse the repository at this point in the history
* 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 <nmd.matt@gmail.com>
  • Loading branch information
Boegie and mglaman authored Jan 31, 2024
1 parent f17c857 commit 20d2715
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 21 deletions.
5 changes: 5 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ parameters:
drupal_root: '%currentWorkingDirectory%'
bleedingEdge:
checkDeprecatedHooksInApiFiles: false
rules:
testClassSuffixNameRule: false
entityMapping:
aggregator_feed:
class: Drupal\aggregator\Entity\Feed
Expand Down Expand Up @@ -238,6 +240,9 @@ parametersSchema:
bleedingEdge: structure([
checkDeprecatedHooksInApiFiles: boolean()
])
rules: structure([
testClassSuffixNameRule: boolean()
])
entityMapping: arrayOf(anyOf(
structure([
class: string()
Expand Down
50 changes: 29 additions & 21 deletions rules.neon
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
rules:
- mglaman\PHPStanDrupal\Rules\Drupal\Coder\DiscouragedFunctionsRule
- mglaman\PHPStanDrupal\Rules\Drupal\GlobalDrupalDependencyInjectionRule
- mglaman\PHPStanDrupal\Rules\Drupal\PluginManager\PluginManagerSetsCacheBackendRule
- mglaman\PHPStanDrupal\Rules\Deprecations\AccessDeprecatedConstant
- mglaman\PHPStanDrupal\Rules\Classes\ClassExtendsInternalClassRule
- mglaman\PHPStanDrupal\Rules\Classes\PluginManagerInspectionRule
- mglaman\PHPStanDrupal\Rules\Deprecations\ConditionManagerCreateInstanceContextConfigurationRule
- mglaman\PHPStanDrupal\Rules\Drupal\RenderCallbackRule
- mglaman\PHPStanDrupal\Rules\Deprecations\StaticServiceDeprecatedServiceRule
- mglaman\PHPStanDrupal\Rules\Deprecations\GetDeprecatedServiceRule
- mglaman\PHPStanDrupal\Rules\Drupal\Tests\BrowserTestBaseDefaultThemeRule
- mglaman\PHPStanDrupal\Rules\Deprecations\ConfigEntityConfigExportRule
- mglaman\PHPStanDrupal\Rules\Deprecations\PluginAnnotationContextDefinitionsRule
- mglaman\PHPStanDrupal\Rules\Drupal\ModuleLoadInclude
- mglaman\PHPStanDrupal\Rules\Drupal\LoadIncludes
- mglaman\PHPStanDrupal\Rules\Drupal\EntityQuery\EntityQueryHasAccessCheckRule
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRouteObjectInterfaceConstantsRule
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRoutingInClassMethodSignatureRule
- mglaman\PHPStanDrupal\Rules\Drupal\RequestStackGetMainRequestRule
- mglaman\PHPStanDrupal\Rules\Drupal\TestClassesProtectedPropertyModulesRule
rules:
- mglaman\PHPStanDrupal\Rules\Drupal\Coder\DiscouragedFunctionsRule
- mglaman\PHPStanDrupal\Rules\Drupal\GlobalDrupalDependencyInjectionRule
- mglaman\PHPStanDrupal\Rules\Drupal\PluginManager\PluginManagerSetsCacheBackendRule
- mglaman\PHPStanDrupal\Rules\Deprecations\AccessDeprecatedConstant
- mglaman\PHPStanDrupal\Rules\Classes\ClassExtendsInternalClassRule
- mglaman\PHPStanDrupal\Rules\Classes\PluginManagerInspectionRule
- mglaman\PHPStanDrupal\Rules\Deprecations\ConditionManagerCreateInstanceContextConfigurationRule
- mglaman\PHPStanDrupal\Rules\Drupal\RenderCallbackRule
- mglaman\PHPStanDrupal\Rules\Deprecations\StaticServiceDeprecatedServiceRule
- mglaman\PHPStanDrupal\Rules\Deprecations\GetDeprecatedServiceRule
- mglaman\PHPStanDrupal\Rules\Drupal\Tests\BrowserTestBaseDefaultThemeRule
- mglaman\PHPStanDrupal\Rules\Deprecations\ConfigEntityConfigExportRule
- mglaman\PHPStanDrupal\Rules\Deprecations\PluginAnnotationContextDefinitionsRule
- mglaman\PHPStanDrupal\Rules\Drupal\ModuleLoadInclude
- mglaman\PHPStanDrupal\Rules\Drupal\LoadIncludes
- mglaman\PHPStanDrupal\Rules\Drupal\EntityQuery\EntityQueryHasAccessCheckRule
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRouteObjectInterfaceConstantsRule
- mglaman\PHPStanDrupal\Rules\Deprecations\SymfonyCmfRoutingInClassMethodSignatureRule
- mglaman\PHPStanDrupal\Rules\Drupal\RequestStackGetMainRequestRule
- mglaman\PHPStanDrupal\Rules\Drupal\TestClassesProtectedPropertyModulesRule

conditionalTags:
mglaman\PHPStanDrupal\Rules\Drupal\Tests\TestClassSuffixNameRule:
phpstan.rules.rule: %drupal.rules.testClassSuffixNameRule%

services:
-
class: mglaman\PHPStanDrupal\Rules\Drupal\Tests\TestClassSuffixNameRule
69 changes: 69 additions & 0 deletions src/Rules/Drupal/Tests/TestClassSuffixNameRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Rules\Drupal\Tests;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ObjectType;
use PHPUnit\Framework\TestCase;

/**
* Implements rule that all non-abstract test classes name should end with "Test".
*
* @implements Rule<Node\Stmt\Class_>
*/
final class TestClassSuffixNameRule implements Rule
{

public function getNodeType(): string
{
return Node\Stmt\Class_::class;
}

public function processNode(Node $node, Scope $scope): array
{
// We're not interested in non-extending classes.
if ($node->extends === null) {
return [];
}

// We're not interested in abstract classes.
if ($node->isAbstract()) {
return [];
}

// We need a namespaced class name.
if ($node->namespacedName === null) {
return [];
}

// We're only interested in \PHPUnit\Framework\TestCase subtype classes.
$classType = $scope->resolveTypeByName($node->namespacedName);
$phpUnitFrameworkTestCaseType = new ObjectType(TestCase::class);
if (!$phpUnitFrameworkTestCaseType->isSuperTypeOf($classType)->yes()) {
return [];
}

// Check class name has suffix "Test".
// @todo replace this str_ends_with() when php 8 is required.
if (substr_compare($node->namespacedName->getLast(), 'Test', -4) === 0) {
return [];
}

return [
RuleErrorBuilder::message(
sprintf(
'Non-abstract test classes names should always have the suffix "Test", found incorrect class name "%s".',
$node->name,
)
)
->line($node->getStartLine())
->tip('See https://www.drupal.org/docs/develop/standards/php/object-oriented-code#naming')
->build()
];
}
}
49 changes: 49 additions & 0 deletions tests/src/Rules/TestClassSuffixNameRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Tests\Rules;

use mglaman\PHPStanDrupal\Rules\Drupal\Tests\TestClassSuffixNameRule;
use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;
use PHPStan\Rules\Rule;

class TestClassSuffixNameRuleTest extends DrupalRuleTestCase
{

protected function getRule(): Rule
{
return new TestClassSuffixNameRule();
}

/**
* @dataProvider fileData
*/
public function testRule(string $path, array $errorMessages): void
{
$this->analyse(
[$path],
$errorMessages
);
}

public function fileData(): \Generator
{
yield [
__DIR__ . '/data/test-cases-TestClassSuffixNameRule.php',
[
[
'Non-abstract test classes names should always have the suffix "Test", found incorrect class name "IncorrectlyNamedDirectlyExtendingPHPUnitFrameworkTestCaseClass".',
29,
'See https://www.drupal.org/docs/develop/standards/php/object-oriented-code#naming'
],
[
'Non-abstract test classes names should always have the suffix "Test", found incorrect class name "IncorrectlyNamedIndirectlyExtendingPHPUnitFrameworkTestCaseClass".',
39,
'See https://www.drupal.org/docs/develop/standards/php/object-oriented-code#naming'
],
]
];
}

}
42 changes: 42 additions & 0 deletions tests/src/Rules/data/test-cases-TestClassSuffixNameRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace TestCasesTestClassSuffixNameRule;

use Drupal\node\Entity\Node;
use Drupal\Tests\views\Functional\ViewTestBase;
use PHPUnit\Framework\TestCase;

class IgnoreNonExtendingClasses
{

}

abstract class IgnoreAbstractClasses extends TestCase
{

}

class IgnoreNonPHPUnitFrameworkTestCaseExtendingClasses extends Node
{

}

class CorrectlyNamedDirectlyExtendingPHPUnitFrameworkTestCaseTest extends TestCase
{

}

class IncorrectlyNamedDirectlyExtendingPHPUnitFrameworkTestCaseClass extends TestCase
{

}

class CorrectlyNamedIndirectlyExtendingPHPUnitFrameworkTestCaseTest extends ViewTestBase
{

}

class IncorrectlyNamedIndirectlyExtendingPHPUnitFrameworkTestCaseClass extends ViewTestBase
{

}

0 comments on commit 20d2715

Please sign in to comment.