Skip to content

Commit

Permalink
Implement AssertEqualsIsDiscouragedRule (#216)
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm authored Dec 17, 2024
1 parent 4b6ad7f commit 10880da
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ It also contains this strict framework-specific rules (can be enabled separately
* Check that you are not using `assertSame()` with `false` as expected value. `assertFalse()` should be used instead.
* Check that you are not using `assertSame()` with `null` as expected value. `assertNull()` should be used instead.
* Check that you are not using `assertSame()` with `count($variable)` as second parameter. `assertCount($variable)` should be used instead.
* Check that you are not using `assertEquals()` with same types (`assertSame()` should be used)

## How to document mock objects in phpDocs?

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
],
"require": {
"php": "^7.4 || ^8.0",
"phpstan/phpstan": "^2.0"
"phpstan/phpstan": "^2.0.4"
},
"conflict": {
"phpunit/phpunit": "<7.0"
Expand Down
7 changes: 7 additions & 0 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ rules:
- PHPStan\Rules\PHPUnit\NoMissingSpaceInMethodAnnotationRule
- PHPStan\Rules\PHPUnit\ShouldCallParentMethodsRule

conditionalTags:
PHPStan\Rules\PHPUnit\AssertEqualsIsDiscouragedRule:
phpstan.rules.rule: [%strictRulesInstalled%, %featureToggles.bleedingEdge%]

services:
-
class: PHPStan\Rules\PHPUnit\DataProviderDeclarationRule
Expand All @@ -17,3 +21,6 @@ services:
deprecationRulesInstalled: %deprecationRulesInstalled%
tags:
- phpstan.rules.rule

-
class: PHPStan\Rules\PHPUnit\AssertEqualsIsDiscouragedRule
64 changes: 64 additions & 0 deletions src/Rules/PHPUnit/AssertEqualsIsDiscouragedRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PHPUnit;

use PhpParser\Node;
use PhpParser\Node\Expr\CallLike;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\TypeCombinator;
use function count;
use function strtolower;

/**
* @implements Rule<CallLike>
*/
class AssertEqualsIsDiscouragedRule implements Rule
{

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

public function processNode(Node $node, Scope $scope): array
{
if (!AssertRuleHelper::isMethodOrStaticCallOnAssert($node, $scope)) {
return [];
}

if (count($node->getArgs()) < 2) {
return [];
}
if (!$node->name instanceof Node\Identifier || strtolower($node->name->name) !== 'assertequals') {
return [];
}

$leftType = TypeCombinator::removeNull($scope->getType($node->getArgs()[0]->value));
$rightType = TypeCombinator::removeNull($scope->getType($node->getArgs()[1]->value));

if ($leftType->isConstantScalarValue()->yes()) {
$leftType = $leftType->generalize(GeneralizePrecision::lessSpecific());
}
if ($rightType->isConstantScalarValue()->yes()) {
$rightType = $rightType->generalize(GeneralizePrecision::lessSpecific());
}

if (
($leftType->isScalar()->yes() && $rightType->isScalar()->yes())
&& ($leftType->isSuperTypeOf($rightType)->yes())
&& ($rightType->isSuperTypeOf($leftType)->yes())
) {
return [
RuleErrorBuilder::message(
'You should use assertSame() instead of assertEquals(), because both values are scalars of the same type',
)->identifier('phpunit.assertEquals')->build(),
];
}

return [];
}

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

namespace PHPStan\Rules\PHPUnit;

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<AssertEqualsIsDiscouragedRule>
*/
final class AssertEqualsIsDiscouragedRuleTest extends RuleTestCase
{

private const ERROR_MESSAGE = 'You should use assertSame() instead of assertEquals(), because both values are scalars of the same type';

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/assert-equals-is-discouraged.php'], [
[self::ERROR_MESSAGE, 19],
[self::ERROR_MESSAGE, 22],
[self::ERROR_MESSAGE, 23],
[self::ERROR_MESSAGE, 24],
[self::ERROR_MESSAGE, 25],
[self::ERROR_MESSAGE, 26],
[self::ERROR_MESSAGE, 27],
[self::ERROR_MESSAGE, 28],
[self::ERROR_MESSAGE, 29],
[self::ERROR_MESSAGE, 30],
[self::ERROR_MESSAGE, 32],
]);
}

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

}
37 changes: 37 additions & 0 deletions tests/Rules/PHPUnit/data/assert-equals-is-discouraged.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace SameOverEqualsTest;

use Exception;
use PHPUnit\Framework\TestCase;

final class AssertSameOverAssertEqualsRule extends TestCase
{
public function dummyTest(string $string, int $integer, bool $boolean, float $float, ?string $nullableString): void
{
$null = null;

$this->assertSame(5, $integer);
static::assertSame(5, $integer);

$this->assertEquals('', $string);
$this->assertEquals(null, $string);
static::assertEquals(null, $string);
static::assertEquals($nullableString, $string);
$this->assertEquals(2, $integer);
$this->assertEquals(2.2, $float);
static::assertEquals((int) '2', (int) $string);
$this->assertEquals(true, $boolean);
$this->assertEquals($string, $string);
$this->assertEquals($integer, $integer);
$this->assertEquals($boolean, $boolean);
$this->assertEquals($float, $float);
$this->assertEquals($null, $null);
$this->assertEquals((string) new Exception(), (string) new Exception());
$this->assertEquals([], []);
$this->assertEquals(new Exception(), new Exception());
static::assertEquals(new Exception(), new Exception());
}
}

0 comments on commit 10880da

Please sign in to comment.