From b8ce39dbecf085a64eced34581038332373e06f7 Mon Sep 17 00:00:00 2001 From: m0003r Date: Tue, 27 Apr 2021 23:37:16 +0300 Subject: [PATCH] Add tests --- src/Checker/PsrContainerChecker.php | 18 +- .../PsrContainerCheckerClassStringTest.php | 251 ++++++++++++++++++ 2 files changed, 255 insertions(+), 14 deletions(-) create mode 100644 test/Unit/Checker/PsrContainerCheckerClassStringTest.php diff --git a/src/Checker/PsrContainerChecker.php b/src/Checker/PsrContainerChecker.php index 5dfd8627..ca3f83ea 100644 --- a/src/Checker/PsrContainerChecker.php +++ b/src/Checker/PsrContainerChecker.php @@ -19,7 +19,6 @@ use Psalm\Type\Union; use Psr\Container\ContainerInterface; -use function count; use function explode; use function is_string; @@ -76,7 +75,7 @@ public static function afterMethodCallAnalysis( } $candidate = self::handleVariable($variableType); - if ($candidate !== null) { + if (! $candidate->isMixed()) { $return_type_candidate = $candidate; } @@ -96,9 +95,8 @@ public static function afterMethodCallAnalysis( } // phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps - protected static function handleVariable(Union $variableType): ?Union + private static function handleVariable(Union $variableType): Union { - $hasMixed = false; /** @var list $types */ $types = []; foreach ($variableType->getAtomicTypes() as $type) { @@ -111,18 +109,10 @@ protected static function handleVariable(Union $variableType): ?Union } elseif ($type instanceof TClassString && $type->as_type !== null) { $types[] = $type->as_type; } else { - if (! $hasMixed) { - $types[] = new Atomic\TMixed(); - } - - $hasMixed = true; + $types[] = new Atomic\TMixed(); } } - if (count($types) > 0) { - return new Union($types); - } - - return null; + return new Union($types); } } diff --git a/test/Unit/Checker/PsrContainerCheckerClassStringTest.php b/test/Unit/Checker/PsrContainerCheckerClassStringTest.php new file mode 100644 index 00000000..7744bea6 --- /dev/null +++ b/test/Unit/Checker/PsrContainerCheckerClassStringTest.php @@ -0,0 +1,251 @@ +getMethodCall(), + self::METHOD_ID, + self::METHOD_ID, + self::METHOD_ID, + $this->createStub(Context::class), + $this->createStub(StatementsSource::class), + $this->createStub(Codebase::class), + $fileReplacements, + $returnTypeCandidate + ); + + self::assertSame($baseReturnType, $returnTypeCandidate); + } + + /** + * @dataProvider pairsProvider + */ + public function testItSetsTheReturnTypeAsAUnionWithFetchedClass(Union $variableType, Union $expectedType): void + { + $fileReplacements = []; + $returnTypeCandidate = new Union([new TMixed()]); + + PsrContainerChecker::afterMethodCallAnalysis( + $this->getMethodCall(), + self::METHOD_ID, + self::METHOD_ID, + self::METHOD_ID, + $this->createContext($variableType), + $this->createStub(StatementsSource::class), + $this->createStub(Codebase::class), + $fileReplacements, + $returnTypeCandidate + ); + + self::assertNotNull($returnTypeCandidate); + self::assertTrue($expectedType->equals($returnTypeCandidate)); + + if (! $expectedType->hasTemplate()) { + return; + } + + // we should also check for template match + self::assertEquals($expectedType->getId(), $returnTypeCandidate->getId()); + } + + /** + * @dataProvider pairsProvider + */ + public function testItSetsTheReturnTypeAsAUnionWithFetchedClassWithContainerImplementingContainerInterface( + Union $variableType, + Union $expectedType + ): void { + $fileReplacements = []; + $returnTypeCandidate = new Union([new TMixed()]); + + $codebase = $this->prophesize(Codebase::class); + $codebase->classImplements(MyOtherContainer::class, ContainerInterface::class) + ->willReturn(true) + ->shouldBeCalledOnce(); + + PsrContainerChecker::afterMethodCallAnalysis( + $this->getMethodCall(), + MyOtherContainer::class . '::get', + MyOtherContainer::class . '::get', + MyOtherContainer::class . '::get', + $this->createContext($variableType), + $this->createStub(StatementsSource::class), + $codebase->reveal(), + $fileReplacements, + $returnTypeCandidate + ); + + self::assertNotNull($returnTypeCandidate); + self::assertTrue($expectedType->equals($returnTypeCandidate)); + + if (! $expectedType->hasTemplate()) { + return; + } + + // we should also check for template match + self::assertEquals($expectedType->getId(), $returnTypeCandidate->getId()); + } + + /** + * @return array + */ + public function pairsProvider(): array + { + return [ + 'mixed variable' => [ + new Union([new TMixed()]), + new Union([new TMixed()]), + ], + 'class string' => [ + new Union([ + new TClassString('object', new TNamedObject('Abracadabra')), + ]), + new Union([new TNamedObject('Abracadabra')]), + ], + 'class string and mixed' => [ + new Union([ + new TClassString('object', new TNamedObject('Abracadabra')), + new TMixed(), + ]), + new Union([ + new TNamedObject('Abracadabra'), + new TMixed(), + ]), + ], + 'templated class string without as_type' => [ + new Union([ + new TTemplateParamClass( + 'T', + 'object', + null, + 'definingclass' + ), + ]), + new Union([ + new TTemplateParam( + 'T', + new Union([new TObject()]), + 'definingclass' + ), + ]), + ], + 'templated class string with as_type' => [ + new Union([ + new TTemplateParamClass( + 'T', + 'Abracadabra', + new TNamedObject('Abracadabra'), + 'definingclass' + ), + ]), + new Union([ + new TTemplateParam( + 'T', + new Union([new TNamedObject('Abracadabra')]), + 'definingclass' + ), + ]), + ], + 'union of class string or templated class string' => [ + new Union([ + new TTemplateParamClass( + 'T', + 'object', + null, + 'definingclass' + ), + new TClassString('object', new TNamedObject('Abracadabra')), + ]), + new Union([ + new TTemplateParam( + 'T', + new Union([new TObject()]), + 'definingclass' + ), + new TNamedObject('Abracadabra'), + ]), + ], + ]; + } + + /** + * @return Stub&Context + */ + protected function createContext(Union $variableType) + { + $stub = $this->createStub(Context::class); + + // phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps + $stub->vars_in_scope['$' . self::VARIABLE_NAME] = $variableType; + + return $stub; + } + + protected function getMethodCall(): MethodCall + { + return new MethodCall( + new Variable('dummy'), + 'get', + [ + new Arg( + new Variable(self::VARIABLE_NAME) + ), + ] + ); + } +} + + +// phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses,Squiz.Classes.ClassFileName.NoMatch + +final class MyOtherContainer implements ContainerInterface +{ + /** + * @inheritDoc + */ + public function get($id) + { + return 'dummy'; + } + + /** + * @inheritDoc + */ + public function has($id) + { + return true; + } +}