diff --git a/src/Handler/ContainerAwareTraitHandler.php b/src/Handler/ContainerAwareTraitHandler.php new file mode 100644 index 00000000..8c0ef2af --- /dev/null +++ b/src/Handler/ContainerAwareTraitHandler.php @@ -0,0 +1,25 @@ +name) { + $storage->initialized_properties['container'] = true; + } + } +} diff --git a/src/Handler/RequiredPropertyHandler.php b/src/Handler/RequiredPropertyHandler.php new file mode 100644 index 00000000..4726107d --- /dev/null +++ b/src/Handler/RequiredPropertyHandler.php @@ -0,0 +1,51 @@ +properties as $name => $property) { + if (!empty($storage->initialized_properties[$name])) { + continue; + } + foreach ($property->attributes as $attribute) { + if ('Symfony\Contracts\Service\Attribute\Required' === $attribute->fq_class_name) { + $storage->initialized_properties[$name] = true; + continue 2; + } + } + $class = $storage->name; + if (!class_exists($class)) { + /** @psalm-suppress UnresolvableInclude */ + require_once $statements_source->getRootFilePath(); + } + /** @psalm-suppress ArgumentTypeCoercion */ + $reflection = $reflection ?? new \ReflectionClass($class); + if ($reflection->hasProperty($name)) { + $reflectionProperty = $reflection->getProperty($name); + $docCommend = $reflectionProperty->getDocComment(); + if ($docCommend && false !== strpos(strtoupper($docCommend), '@REQUIRED')) { + $storage->initialized_properties[$name] = true; + } + } + } + } +} diff --git a/src/Plugin.php b/src/Plugin.php index 9c022284..1d49e836 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -8,11 +8,13 @@ use Psalm\Plugin\RegistrationInterface; use Psalm\SymfonyPsalmPlugin\Handler\AnnotationHandler; use Psalm\SymfonyPsalmPlugin\Handler\ConsoleHandler; +use Psalm\SymfonyPsalmPlugin\Handler\ContainerAwareTraitHandler; use Psalm\SymfonyPsalmPlugin\Handler\ContainerDependencyHandler; use Psalm\SymfonyPsalmPlugin\Handler\ContainerHandler; use Psalm\SymfonyPsalmPlugin\Handler\DoctrineQueryBuilderHandler; use Psalm\SymfonyPsalmPlugin\Handler\DoctrineRepositoryHandler; use Psalm\SymfonyPsalmPlugin\Handler\HeaderBagHandler; +use Psalm\SymfonyPsalmPlugin\Handler\RequiredPropertyHandler; use Psalm\SymfonyPsalmPlugin\Handler\ParameterBagHandler; use Psalm\SymfonyPsalmPlugin\Handler\RequiredSetterHandler; use Psalm\SymfonyPsalmPlugin\Provider\FormGetErrorsReturnTypeProvider; @@ -37,14 +39,18 @@ public function __invoke(RegistrationInterface $api, SimpleXMLElement $config = require_once __DIR__.'/Handler/HeaderBagHandler.php'; require_once __DIR__.'/Handler/ContainerHandler.php'; require_once __DIR__.'/Handler/ConsoleHandler.php'; + require_once __DIR__.'/Handler/ContainerAwareTraitHandler.php'; require_once __DIR__.'/Handler/ContainerDependencyHandler.php'; + require_once __DIR__.'/Handler/RequiredPropertyHandler.php'; require_once __DIR__.'/Handler/RequiredSetterHandler.php'; require_once __DIR__.'/Handler/DoctrineQueryBuilderHandler.php'; require_once __DIR__.'/Provider/FormGetErrorsReturnTypeProvider.php'; $api->registerHooksFromClass(HeaderBagHandler::class); $api->registerHooksFromClass(ConsoleHandler::class); + $api->registerHooksFromClass(ContainerAwareTraitHandler::class); $api->registerHooksFromClass(ContainerDependencyHandler::class); + $api->registerHooksFromClass(RequiredPropertyHandler::class); $api->registerHooksFromClass(RequiredSetterHandler::class); if (class_exists(\Doctrine\ORM\QueryBuilder::class)) { diff --git a/tests/acceptance/acceptance/PropertyAccessorInterface.feature b/tests/acceptance/acceptance/PropertyAccessorInterface.feature index 79499b96..0dc24a55 100644 --- a/tests/acceptance/acceptance/PropertyAccessorInterface.feature +++ b/tests/acceptance/acceptance/PropertyAccessorInterface.feature @@ -30,7 +30,10 @@ Feature: PropertyAccessorInterface """ class Company { - public string $name = 'Acme'; + /** + * @var string + */ + public $name = 'Acme'; } $company = new Company(); diff --git a/tests/acceptance/acceptance/RequiredAttribute.feature b/tests/acceptance/acceptance/RequiredAttribute.feature new file mode 100644 index 00000000..a4c75af3 --- /dev/null +++ b/tests/acceptance/acceptance/RequiredAttribute.feature @@ -0,0 +1,57 @@ +@symfony-common +Feature: RequiredAttribute + + Background: + Given I have the following config + """ + + + + + + + + + + ../../tests/acceptance/container.xml + + + + """ + + Scenario: PropertyNotSetInConstructor error is not raised when the @required annotation is present. + Given I have the following code + """ + a = $a; }