Form get errors (#243)

* FormGetErrors provider

* Wip

* Add stub

* Update stub
This commit is contained in:
Vincent Langlet 2022-02-08 08:02:16 +01:00 committed by GitHub
parent 83830775d5
commit f5348e9a7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 158 additions and 0 deletions

View File

@ -15,6 +15,7 @@ use Psalm\SymfonyPsalmPlugin\Handler\DoctrineRepositoryHandler;
use Psalm\SymfonyPsalmPlugin\Handler\HeaderBagHandler;
use Psalm\SymfonyPsalmPlugin\Handler\ParameterBagHandler;
use Psalm\SymfonyPsalmPlugin\Handler\RequiredSetterHandler;
use Psalm\SymfonyPsalmPlugin\Provider\FormGetErrorsReturnTypeProvider;
use Psalm\SymfonyPsalmPlugin\Symfony\ContainerMeta;
use Psalm\SymfonyPsalmPlugin\Twig\AnalyzedTemplatesTainter;
use Psalm\SymfonyPsalmPlugin\Twig\CachedTemplatesMapping;
@ -39,6 +40,7 @@ class Plugin implements PluginEntryPointInterface
require_once __DIR__.'/Handler/ContainerDependencyHandler.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);
@ -99,6 +101,8 @@ class Plugin implements PluginEntryPointInterface
TemplateFileAnalyzer::setTemplateRootPath($twig_root_path);
}
$api->registerHooksFromClass(FormGetErrorsReturnTypeProvider::class);
}
private function addStubs(RegistrationInterface $api, string $path): void

View File

@ -0,0 +1,55 @@
<?php
namespace Psalm\SymfonyPsalmPlugin\Provider;
use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent;
use Psalm\Type;
use Psalm\Type\Atomic\TNamedObject;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormErrorIterator;
class FormGetErrorsReturnTypeProvider implements \Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface
{
public static function getClassLikeNames(): array
{
return ['Symfony\Component\Form\FormInterface'];
}
public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Type\Union
{
$method_name_lowercase = $event->getMethodNameLowercase();
if ('geterrors' !== $method_name_lowercase) {
return null;
}
$args = $event->getCallArgs();
$source = $event->getSource();
if (isset($args[0]) && isset($args[1])) {
$first_arg_type = $source->getNodeTypeProvider()->getType($args[0]->value);
$second_arg_type = $source->getNodeTypeProvider()->getType($args[1]->value);
if (
$first_arg_type
&& $first_arg_type->isTrue()
&& $second_arg_type
&& $second_arg_type->isFalse()
) {
return new Type\Union([
new Type\Atomic\TGenericObject(FormErrorIterator::class, [
new Type\Union([
new TNamedObject(FormError::class),
new TNamedObject(FormErrorIterator::class),
]),
]),
]);
}
}
return new Type\Union([
new Type\Atomic\TGenericObject(FormErrorIterator::class, [
new Type\Union([new TNamedObject(FormError::class)]),
]),
]);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Symfony\Component\Form;
/**
* @template T of FormError|FormErrorIterator
* @implements \ArrayAccess<int, T>
* @implements \RecursiveIterator<int, T>
* @implements \SeekableIterator<int, T>
*/
class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \ArrayAccess, \Countable
{
/**
* @param T[]
*/
public function __construct(FormInterface $form, array $errors);
/** @return T */
public function current();
/**
* @param int $position
*
* @return T
*/
public function offsetGet($position);
}

View File

@ -0,0 +1,72 @@
@symfony-common
Feature: Form getErrors return type provider
Background:
Given I have Symfony plugin enabled
Scenario: getErrors with anything else than (true, false)
Given I have the following code
"""
<?php
use Symfony\Component\Form\FormInterface;
function foo(FormInterface $form): array
{
$messages = [];
foreach ($form->getErrors() as $error1) {
$messages[] = $error1->getMessage();
}
foreach ($form->getErrors(true) as $error2) {
$messages[] = $error2->getMessage();
}
foreach ($form->getErrors(false) as $error3) {
$messages[] = $error3->getMessage();
}
foreach ($form->getErrors(true, true) as $error4) {
$messages[] = $error4->getMessage();
}
foreach ($form->getErrors(false, false) as $error5) {
$messages[] = $error5->getMessage();
}
foreach ($form->getErrors(false, true) as $error6) {
$messages[] = $error6->getMessage();
}
return $messages;
}
"""
When I run Psalm
Then I see no other errors
Scenario: getErrors with (true, false)
Given I have the following code
"""
<?php
use Symfony\Component\Form\FormInterface;
function foo(FormInterface $form): array
{
$messages = [];
foreach ($form->getErrors(true, false) as $error7) {
/** @psalm-trace $error7 */
$messages[] = $error7->getMessage();
}
return $messages;
}
"""
When I run Psalm
Then I see these errors
| Type | Message |
| Trace | $error7: Symfony\Component\Form\FormError\|Symfony\Component\Form\FormErrorIterator |
| MixedAssignment | Unable to determine the type of this assignment |
| PossiblyUndefinedMethod | Method Symfony\Component\Form\FormErrorIterator::getMessage does not exist |
And I see no other errors