-
Notifications
You must be signed in to change notification settings - Fork 57
Add support for createMockForIntersectionOfInterfaces #272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
bd1a5b1
0a159e2
fa3fa4b
78e8820
bcaf46c
c88dbd0
a4578dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| <?php declare(strict_types = 1); | ||
|
|
||
| namespace PHPStan\Type\PHPUnit; | ||
|
|
||
| use PhpParser\Node\Arg; | ||
| use PhpParser\Node\Expr\MethodCall; | ||
| use PhpParser\Node\Expr\StaticCall; | ||
| use PHPStan\Analyser\Scope; | ||
| use PHPStan\Reflection\MethodReflection; | ||
| use PHPStan\Type\DynamicMethodReturnTypeExtension; | ||
| use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; | ||
| use PHPStan\Type\ObjectType; | ||
| use PHPStan\Type\Type; | ||
| use PHPStan\Type\TypeCombinator; | ||
| use PHPUnit\Framework\MockObject\MockObject; | ||
| use PHPUnit\Framework\MockObject\Stub; | ||
| use PHPUnit\Framework\TestCase; | ||
| use function count; | ||
|
|
||
| class MockForIntersectionDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension, DynamicStaticMethodReturnTypeExtension | ||
| { | ||
|
|
||
| public function getClass(): string | ||
| { | ||
| return TestCase::class; | ||
| } | ||
|
|
||
| public function isMethodSupported(MethodReflection $methodReflection): bool | ||
| { | ||
| return $methodReflection->getName() === 'createMockForIntersectionOfInterfaces'; | ||
| } | ||
|
|
||
| public function isStaticMethodSupported(MethodReflection $methodReflection): bool | ||
| { | ||
| return $methodReflection->getName() === 'createStubForIntersectionOfInterfaces'; | ||
| } | ||
|
|
||
| public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type | ||
| { | ||
| return $this->getTypeFromCall($methodReflection, $methodCall->getArgs(), $scope); | ||
| } | ||
|
|
||
| public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type | ||
| { | ||
| return $this->getTypeFromCall($methodReflection, $methodCall->getArgs(), $scope); | ||
| } | ||
|
|
||
| /** | ||
| * @param array<Arg> $args | ||
| */ | ||
| private function getTypeFromCall(MethodReflection $methodReflection, array $args, Scope $scope): ?Type | ||
| { | ||
| if (!isset($args[0])) { | ||
| return null; | ||
| } | ||
|
|
||
| $interfaces = $scope->getType($args[0]->value); | ||
| $constantArrays = $interfaces->getConstantArrays(); | ||
| if (count($constantArrays) !== 1) { | ||
| return null; | ||
| } | ||
|
|
||
| $constantArray = $constantArrays[0]; | ||
| if (count($constantArray->getOptionalKeys()) > 0) { | ||
| return null; | ||
| } | ||
|
|
||
| $result = []; | ||
| if ($methodReflection->getName() === 'createMockForIntersectionOfInterfaces') { | ||
| $result[] = new ObjectType(MockObject::class); | ||
| } else { | ||
| $result[] = new ObjectType(Stub::class); | ||
| } | ||
|
|
||
| foreach ($constantArray->getValueTypes() as $valueType) { | ||
| if (!$valueType->isClassString()->yes()) { | ||
| return null; | ||
| } | ||
|
|
||
| $values = $valueType->getConstantScalarValues(); | ||
| if (count($values) !== 1) { | ||
| return null; | ||
| } | ||
|
|
||
| $result[] = new ObjectType((string) $values[0]); | ||
|
VincentLanglet marked this conversation as resolved.
|
||
| } | ||
|
|
||
| return TypeCombinator::intersect(...$result); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,8 @@ | |||||
|
|
||||||
| use PHPStan\Rules\Rule; | ||||||
| use PHPStan\Testing\RuleTestCase; | ||||||
| use PHPUnit\Framework\TestCase; | ||||||
| use function method_exists; | ||||||
| use const PHP_VERSION_ID; | ||||||
|
|
||||||
| /** | ||||||
|
|
@@ -34,6 +36,17 @@ public function testRule(): void | |||||
| ], | ||||||
| ]; | ||||||
|
|
||||||
| if (method_exists(TestCase::class, 'createMockForIntersectionOfInterfaces')) { // @phpstan-ignore-line function.alreadyNarrowedType | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kinda curious about this feedback. Isn't I understand it as Are phpstan-ignore-line and phpstan-ignore the same ?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my understanding is
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exactly. I suspect people do this mistake often but I don't know how to prevent it.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't know that, and made the mistake a lot then Especially because I don't like adding
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as far as I remember there is a open feature request (cannot find it right now), which goal is to disallow
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alternatively we could look at |
||||||
| $expectedErrors[] = [ | ||||||
| 'Trying to mock an undefined method bazMethod() on class MockMethodCall\FooInterface&MockMethodCall\BarInterface.', | ||||||
| 49, | ||||||
| ]; | ||||||
| $expectedErrors[] = [ | ||||||
| 'Trying to mock an undefined method bazMethod() on class MockMethodCall\FooInterface&MockMethodCall\BarInterface.', | ||||||
| 57, | ||||||
| ]; | ||||||
| } | ||||||
|
|
||||||
| $this->analyse([__DIR__ . '/data/mock-method-call.php'], $expectedErrors); | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| <?php declare(strict_types = 1); | ||
|
|
||
| namespace PHPStan\Type\PHPUnit; | ||
|
|
||
| use PHPStan\Testing\TypeInferenceTestCase; | ||
| use PHPUnit\Framework\Attributes\DataProvider; | ||
| use PHPUnit\Framework\TestCase; | ||
| use function method_exists; | ||
|
|
||
| class MockForIntersectionDynamicReturnTypeExtensionTest extends TypeInferenceTestCase | ||
| { | ||
|
|
||
| /** @return mixed[] */ | ||
| public static function dataFileAsserts(): iterable | ||
| { | ||
| if (method_exists(TestCase::class, 'createMockForIntersectionOfInterfaces')) { // @phpstan-ignore-line function.alreadyNarrowedType | ||
|
VincentLanglet marked this conversation as resolved.
Outdated
|
||
| yield from self::gatherAssertTypes(__DIR__ . '/data/mock-for-intersection.php'); | ||
| } | ||
|
|
||
| return []; | ||
| } | ||
|
|
||
| /** | ||
| * @dataProvider dataFileAsserts | ||
| * @param mixed ...$args | ||
| */ | ||
| #[DataProvider('dataFileAsserts')] | ||
| public function testFileAsserts( | ||
| string $assertType, | ||
| string $file, | ||
| ...$args | ||
| ): void | ||
| { | ||
| $this->assertFileAsserts($assertType, $file, ...$args); | ||
| } | ||
|
|
||
| public static function getAdditionalConfigFiles(): array | ||
| { | ||
| return [__DIR__ . '/../../../extension.neon']; | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| <?php | ||
|
|
||
| namespace MockForIntersection; | ||
|
|
||
| use PHPUnit\Framework\TestCase; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| class Foo extends TestCase | ||
| { | ||
|
|
||
| public function testFoo(bool $bool): void | ||
| { | ||
| assertType( | ||
| 'MockForIntersection\BarInterface&MockForIntersection\FooInterface&PHPUnit\Framework\MockObject\MockObject', | ||
| $this->createMockForIntersectionOfInterfaces([FooInterface::class, BarInterface::class]), | ||
| ); | ||
| assertType( | ||
| 'MockForIntersection\BarInterface&MockForIntersection\FooInterface&PHPUnit\Framework\MockObject\Stub', | ||
| self::createStubForIntersectionOfInterfaces([FooInterface::class, BarInterface::class]), | ||
| ); | ||
|
|
||
|
|
||
| assertType( | ||
| 'PHPUnit\Framework\MockObject\MockObject', | ||
| $this->createMockForIntersectionOfInterfaces($bool ? [FooInterface::class, BarInterface::class] : [FooInterface::class]), | ||
| ); | ||
| assertType( | ||
| 'PHPUnit\Framework\MockObject\MockObject', | ||
| $this->createMockForIntersectionOfInterfaces($bool ? [FooInterface::class] : [BarInterface::class]), | ||
| ); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| interface FooInterface {} | ||
| interface BarInterface {} |
Uh oh!
There was an error while loading. Please reload this page.