diff --git a/composer.json b/composer.json index 484ea046..79e67f8e 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "phpstan/phpstan-strict-rules": "^0.12.5", "phpunit/phpunit": "^7.5.20", "symfony/console": "^4.0", + "symfony/config": "^4.2", "symfony/framework-bundle": "^4.0", "symfony/http-foundation": "^4.0", "symfony/messenger": "^4.2", diff --git a/extension.neon b/extension.neon index 4209bc61..819442f3 100644 --- a/extension.neon +++ b/extension.neon @@ -145,3 +145,13 @@ services: - factory: PHPStan\Type\Symfony\InputInterfaceHasOptionDynamicReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + + # new TreeBuilder() return type + - + factory: PHPStan\Type\Symfony\TreeBuilderDynamicReturnTypeExtension + tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension] + + # TreeBuilder::getRootNode() return type + - + factory: PHPStan\Type\Symfony\TreeBuilderGetRootNodeDynamicReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] diff --git a/src/Type/Symfony/TreeBuilderDynamicReturnTypeExtension.php b/src/Type/Symfony/TreeBuilderDynamicReturnTypeExtension.php new file mode 100644 index 00000000..56f29d35 --- /dev/null +++ b/src/Type/Symfony/TreeBuilderDynamicReturnTypeExtension.php @@ -0,0 +1,56 @@ + 'Symfony\Component\Config\Definition\Builder\VariableNodeDefinition', + 'scalar' => 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition', + 'boolean' => 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition', + 'integer' => 'Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition', + 'float' => 'Symfony\Component\Config\Definition\Builder\FloatNodeDefinition', + 'array' => 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition', + 'enum' => 'Symfony\Component\Config\Definition\Builder\EnumNodeDefinition', + ]; + + public function getClass(): string + { + return 'Symfony\Component\Config\Definition\Builder\TreeBuilder'; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === '__construct'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type + { + if (!$methodCall->class instanceof Name) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $className = $scope->resolveName($methodCall->class); + + $type = 'array'; + + if (isset($methodCall->args[1])) { + $argStrings = TypeUtils::getConstantStrings($scope->getType($methodCall->args[1]->value)); + if (count($argStrings) === 1 && isset(self::MAPPING[$argStrings[0]->getValue()])) { + $type = $argStrings[0]->getValue(); + } + } + + return new TreeBuilderType($className, self::MAPPING[$type]); + } + +} diff --git a/src/Type/Symfony/TreeBuilderGetRootNodeDynamicReturnTypeExtension.php b/src/Type/Symfony/TreeBuilderGetRootNodeDynamicReturnTypeExtension.php new file mode 100644 index 00000000..b8b00aba --- /dev/null +++ b/src/Type/Symfony/TreeBuilderGetRootNodeDynamicReturnTypeExtension.php @@ -0,0 +1,39 @@ +getName() === 'getRootNode'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type + { + $calledOnType = $scope->getType($methodCall->var); + if ($calledOnType instanceof TreeBuilderType) { + return new ObjectType($calledOnType->getRootNodeClassName()); + } + + return $methodReflection->getVariants()[0]->getReturnType(); + } + +} diff --git a/src/Type/Symfony/TreeBuilderType.php b/src/Type/Symfony/TreeBuilderType.php new file mode 100644 index 00000000..faf26c6a --- /dev/null +++ b/src/Type/Symfony/TreeBuilderType.php @@ -0,0 +1,25 @@ +rootNodeClassName = $rootNodeClassName; + } + + public function getRootNodeClassName(): string + { + return $this->rootNodeClassName; + } + +} diff --git a/tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php b/tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php index 2e7d7266..ef59ccc4 100644 --- a/tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php +++ b/tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php @@ -18,7 +18,7 @@ public function testAll(string $expression, string $type): void __DIR__ . '/envelope_all.php', $expression, $type, - new EnvelopeReturnTypeExtension() + [new EnvelopeReturnTypeExtension()] ); } diff --git a/tests/Type/Symfony/ExtensionTestCase.php b/tests/Type/Symfony/ExtensionTestCase.php index 49c4b0fa..26a805a6 100644 --- a/tests/Type/Symfony/ExtensionTestCase.php +++ b/tests/Type/Symfony/ExtensionTestCase.php @@ -17,21 +17,28 @@ use PHPStan\PhpDoc\PhpDocStringResolver; use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; use PHPStan\Testing\TestCase; -use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\VerbosityLevel; abstract class ExtensionTestCase extends TestCase { + /** + * @param string $file + * @param string $expression + * @param string $type + * @param \PHPStan\Type\DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions + * @param \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions + */ protected function processFile( string $file, string $expression, string $type, - DynamicMethodReturnTypeExtension $extension + array $dynamicMethodReturnTypeExtensions = [], + array $dynamicStaticMethodReturnTypeExtensions = [] ): void { - $broker = $this->createBroker([$extension]); + $broker = $this->createBroker($dynamicMethodReturnTypeExtensions, $dynamicStaticMethodReturnTypeExtensions); $parser = $this->getParser(); $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); $fileHelper = new FileHelper($currentWorkingDirectory); diff --git a/tests/Type/Symfony/HeaderBagDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/HeaderBagDynamicReturnTypeExtensionTest.php index f4b7d4cb..f84f3032 100644 --- a/tests/Type/Symfony/HeaderBagDynamicReturnTypeExtensionTest.php +++ b/tests/Type/Symfony/HeaderBagDynamicReturnTypeExtensionTest.php @@ -16,7 +16,7 @@ public function testGet(string $expression, string $type): void __DIR__ . '/header_bag_get.php', $expression, $type, - new HeaderBagDynamicReturnTypeExtension() + [new HeaderBagDynamicReturnTypeExtension()] ); } diff --git a/tests/Type/Symfony/InputBagDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/InputBagDynamicReturnTypeExtensionTest.php index 7e8f1d2a..b80b02ad 100644 --- a/tests/Type/Symfony/InputBagDynamicReturnTypeExtensionTest.php +++ b/tests/Type/Symfony/InputBagDynamicReturnTypeExtensionTest.php @@ -20,7 +20,7 @@ public function testGet(string $expression, string $type): void __DIR__ . '/input_bag_get.php', $expression, $type, - new InputBagDynamicReturnTypeExtension() + [new InputBagDynamicReturnTypeExtension()] ); } diff --git a/tests/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtensionTest.php index 9689018e..00d85e00 100644 --- a/tests/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtensionTest.php +++ b/tests/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtensionTest.php @@ -17,7 +17,7 @@ public function testArgumentTypes(string $expression, string $type): void __DIR__ . '/ExampleBaseCommand.php', $expression, $type, - new InputInterfaceGetArgumentDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php')) + [new InputInterfaceGetArgumentDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php'))] ); } diff --git a/tests/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtensionTest.php index affb0c62..ede5f68d 100644 --- a/tests/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtensionTest.php +++ b/tests/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtensionTest.php @@ -17,7 +17,7 @@ public function testArgumentTypes(string $expression, string $type): void __DIR__ . '/ExampleOptionCommand.php', $expression, $type, - new InputInterfaceGetOptionDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php')) + [new InputInterfaceGetOptionDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php'))] ); } diff --git a/tests/Type/Symfony/KernelInterfaceDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/KernelInterfaceDynamicReturnTypeExtensionTest.php index a5178a51..40bef996 100644 --- a/tests/Type/Symfony/KernelInterfaceDynamicReturnTypeExtensionTest.php +++ b/tests/Type/Symfony/KernelInterfaceDynamicReturnTypeExtensionTest.php @@ -16,7 +16,7 @@ public function testGet(string $expression, string $type): void __DIR__ . '/kernel_interface.php', $expression, $type, - new KernelInterfaceDynamicReturnTypeExtension() + [new KernelInterfaceDynamicReturnTypeExtension()] ); } diff --git a/tests/Type/Symfony/RequestDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/RequestDynamicReturnTypeExtensionTest.php index 22572808..f0834674 100644 --- a/tests/Type/Symfony/RequestDynamicReturnTypeExtensionTest.php +++ b/tests/Type/Symfony/RequestDynamicReturnTypeExtensionTest.php @@ -16,7 +16,7 @@ public function testGetContent(string $expression, string $type): void __DIR__ . '/request_get_content.php', $expression, $type, - new RequestDynamicReturnTypeExtension() + [new RequestDynamicReturnTypeExtension()] ); } diff --git a/tests/Type/Symfony/SerializerDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/SerializerDynamicReturnTypeExtensionTest.php index 8b60042c..0857127f 100644 --- a/tests/Type/Symfony/SerializerDynamicReturnTypeExtensionTest.php +++ b/tests/Type/Symfony/SerializerDynamicReturnTypeExtensionTest.php @@ -16,10 +16,10 @@ public function testSerializerInterface(string $expression, string $type): void __DIR__ . '/serializer.php', $expression, $type, - new SerializerDynamicReturnTypeExtension( + [new SerializerDynamicReturnTypeExtension( 'Symfony\Component\Serializer\SerializerInterface', 'deserialize' - ) + )] ); } @@ -32,10 +32,10 @@ public function testDenormalizerInterface(string $expression, string $type): voi __DIR__ . '/denormalizer.php', $expression, $type, - new SerializerDynamicReturnTypeExtension( + [new SerializerDynamicReturnTypeExtension( 'Symfony\Component\Serializer\Normalizer\DenormalizerInterface', 'denormalize' - ) + )] ); } diff --git a/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php index 9580bf91..8cfaa65d 100644 --- a/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php +++ b/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php @@ -18,7 +18,7 @@ public function testServices(string $expression, string $type, ?string $containe __DIR__ . '/ExampleController.php', $expression, $type, - new ServiceDynamicReturnTypeExtension(Controller::class, true, (new XmlServiceMapFactory($container))->create()) + [new ServiceDynamicReturnTypeExtension(Controller::class, true, (new XmlServiceMapFactory($container))->create())] ); } @@ -55,7 +55,7 @@ public function testConstantHassersOff(string $expression, string $type, ?string __DIR__ . '/ExampleController.php', $expression, $type, - new ServiceDynamicReturnTypeExtension(Controller::class, false, (new XmlServiceMapFactory($container))->create()) + [new ServiceDynamicReturnTypeExtension(Controller::class, false, (new XmlServiceMapFactory($container))->create())] ); } diff --git a/tests/Type/Symfony/TreeBuilderDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/TreeBuilderDynamicReturnTypeExtensionTest.php new file mode 100644 index 00000000..bbb90af6 --- /dev/null +++ b/tests/Type/Symfony/TreeBuilderDynamicReturnTypeExtensionTest.php @@ -0,0 +1,39 @@ +processFile( + __DIR__ . '/tree_builder.php', + $expression, + $type, + [new TreeBuilderGetRootNodeDynamicReturnTypeExtension()], + [new TreeBuilderDynamicReturnTypeExtension()] + ); + } + + /** + * @return \Iterator + */ + public function getProvider(): Iterator + { + yield ['$treeRootNode', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition']; + yield ['$variableRootNode', 'Symfony\Component\Config\Definition\Builder\VariableNodeDefinition']; + yield ['$scalarRootNode', 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition']; + yield ['$booleanRootNode', 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition']; + yield ['$integerRootNode', 'Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition']; + yield ['$floatRootNode', 'Symfony\Component\Config\Definition\Builder\FloatNodeDefinition']; + yield ['$arrayRootNode', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition']; + yield ['$enumRootNode', 'Symfony\Component\Config\Definition\Builder\EnumNodeDefinition']; + } + +} diff --git a/tests/Type/Symfony/tree_builder.php b/tests/Type/Symfony/tree_builder.php new file mode 100644 index 00000000..24d60d71 --- /dev/null +++ b/tests/Type/Symfony/tree_builder.php @@ -0,0 +1,29 @@ +getRootNode(); + +$variableTreeBuilder = new TreeBuilder('my_tree', 'variable'); +$variableRootNode = $variableTreeBuilder->getRootNode(); + +$scalarTreeBuilder = new TreeBuilder('my_tree', 'scalar'); +$scalarRootNode = $scalarTreeBuilder->getRootNode(); + +$booleanTreeBuilder = new TreeBuilder('my_tree', 'boolean'); +$booleanRootNode = $booleanTreeBuilder->getRootNode(); + +$integerTreeBuilder = new TreeBuilder('my_tree', 'integer'); +$integerRootNode = $integerTreeBuilder->getRootNode(); + +$floatTreeBuilder = new TreeBuilder('my_tree', 'float'); +$floatRootNode = $floatTreeBuilder->getRootNode(); + +$arrayTreeBuilder = new TreeBuilder('my_tree', 'array'); +$arrayRootNode = $arrayTreeBuilder->getRootNode(); + +$enumTreeBuilder = new TreeBuilder('my_tree', 'enum'); +$enumRootNode = $enumTreeBuilder->getRootNode(); + +die;

AltStyle によって変換されたページ (->オリジナル) /