| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,14 +2,21 @@ | |||
| 2 | 2 | ||
| 3 | 3 | namespace PHPStan\Rules\PHPUnit; | |
| 4 | 4 | ||
| 5 | + use Countable; | ||
| 5 | 6 | use PhpParser\Node; | |
| 6 | 7 | use PhpParser\Node\Expr\CallLike; | |
| 7 | 8 | use PhpParser\Node\Expr\MethodCall; | |
| 8 | 9 | use PhpParser\Node\Expr\StaticCall; | |
| 9 | 10 | use PhpParser\Node\Identifier; | |
| 11 | + use PhpParser\Node\Scalar\Int_; | ||
| 12 | + use PhpParser\Node\Scalar\String_; | ||
| 10 | 13 | use PHPStan\Analyser\Scope; | |
| 11 | 14 | use PHPStan\Rules\Rule; | |
| 12 | 15 | use PHPStan\Rules\RuleErrorBuilder; | |
| 16 | + use PHPStan\Type\Type; | ||
| 17 | + use PHPStan\Type\TypeCombinator; | ||
| 18 | + use PHPStan\Type\UnionType; | ||
| 19 | + use function array_merge; | ||
| 13 | 20 | use function count; | |
| 14 | 21 | use function in_array; | |
| 15 | 22 | use function sprintf; | |
@@ -43,11 +50,83 @@ public function processNode(Node $node, Scope $scope): array | |||
| 43 | 50 | return []; | |
| 44 | 51 | } | |
| 45 | 52 | ||
| 53 | + $errorBuilder = RuleErrorBuilder::message(sprintf('%s() is not allowed. Use more strict assertion.', $node->name->toString())) | ||
| 54 | + ->identifier('phpunit.assertEmpty'); | ||
| 55 | + | ||
| 56 | + if (AssertRuleHelper::hasNamedOrUnpackedArguments($node)) { | ||
| 57 | + return [$errorBuilder->build()]; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + $replacement = $this->getReplacement($scope->getNativeType($node->getArgs()[0]->value), $node->name->toLowerString() === 'assertnotempty'); | ||
| 61 | + if ($replacement !== null) { | ||
| 62 | + [$correctName, $expectedValue] = $replacement; | ||
| 63 | + $errorBuilder->fixNode($node, static function (CallLike $node) use ($correctName, $expectedValue) { | ||
| 64 | + $node->name = new Identifier($correctName); | ||
| 65 | + if ($expectedValue !== null) { | ||
| 66 | + $node->args = array_merge([new Node\Arg($expectedValue)], $node->args); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + return $node; | ||
| 70 | + }); | ||
| 71 | + } | ||
| 72 | + | ||
| 46 | 73 | return [ | |
| 47 | - RuleErrorBuilder::message(sprintf('%s() is not allowed. Use more strict assertion.', $node->name->toString())) | ||
| 48 | - ->identifier('phpunit.assertEmpty') | ||
| 49 | - ->build(), | ||
| 74 | + $errorBuilder->build(), | ||
| 50 | 75 | ]; | |
| 51 | 76 | } | |
| 52 | 77 | ||
| 78 | + /** | ||
| 79 | + * @return array{string, Node\Expr|null}|null | ||
| 80 | + */ | ||
| 81 | + private function getReplacement(Type $type, bool $negated): ?array | ||
| 82 | + { | ||
| 83 | + if ($type instanceof UnionType) { | ||
| 84 | + if (TypeCombinator::containsNull($type)) { | ||
| 85 | + $typeWithoutNull = TypeCombinator::removeNull($type); | ||
| 86 | + | ||
| 87 | + $classReflections = $typeWithoutNull->getObjectClassReflections(); | ||
| 88 | + if (count($classReflections) === 0) { | ||
| 89 | + return null; | ||
| 90 | + } | ||
| 91 | + foreach ($classReflections as $classReflection) { | ||
| 92 | + if ( | ||
| 93 | + $classReflection->isBuiltin() | ||
| 94 | + || !$classReflection->isFinalByKeyword() | ||
| 95 | + || $classReflection->implementsInterface(Countable::class) | ||
| 96 | + ) { | ||
| 97 | + return null; | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + $parentClass = $classReflection->getParentClass(); | ||
| 101 | + while ($parentClass !== null) { | ||
| 102 | + // Builtin parents can define different empty() semantics. | ||
| 103 | + if ($parentClass->isBuiltin()) { | ||
| 104 | + return null; | ||
| 105 | + } | ||
| 106 | + $parentClass = $parentClass->getParentClass(); | ||
| 107 | + } | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + return [$negated ? 'assertNotNull' : 'assertNull', null]; | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + return null; | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + if ($type->isBoolean()->yes()) { | ||
| 117 | + return [$negated ? 'assertTrue' : 'assertFalse', null]; | ||
| 118 | + } | ||
| 119 | + if ($type->isArray()->yes()) { | ||
| 120 | + return [$negated ? 'assertNotCount' : 'assertCount', new Int_(0)]; | ||
| 121 | + } | ||
| 122 | + if ($type->isInteger()->yes()) { | ||
| 123 | + return [$negated ? 'assertNotSame' : 'assertSame', new Int_(0)]; | ||
| 124 | + } | ||
| 125 | + if ($type->isNonFalsyString()->yes()) { | ||
| 126 | + return [$negated ? 'assertNotSame' : 'assertSame', new String_('')]; | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + return null; | ||
| 130 | + } | ||
| 131 | + | ||
| 53 | 132 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | 5 | use PHPStan\Rules\Rule; | |
| 6 | 6 | use PHPStan\Testing\RuleTestCase; | |
| 7 | + use const PHP_VERSION_ID; | ||
| 7 | 8 | ||
| 8 | 9 | /** | |
| 9 | 10 | * @extends RuleTestCase<AssertEmptyIsDiscouragedRule> | |
@@ -21,6 +22,20 @@ public function testRule(): void | |||
| 21 | 22 | ]); | |
| 22 | 23 | } | |
| 23 | 24 | ||
| 25 | + public function testFix(): void | ||
| 26 | + { | ||
| 27 | + $this->fix(__DIR__ . '/data/assert-empty-is-discouraged-fixable.php', __DIR__ . '/data/assert-empty-is-discouraged-fixable.php.fixed'); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public function testNativeUnionTypeIsNotFixed(): void | ||
| 31 | + { | ||
| 32 | + if (PHP_VERSION_ID < 80000) { | ||
| 33 | + self::markTestSkipped('Native union types require PHP 8.0.'); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + $this->fix(__DIR__ . '/data/assert-empty-is-discouraged-native-union.php', __DIR__ . '/data/assert-empty-is-discouraged-native-union.php.fixed'); | ||
| 37 | + } | ||
| 38 | + | ||
| 24 | 39 | protected function getRule(): Rule | |
| 25 | 40 | { | |
| 26 | 41 | return new AssertEmptyIsDiscouragedRule(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,143 @@ | |||
| 1 | + <?php declare(strict_types = 1); | ||
| 2 | + | ||
| 3 | + namespace AssertEmptyIsDiscouragedFixTest; | ||
| 4 | + | ||
| 5 | + use PHPUnit\Framework\Assert; | ||
| 6 | + use PHPUnit\Framework\TestCase; | ||
| 7 | + | ||
| 8 | + final class UserDefinedObject | ||
| 9 | + { | ||
| 10 | + | ||
| 11 | + } | ||
| 12 | + | ||
| 13 | + /** @final */ | ||
| 14 | + class PhpDocFinalObject | ||
| 15 | + { | ||
| 16 | + | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + class UserDefinedCountable implements \Countable | ||
| 20 | + { | ||
| 21 | + | ||
| 22 | + public function count(): int | ||
| 23 | + { | ||
| 24 | + return 0; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + class OpenParent | ||
| 30 | + { | ||
| 31 | + | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + interface Thing | ||
| 35 | + { | ||
| 36 | + | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + class ThingImplementation implements Thing, \Countable | ||
| 40 | + { | ||
| 41 | + | ||
| 42 | + public function count(): int | ||
| 43 | + { | ||
| 44 | + return 0; | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + final class FinalEmptyIterator extends \EmptyIterator | ||
| 50 | + { | ||
| 51 | + | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + final class FinalCountable implements \Countable | ||
| 55 | + { | ||
| 56 | + | ||
| 57 | + public function count(): int | ||
| 58 | + { | ||
| 59 | + return 0; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + final class AssertEmptyTest extends TestCase | ||
| 65 | + { | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * @param non-falsy-string $nonFalsyString | ||
| 69 | + * @param non-falsy-string $otherNonFalsyString | ||
| 70 | + * @param int $phpDocInteger | ||
| 71 | + * @param string|int $union | ||
| 72 | + */ | ||
| 73 | + public function test( | ||
| 74 | + array $arguments, | ||
| 75 | + bool $boolean, | ||
| 76 | + array $array, | ||
| 77 | + int $integer, | ||
| 78 | + float $float, | ||
| 79 | + string $string, | ||
| 80 | + $union, | ||
| 81 | + string $nonFalsyString, | ||
| 82 | + string $otherNonFalsyString, | ||
| 83 | + ?\stdClass $nullableObject, | ||
| 84 | + ?object $otherNullableObject, | ||
| 85 | + ?\SimpleXMLElement $nullableSimpleXml, | ||
| 86 | + ?UserDefinedObject $nullableUserDefinedObject, | ||
| 87 | + ?UserDefinedObject $otherNullableUserDefinedObject, | ||
| 88 | + ?PhpDocFinalObject $nullablePhpDocFinalObject, | ||
| 89 | + ?UserDefinedCountable $nullableCountable, | ||
| 90 | + ?OpenParent $nullableOpenParent, | ||
| 91 | + ?Thing $nullableThing, | ||
| 92 | + ?FinalEmptyIterator $nullableEmptyIterator, | ||
| 93 | + ?FinalCountable $nullableFinalCountable, | ||
| 94 | + $phpDocInteger, | ||
| 95 | + $mixed | ||
| 96 | + ): void | ||
| 97 | + { | ||
| 98 | + $this->assertEmpty(...$arguments); | ||
| 99 | + $this->assertEmpty($boolean); | ||
| 100 | + $this->assertNotEmpty($boolean); | ||
| 101 | + $this->assertEmpty($array, 'message'); | ||
| 102 | + $this->assertNotEmpty($array); | ||
| 103 | + $this->assertEmpty($integer); | ||
| 104 | + $this->assertNotEmpty($integer); | ||
| 105 | + Assert::assertEmpty($float); | ||
| 106 | + Assert::assertNotEmpty($float); | ||
| 107 | + $this->assertEmpty($nonFalsyString); | ||
| 108 | + $this->assertNotEmpty($otherNonFalsyString); | ||
| 109 | + $this->assertEmpty($string); | ||
| 110 | + $this->assertNotEmpty($string); | ||
| 111 | + $this->assertEmpty($union); | ||
| 112 | + $this->assertNotEmpty($union); | ||
| 113 | + $this->assertEmpty($nullableObject); | ||
| 114 | + $this->assertNotEmpty($otherNullableObject); | ||
| 115 | + $this->assertEmpty($nullableSimpleXml); | ||
| 116 | + $this->assertEmpty($nullableUserDefinedObject); | ||
| 117 | + $this->assertNotEmpty($otherNullableUserDefinedObject); | ||
| 118 | + $this->assertEmpty($nullablePhpDocFinalObject); | ||
| 119 | + $this->assertNotEmpty($nullablePhpDocFinalObject); | ||
| 120 | + $this->assertEmpty($nullableCountable); | ||
| 121 | + $this->assertNotEmpty($nullableCountable); | ||
| 122 | + $this->assertEmpty($nullableOpenParent); | ||
| 123 | + $this->assertNotEmpty($nullableOpenParent); | ||
| 124 | + $this->assertEmpty($nullableThing); | ||
| 125 | + $this->assertNotEmpty($nullableThing); | ||
| 126 | + $this->assertEmpty($nullableEmptyIterator); | ||
| 127 | + $this->assertNotEmpty($nullableEmptyIterator); | ||
| 128 | + $this->assertEmpty($nullableFinalCountable); | ||
| 129 | + $this->assertNotEmpty($nullableFinalCountable); | ||
| 130 | + $this->assertNotEmpty($phpDocInteger); | ||
| 131 | + $this->assertEmpty($mixed); | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + public function testNativeNonFalsyString(string $value): void | ||
| 135 | + { | ||
| 136 | + if ($value === '' || $value === '0') { | ||
| 137 | + return; | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + $this->assertNotEmpty($value); | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments