Skip to content

Commit 410764b

Browse files
committed
feat: Discourage assert(Not)Empty if "empty" usage is disallowed
fixes: #270
1 parent 203970d commit 410764b

6 files changed

Lines changed: 124 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ It also contains this strict framework-specific rules (can be enabled separately
2424
* Check that you are not using `assertSame()` with `count($variable)` as second parameter. `assertCount($variable)` should be used instead.
2525
* Check that you are not using `assertEquals()` with same types (`assertSame()` should be used)
2626
* Check that you are not using `assertNotEquals()` with same types (`assertNotSame()` should be used)
27+
* When PHPStan Strict Rules' `disallowedEmpty` rule is enabled, disallow PHPUnit's `assertEmpty()` and `assertNotEmpty()` assertions as well.
2728

2829
## How to document mock objects in phpDocs?
2930

extension.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
parameters:
2+
strictRules:
3+
disallowedEmpty: false
24
phpunit:
35
convertUnionToIntersectionType: true
46
reportMissingDataProviderReturnType: false

rules.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ rules:
1212
conditionalTags:
1313
PHPStan\Rules\PHPUnit\AssertEqualsIsDiscouragedRule:
1414
phpstan.rules.rule: [%strictRulesInstalled%, %featureToggles.bleedingEdge%]
15+
PHPStan\Rules\PHPUnit\AssertEmptyIsDiscouragedRule:
16+
phpstan.rules.rule: [%strictRulesInstalled%, %strictRules.disallowedEmpty%]
1517

1618
PHPStan\Rules\PHPUnit\DataProviderDataRule:
1719
phpstan.rules.rule: %featureToggles.bleedingEdge%
@@ -39,5 +41,8 @@ services:
3941
-
4042
class: PHPStan\Rules\PHPUnit\AssertEqualsIsDiscouragedRule
4143

44+
-
45+
class: PHPStan\Rules\PHPUnit\AssertEmptyIsDiscouragedRule
46+
4247
-
4348
class: PHPStan\Rules\PHPUnit\DataProviderDataRule
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PHPUnit;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\CallLike;
7+
use PhpParser\Node\Expr\FuncCall;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PhpParser\Node\Expr\StaticCall;
10+
use PhpParser\Node\Identifier;
11+
use PhpParser\Node\Name;
12+
use PHPStan\Analyser\Scope;
13+
use PHPStan\Rules\Rule;
14+
use PHPStan\Rules\RuleErrorBuilder;
15+
use function count;
16+
use function in_array;
17+
use function sprintf;
18+
use function strtolower;
19+
20+
/**
21+
* @implements Rule<CallLike>
22+
*/
23+
class AssertEmptyIsDiscouragedRule implements Rule
24+
{
25+
26+
public function getNodeType(): string
27+
{
28+
return CallLike::class;
29+
}
30+
31+
public function processNode(Node $node, Scope $scope): array
32+
{
33+
if ($node->isFirstClassCallable() || count($node->getArgs()) < 1) {
34+
return [];
35+
}
36+
37+
if ($node instanceof MethodCall || $node instanceof StaticCall) {
38+
if (!$node->name instanceof Identifier || !in_array($node->name->toLowerString(), ['assertempty', 'assertnotempty'], true)) {
39+
return [];
40+
}
41+
if (!AssertRuleHelper::isMethodOrStaticCallOnAssert($node, $scope)) {
42+
return [];
43+
}
44+
} elseif ($node instanceof FuncCall) {
45+
if (!$node->name instanceof Name || !in_array(strtolower($scope->resolveName($node->name)), ['phpunit\\framework\\assertempty', 'phpunit\\framework\\assertnotempty'], true)) {
46+
return [];
47+
}
48+
} else {
49+
return [];
50+
}
51+
52+
return [
53+
RuleErrorBuilder::message(sprintf(
54+
'%s() is not allowed. Use more strict assertion.',
55+
$node instanceof FuncCall ? $node->name->getLast() : $node->name->toString(),
56+
))
57+
->identifier('empty.notAllowed')
58+
->build(),
59+
];
60+
}
61+
62+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PHPUnit;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<AssertEmptyIsDiscouragedRule>
10+
*/
11+
final class AssertEmptyIsDiscouragedRuleTest extends RuleTestCase
12+
{
13+
14+
public function testRule(): void
15+
{
16+
$this->analyse([__DIR__ . '/data/assert-empty-is-discouraged.php'], [
17+
['assertEmpty() is not allowed. Use more strict assertion.', 15],
18+
['assertNotEmpty() is not allowed. Use more strict assertion.', 16],
19+
['assertEmpty() is not allowed. Use more strict assertion.', 17],
20+
['assertNotEmpty() is not allowed. Use more strict assertion.', 18],
21+
['assertEmpty() is not allowed. Use more strict assertion.', 19],
22+
['assertNotEmpty() is not allowed. Use more strict assertion.', 20],
23+
]);
24+
}
25+
26+
protected function getRule(): Rule
27+
{
28+
return new AssertEmptyIsDiscouragedRule();
29+
}
30+
31+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace AssertEmptyIsDiscouragedTest;
4+
5+
use PHPUnit\Framework\Assert;
6+
use PHPUnit\Framework\TestCase;
7+
use function PHPUnit\Framework\assertEmpty;
8+
use function PHPUnit\Framework\assertNotEmpty;
9+
10+
final class AssertEmptyTest extends TestCase
11+
{
12+
13+
public function test(): void
14+
{
15+
$this->assertEmpty([]);
16+
$this->assertNotEmpty([1]);
17+
Assert::assertEmpty([]);
18+
static::assertNotEmpty([1]);
19+
assertEmpty([]);
20+
assertNotEmpty([1]);
21+
}
22+
23+
}

0 commit comments

Comments
 (0)