From 433dacf3491ada5c7932eafb2f04db85457737e7 Mon Sep 17 00:00:00 2001 From: Martin Hujer Date: Thu, 21 Dec 2017 17:20:26 +0100 Subject: [PATCH] AssertEqualsIsDiscouragedRule --- README.md | 1 + .../PHPUnit/AssertEqualsIsDiscouragedRule.php | 92 +++++++++++++++++++ strictRules.neon | 4 + .../AssertEqualsIsDiscouragedRuleTest.php | 53 +++++++++++ .../data/assert-equals-is-discouraged.php | 34 +++++++ 5 files changed, 184 insertions(+) create mode 100644 src/Rules/PHPUnit/AssertEqualsIsDiscouragedRule.php create mode 100644 tests/Rules/PHPUnit/AssertEqualsIsDiscouragedRuleTest.php create mode 100644 tests/Rules/PHPUnit/data/assert-equals-is-discouraged.php diff --git a/README.md b/README.md index e73c901..e908206 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,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) or without explanatory comment above. ## How to document mock objects in phpDocs? diff --git a/src/Rules/PHPUnit/AssertEqualsIsDiscouragedRule.php b/src/Rules/PHPUnit/AssertEqualsIsDiscouragedRule.php new file mode 100644 index 0000000..ba38f7d --- /dev/null +++ b/src/Rules/PHPUnit/AssertEqualsIsDiscouragedRule.php @@ -0,0 +1,92 @@ +args) < 2) { + return []; + } + if (!is_string($node->name) || strtolower($node->name) !== 'assertequals') { + return []; + } + + $leftType = $scope->getType($node->args[0]->value); + $rightType = $scope->getType($node->args[1]->value); + + if ( + ($leftType instanceof BooleanType && $rightType instanceof BooleanType) + || ($leftType instanceof IntegerType && $rightType instanceof IntegerType) + || ($leftType instanceof StringType && $rightType instanceof StringType) + ) { + $typeDescription = $leftType->describe(); + if ($leftType instanceof BooleanType) { + $typeDescription = 'bool'; + } + return [ + sprintf( + 'You should use assertSame instead of assertEquals, because both values are of the same type "%s"', + $typeDescription + ), + ]; + } + if ( + ($leftType instanceof FloatType && $rightType instanceof FloatType) + && count($node->args) < 4 // is not using delta for comparing floats + ) { + return [ + 'You should use assertSame instead of assertEquals, because both values are of the same type "float" and you are not using $delta argument', + ]; + } + + if (!$node->hasAttribute('comments')) { + return [ + 'You should use assertSame instead of assertEquals. Or it should have a comment above with explanation: // assertEquals because ...', + ]; + } + + /** @var \PhpParser\Comment[] $comments */ + $comments = $node->getAttribute('comments'); + $comment = $comments[count($comments) - 1]; + + // the comment should be on the line above the assertEquals() + if ($comment->getLine() !== ($node->getLine() - 1)) { + return [ + 'You should use assertSame instead of assertEquals. Or it should have a comment above with explanation: // assertEquals because ... (The comment is not directly above the assertEquals)', + ]; + } + + if (!preg_match('~^//\s+assertEquals because(.*)~', $comment->getReformattedText())) { + return [ + 'You should use assertSame instead of assertEquals. Or it should have a comment above with explanation: // assertEquals because ... (There is a different comment)', + ]; + } + + return []; + } + +} diff --git a/strictRules.neon b/strictRules.neon index db0671e..6e7eee4 100644 --- a/strictRules.neon +++ b/strictRules.neon @@ -11,3 +11,7 @@ services: class: PHPStan\Rules\PHPUnit\AssertSameWithCountRule tags: - phpstan.rules.rule + - + class: PHPStan\Rules\PHPUnit\AssertEqualsIsDiscouragedRule + tags: + - phpstan.rules.rule diff --git a/tests/Rules/PHPUnit/AssertEqualsIsDiscouragedRuleTest.php b/tests/Rules/PHPUnit/AssertEqualsIsDiscouragedRuleTest.php new file mode 100644 index 0000000..4042b56 --- /dev/null +++ b/tests/Rules/PHPUnit/AssertEqualsIsDiscouragedRuleTest.php @@ -0,0 +1,53 @@ +analyse([__DIR__ . '/data/assert-equals-is-discouraged.php'], [ + [ + 'You should use assertSame instead of assertEquals, because both values are of the same type "string"', + 11, + ], + [ + 'You should use assertSame instead of assertEquals, because both values are of the same type "int"', + 12, + ], + [ + 'You should use assertSame instead of assertEquals, because both values are of the same type "bool"', + 13, + ], + [ + 'You should use assertSame instead of assertEquals, because both values are of the same type "float" and you are not using $delta argument', + 16, + ], + [ + 'You should use assertSame instead of assertEquals. Or it should have a comment above with explanation: // assertEquals because ... (There is a different comment)', + 19, + ], + [ + 'You should use assertSame instead of assertEquals. Or it should have a comment above with explanation: // assertEquals because ...', + 21, + ], + [ + 'You should use assertSame instead of assertEquals. Or it should have a comment above with explanation: // assertEquals because ... (There is a different comment)', + 24, + ], + [ + 'You should use assertSame instead of assertEquals. Or it should have a comment above with explanation: // assertEquals because ... (The comment is not directly above the assertEquals)', + 28, + ], + ]); + } + +} diff --git a/tests/Rules/PHPUnit/data/assert-equals-is-discouraged.php b/tests/Rules/PHPUnit/data/assert-equals-is-discouraged.php new file mode 100644 index 0000000..18c02c1 --- /dev/null +++ b/tests/Rules/PHPUnit/data/assert-equals-is-discouraged.php @@ -0,0 +1,34 @@ +assertEquals('a', 'b'); + $this->assertEquals(1, 2); + $this->assertEquals(true, false); + + // comparing floats without delta + $this->assertEquals(1.0, 2.0); + + // comparing floats with delta + $this->assertEquals(1.0, 2.0, '', 0.01); + + $this->assertEquals(1, '1'); // assertEquals without comment on previous line + + // with incorrect comment + $this->assertEquals(1, '1'); + + // assertEquals because I want it! But sadly, the comment is not just above the assert. + + $this->assertEquals(1, '1'); + + // assertEquals because I want it! + $this->assertEquals(1, '1'); + } + +}