Skip to content

Commit

Permalink
Add ShouldCallParentMethodsRule
Browse files Browse the repository at this point in the history
  • Loading branch information
canvural authored and ondrejmirtes committed Jul 28, 2020
1 parent 92fb18f commit d686c82
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 0 deletions.
1 change: 1 addition & 0 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ rules:
- PHPStan\Rules\PHPUnit\AssertSameNullExpectedRule
- PHPStan\Rules\PHPUnit\AssertSameWithCountRule
- PHPStan\Rules\PHPUnit\MockMethodCallRule
- PHPStan\Rules\PHPUnit\ShouldCallParentMethodsRule
104 changes: 104 additions & 0 deletions src/Rules/PHPUnit/ShouldCallParentMethodsRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PHPUnit;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassMethodNode;
use PHPStan\Rules\RuleErrorBuilder;
use PHPUnit\Framework\TestCase;

/**
* @implements \PHPStan\Rules\Rule<InClassMethodNode>
*/
class ShouldCallParentMethodsRule implements \PHPStan\Rules\Rule
{

public function getNodeType(): string
{
return InClassMethodNode::class;
}

public function processNode(Node $node, Scope $scope): array
{
/** @var InClassMethodNode $node */
$node = $node;

if ($scope->getClassReflection() === null) {
return [];
}

if (!$scope->getClassReflection()->isSubclassOf(TestCase::class)) {
return [];
}

$parentClass = $scope->getClassReflection()->getParentClass();

if ($parentClass === false) {
return [];
}

if ($parentClass->getName() === TestCase::class) {
return [];
}

if (!in_array(strtolower($node->getOriginalNode()->name->name), ['setup', 'teardown'], true)) {
return [];
}

$hasParentCall = $this->hasParentClassCall($node->getOriginalNode()->getStmts());

if (!$hasParentCall) {
return [
RuleErrorBuilder::message(
sprintf('Missing call to parent::%s method.', $node->getOriginalNode()->name->name)
)->build(),
];
}

return [];
}

/**
* @param Node\Stmt[]|null $stmts
*
* @return bool
*/
private function hasParentClassCall(?array $stmts): bool
{
if ($stmts === null) {
return false;
}

foreach ($stmts as $stmt) {
if (! $stmt instanceof Node\Stmt\Expression) {
continue;
}

if (! $stmt->expr instanceof Node\Expr\StaticCall) {
continue;
}

if (! $stmt->expr->class instanceof Node\Name) {
continue;
}

$class = (string) $stmt->expr->class;

if (strtolower($class) !== 'parent') {
continue;
}

if (! $stmt->expr->name instanceof Node\Identifier) {
continue;
}

if (in_array(strtolower($stmt->expr->name->name), ['setup', 'teardown'], true)) {
return true;
}
}

return false;
}

}
32 changes: 32 additions & 0 deletions tests/Rules/PHPUnit/ShouldCallParentMethodsRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PHPUnit;

use PHPStan\Rules\Rule;

/**
* @extends \PHPStan\Testing\RuleTestCase<ShouldCallParentMethodsRule>
*/
class ShouldCallParentMethodsRuleTest extends \PHPStan\Testing\RuleTestCase
{

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

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/missing-parent-method-calls.php'], [
[
'Missing call to parent::setUp method.',
32,
],
[
'Missing call to parent::tearDown method.',
63,
],
]);
}

}
77 changes: 77 additions & 0 deletions tests/Rules/PHPUnit/data/missing-parent-method-calls.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace MissingParentMethodCalls;

use PHPUnit\Framework\TestCase;

class FooTest extends TestCase
{
public function setUp(): void
{
$this->foo = true;
}
}

class BaseTestCase extends TestCase
{
public function setUp(): void
{
$this->bar = true;
}

public function tearDown(): void
{
$this->bar = null;
}
}

class BazTest extends BaseTestCase
{
private $baz;

public function setUp(): void
{
$this->baz = true;
}

public function baz(): bool
{
return $this->baz;
}
}

class BarBazTest extends BaseTestCase
{
public function setUp(): void
{
parent::setUp();

$this->barBaz = true;
}
}

class FooBarBazTest extends BaseTestCase
{
public function setUp(): void
{
$result = 1 + 1;
parent::setUp();

$this->fooBarBaz = $result;
}

public function tearDown(): void
{
$this->fooBarBaz = null;
}
}

class NormalBaseClass {}

class NormalClass extends NormalBaseClass
{
public function setUp()
{
return true;
}
}

0 comments on commit d686c82

Please sign in to comment.