diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 07de108..65f351a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -101,6 +101,7 @@ jobs: - "^9.5" - "^10.5" - "^11.5" + - "^12.0.9" exclude: - php-version: "7.4" phpunit-version: "^10.5" @@ -112,6 +113,14 @@ jobs: phpunit-version: "^11.5" - php-version: "8.1" phpunit-version: "^11.5" + - php-version: "7.4" + phpunit-version: "^12.0.9" + - php-version: "8.0" + phpunit-version: "^12.0.9" + - php-version: "8.1" + phpunit-version: "^12.0.9" + - php-version: "8.2" + phpunit-version: "^12.0.9" steps: - name: "Checkout" @@ -158,6 +167,7 @@ jobs: - "^9.5" - "^10.5" - "^11.5" + - "^12.0.9" exclude: - php-version: "7.4" phpunit-version: "^10.5" @@ -169,6 +179,14 @@ jobs: phpunit-version: "^11.5" - php-version: "8.1" phpunit-version: "^11.5" + - php-version: "7.4" + phpunit-version: "^12.0.9" + - php-version: "8.0" + phpunit-version: "^12.0.9" + - php-version: "8.1" + phpunit-version: "^12.0.9" + - php-version: "8.2" + phpunit-version: "^12.0.9" steps: - name: "Checkout" diff --git a/extension.neon b/extension.neon index 8de21f5..117c992 100644 --- a/extension.neon +++ b/extension.neon @@ -12,7 +12,6 @@ parameters: - stubs/Assert.stub - stubs/AssertionFailedError.stub - stubs/ExpectationFailedException.stub - - stubs/InvocationMocker.stub - stubs/MockBuilder.stub - stubs/MockObject.stub - stubs/Stub.stub @@ -42,18 +41,10 @@ services: class: PHPStan\Type\PHPUnit\Assert\AssertStaticMethodTypeSpecifyingExtension tags: - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension - - - class: PHPStan\Type\PHPUnit\InvocationMockerDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - class: PHPStan\Type\PHPUnit\MockBuilderDynamicReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - - class: PHPStan\Type\PHPUnit\MockObjectDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - class: PHPStan\Rules\PHPUnit\CoversHelper - diff --git a/phpstan.neon b/phpstan.neon index 7c96296..5737945 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,3 +9,8 @@ includes: parameters: excludePaths: - tests/*/data/* + ignoreErrors: + - + message: '#^Attribute class PHPUnit\\Framework\\Attributes\\DataProvider does not exist\.$#' + identifier: attribute.notFound + reportUnmatched: false diff --git a/src/Rules/PHPUnit/MockMethodCallRule.php b/src/Rules/PHPUnit/MockMethodCallRule.php index b6f8932..e953d18 100644 --- a/src/Rules/PHPUnit/MockMethodCallRule.php +++ b/src/Rules/PHPUnit/MockMethodCallRule.php @@ -5,9 +5,10 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPUnit\Framework\MockObject\Builder\InvocationMocker; +use PHPStan\Type\Type; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; use function array_filter; @@ -47,44 +48,58 @@ public function processNode(Node $node, Scope $scope): array $method = $constantString->getValue(); $type = $scope->getType($node->var); - if ( - ( - in_array(MockObject::class, $type->getObjectClassNames(), true) - || in_array(Stub::class, $type->getObjectClassNames(), true) - ) - && !$type->hasMethod($method)->yes() - ) { - $mockClasses = array_filter($type->getObjectClassNames(), static fn (string $class): bool => $class !== MockObject::class && $class !== Stub::class); - if (count($mockClasses) === 0) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf( - 'Trying to mock an undefined method %s() on class %s.', - $method, - implode('&', $mockClasses), - ))->identifier('phpunit.mockMethod')->build(); + $error = $this->checkCallOnType($type, $method); + if ($error !== null) { + $errors[] = $error; continue; } - $mockedClassObject = $type->getTemplateType(InvocationMocker::class, 'TMockedClass'); - if ($mockedClassObject->hasMethod($method)->yes()) { + if (!$node->var instanceof MethodCall) { continue; } - $classNames = $mockedClassObject->getObjectClassNames(); - if (count($classNames) === 0) { + if (!$node->var->name instanceof Node\Identifier) { continue; } - $errors[] = RuleErrorBuilder::message(sprintf( + if ($node->var->name->toLowerString() !== 'expects') { + continue; + } + + $varType = $scope->getType($node->var->var); + $error = $this->checkCallOnType($varType, $method); + if ($error === null) { + continue; + } + + $errors[] = $error; + } + + return $errors; + } + + private function checkCallOnType(Type $type, string $method): ?IdentifierRuleError + { + if ( + ( + in_array(MockObject::class, $type->getObjectClassNames(), true) + || in_array(Stub::class, $type->getObjectClassNames(), true) + ) + && !$type->hasMethod($method)->yes() + ) { + $mockClasses = array_filter($type->getObjectClassNames(), static fn (string $class): bool => $class !== MockObject::class && $class !== Stub::class); + if (count($mockClasses) === 0) { + return null; + } + + return RuleErrorBuilder::message(sprintf( 'Trying to mock an undefined method %s() on class %s.', $method, - implode('|', $classNames), + implode('&', $mockClasses), ))->identifier('phpunit.mockMethod')->build(); } - return $errors; + return null; } } diff --git a/src/Type/PHPUnit/InvocationMockerDynamicReturnTypeExtension.php b/src/Type/PHPUnit/InvocationMockerDynamicReturnTypeExtension.php deleted file mode 100644 index 44764f6..0000000 --- a/src/Type/PHPUnit/InvocationMockerDynamicReturnTypeExtension.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php declare(strict_types = 1); - -namespace PHPStan\Type\PHPUnit; - -use PhpParser\Node\Expr\MethodCall; -use PHPStan\Analyser\Scope; -use PHPStan\Reflection\MethodReflection; -use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\Type; -use PHPUnit\Framework\MockObject\Builder\InvocationMocker; - -class InvocationMockerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension -{ - - public function getClass(): string - { - return InvocationMocker::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() !== 'getMatcher'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - return $scope->getType($methodCall->var); - } - -} diff --git a/src/Type/PHPUnit/MockObjectDynamicReturnTypeExtension.php b/src/Type/PHPUnit/MockObjectDynamicReturnTypeExtension.php deleted file mode 100644 index 626f168..0000000 --- a/src/Type/PHPUnit/MockObjectDynamicReturnTypeExtension.php +++ /dev/null @@ -1,43 +0,0 @@ -<?php declare(strict_types = 1); - -namespace PHPStan\Type\PHPUnit; - -use PhpParser\Node\Expr\MethodCall; -use PHPStan\Analyser\Scope; -use PHPStan\Reflection\MethodReflection; -use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\Generic\GenericObjectType; -use PHPStan\Type\ObjectType; -use PHPStan\Type\Type; -use PHPUnit\Framework\MockObject\Builder\InvocationMocker; -use PHPUnit\Framework\MockObject\MockObject; -use function array_filter; -use function array_values; -use function count; - -class MockObjectDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension -{ - - public function getClass(): string - { - return MockObject::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === 'expects'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - $type = $scope->getType($methodCall->var); - $mockClasses = array_values(array_filter($type->getObjectClassNames(), static fn (string $class): bool => $class !== MockObject::class)); - - if (count($mockClasses) !== 1) { - return new ObjectType(InvocationMocker::class); - } - - return new GenericObjectType(InvocationMocker::class, [new ObjectType($mockClasses[0])]); - } - -} diff --git a/stubs/InvocationMocker.stub b/stubs/InvocationMocker.stub deleted file mode 100644 index c58719f..0000000 --- a/stubs/InvocationMocker.stub +++ /dev/null @@ -1,13 +0,0 @@ -<?php - -namespace PHPUnit\Framework\MockObject\Builder; - -use PHPUnit\Framework\MockObject\Stub; - -/** - * @template TMockedClass - */ -class InvocationMocker -{ - -} diff --git a/tests/Rules/PHPUnit/MockMethodCallRuleTest.php b/tests/Rules/PHPUnit/MockMethodCallRuleTest.php index c9c33e6..a7f7308 100644 --- a/tests/Rules/PHPUnit/MockMethodCallRuleTest.php +++ b/tests/Rules/PHPUnit/MockMethodCallRuleTest.php @@ -4,7 +4,6 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; -use function interface_exists; /** * @extends RuleTestCase<MockMethodCallRule> @@ -28,14 +27,11 @@ public function testRule(): void 'Trying to mock an undefined method doBadThing() on class MockMethodCall\Bar.', 20, ], - ]; - - if (interface_exists('PHPUnit\Framework\MockObject\Builder\InvocationStubber')) { - $expectedErrors[] = [ + [ 'Trying to mock an undefined method doBadThing() on class MockMethodCall\Bar.', 36, - ]; - } + ], + ]; $this->analyse([__DIR__ . '/data/mock-method-call.php'], $expectedErrors); } diff --git a/tests/Type/PHPUnit/AssertFunctionTypeSpecifyingExtensionTest.php b/tests/Type/PHPUnit/AssertFunctionTypeSpecifyingExtensionTest.php index 4405c4d..4014017 100644 --- a/tests/Type/PHPUnit/AssertFunctionTypeSpecifyingExtensionTest.php +++ b/tests/Type/PHPUnit/AssertFunctionTypeSpecifyingExtensionTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\PHPUnit; use PHPStan\Testing\TypeInferenceTestCase; +use PHPUnit\Framework\Attributes\DataProvider; use function function_exists; class AssertFunctionTypeSpecifyingExtensionTest extends TypeInferenceTestCase @@ -26,6 +27,7 @@ public static function dataFileAsserts(): iterable * @dataProvider dataFileAsserts * @param mixed ...$args */ + #[DataProvider('dataFileAsserts')] public function testFileAsserts( string $assertType, string $file, diff --git a/tests/Type/PHPUnit/AssertMethodTypeSpecifyingExtensionTest.php b/tests/Type/PHPUnit/AssertMethodTypeSpecifyingExtensionTest.php index 0b36c2b..8c6ebb8 100644 --- a/tests/Type/PHPUnit/AssertMethodTypeSpecifyingExtensionTest.php +++ b/tests/Type/PHPUnit/AssertMethodTypeSpecifyingExtensionTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\PHPUnit; use PHPStan\Testing\TypeInferenceTestCase; +use PHPUnit\Framework\Attributes\DataProvider; class AssertMethodTypeSpecifyingExtensionTest extends TypeInferenceTestCase { @@ -17,6 +18,7 @@ public static function dataFileAsserts(): iterable * @dataProvider dataFileAsserts * @param mixed ...$args */ + #[DataProvider('dataFileAsserts')] public function testFileAsserts( string $assertType, string $file,