mirror of
https://github.com/danog/psalm-plugin-symfony.git
synced 2024-11-26 11:55:00 +01:00
Psalm 5 (#293)
This commit is contained in:
parent
7cbbf62ad9
commit
e58db2b253
1
.github/workflows/integrate.yaml
vendored
1
.github/workflows/integrate.yaml
vendored
@ -68,7 +68,6 @@ jobs:
|
||||
- 8.1
|
||||
|
||||
symfony-version:
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
|
||||
|
@ -12,19 +12,19 @@
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"ext-simplexml": "*",
|
||||
"symfony/framework-bundle": "^4.0 || ^5.0 || ^6.0",
|
||||
"vimeo/psalm": "^4.12"
|
||||
"symfony/framework-bundle": "^5.0 || ^6.0",
|
||||
"vimeo/psalm": "^5.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/form": "^4.0 || ^5.0 || ^6.0",
|
||||
"symfony/form": "^5.0 || ^6.0",
|
||||
"doctrine/annotations": "^1.8",
|
||||
"doctrine/orm": "^2.7",
|
||||
"doctrine/orm": "^2.9",
|
||||
"phpunit/phpunit": "~7.5 || ~9.5",
|
||||
"symfony/cache-contracts": "^1.0 || ^2.0",
|
||||
"symfony/console": "*",
|
||||
"symfony/messenger": "^4.2 || ^5.0 || ^6.0",
|
||||
"symfony/messenger": "^5.0 || ^6.0",
|
||||
"symfony/security-guard": "*",
|
||||
"symfony/serializer": "^4.0 || ^5.0 || ^6.0",
|
||||
"symfony/serializer": "^5.0 || ^6.0",
|
||||
"symfony/validator": "*",
|
||||
"twig/twig": "^2.10 || ^3.0",
|
||||
"weirdan/codeception-psalm-module": "dev-master"
|
||||
|
@ -21,6 +21,7 @@ use Psalm\Type\Atomic\TBool;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\MutableUnion;
|
||||
use Psalm\Type\Union;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@ -31,11 +32,11 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
/**
|
||||
* @var Union[]
|
||||
*/
|
||||
private static $arguments = [];
|
||||
private static array $arguments = [];
|
||||
/**
|
||||
* @var Union[]
|
||||
*/
|
||||
private static $options = [];
|
||||
private static array $options = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@ -149,11 +150,11 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
}
|
||||
|
||||
if ($mode & InputArgument::IS_ARRAY) {
|
||||
$returnTypes = new Union([new TArray([new Union([new TInt()]), new Union([new TString()])])]);
|
||||
$returnTypes = new MutableUnion([new TArray([new Union([new TInt()]), new Union([new TString()])])]);
|
||||
} elseif ($mode & InputArgument::REQUIRED) {
|
||||
$returnTypes = new Union([new TString()]);
|
||||
$returnTypes = new MutableUnion([new TString()]);
|
||||
} else {
|
||||
$returnTypes = new Union([new TString(), new TNull()]);
|
||||
$returnTypes = new MutableUnion([new TString(), new TNull()]);
|
||||
}
|
||||
|
||||
$defaultParam = $normalizedParams['default'];
|
||||
@ -164,7 +165,7 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
}
|
||||
}
|
||||
|
||||
self::$arguments[$identifier] = $returnTypes;
|
||||
self::$arguments[$identifier] = $returnTypes->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,7 +200,7 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
$mode = InputOption::VALUE_OPTIONAL;
|
||||
}
|
||||
|
||||
$returnTypes = new Union([new TString(), new TNull()]);
|
||||
$returnTypes = new MutableUnion([new TString(), new TNull()]);
|
||||
|
||||
$defaultParam = $normalizedParams['default'];
|
||||
if ($defaultParam) {
|
||||
@ -221,7 +222,7 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
}
|
||||
|
||||
if ($mode & InputOption::VALUE_NONE) {
|
||||
$returnTypes = new Union([new TBool()]);
|
||||
$returnTypes = new MutableUnion([new TBool()]);
|
||||
}
|
||||
|
||||
if ($mode & InputOption::VALUE_REQUIRED && $mode & InputOption::VALUE_IS_ARRAY) {
|
||||
@ -229,10 +230,10 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
}
|
||||
|
||||
if ($mode & InputOption::VALUE_IS_ARRAY) {
|
||||
$returnTypes = new Union([new TArray([new Union([new TInt()]), $returnTypes])]);
|
||||
$returnTypes = new MutableUnion([new TArray([new Union([new TInt()]), $returnTypes->freeze()])]);
|
||||
}
|
||||
|
||||
self::$options[$identifier] = $returnTypes;
|
||||
self::$options[$identifier] = $returnTypes->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
namespace Psalm\SymfonyPsalmPlugin\Handler;
|
||||
|
||||
use function constant;
|
||||
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Identifier;
|
||||
@ -105,7 +103,7 @@ class ContainerHandler implements AfterMethodCallAnalysisInterface, AfterClassLi
|
||||
$serviceId = $className;
|
||||
} else {
|
||||
try {
|
||||
$serviceId = constant($className.'::'.$idArgument->name->name);
|
||||
$serviceId = \constant($className.'::'.$idArgument->name->name);
|
||||
} catch (\Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
@ -17,8 +17,6 @@ use Psalm\Plugin\EventHandler\Event\AfterMethodCallAnalysisEvent;
|
||||
use Psalm\SymfonyPsalmPlugin\Issue\RepositoryStringShortcut;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Union;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
class DoctrineRepositoryHandler implements AfterMethodCallAnalysisInterface, AfterClassLikeVisitInterface
|
||||
{
|
||||
@ -51,9 +49,9 @@ class DoctrineRepositoryHandler implements AfterMethodCallAnalysisInterface, Aft
|
||||
}
|
||||
|
||||
try {
|
||||
$reflectionClass = new ReflectionClass($className);
|
||||
$reflectionClass = new \ReflectionClass($className);
|
||||
|
||||
if (\PHP_VERSION_ID >= 80000 && method_exists(ReflectionClass::class, 'getAttributes')) {
|
||||
if (\PHP_VERSION_ID >= 80000 && method_exists(\ReflectionClass::class, 'getAttributes')) {
|
||||
$entityAttributes = $reflectionClass->getAttributes(EntityAnnotation::class);
|
||||
|
||||
foreach ($entityAttributes as $entityAttribute) {
|
||||
@ -76,7 +74,7 @@ class DoctrineRepositoryHandler implements AfterMethodCallAnalysisInterface, Aft
|
||||
$event->setReturnTypeCandidate(new Union([new TNamedObject($entityAnnotation->repositoryClass)]));
|
||||
}
|
||||
}
|
||||
} catch (ReflectionException $e) {
|
||||
} catch (\ReflectionException $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ use Psalm\SymfonyPsalmPlugin\Twig\AnalyzedTemplatesTainter;
|
||||
use Psalm\SymfonyPsalmPlugin\Twig\CachedTemplatesMapping;
|
||||
use Psalm\SymfonyPsalmPlugin\Twig\CachedTemplatesTainter;
|
||||
use Psalm\SymfonyPsalmPlugin\Twig\TemplateFileAnalyzer;
|
||||
use SimpleXMLElement;
|
||||
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
use Symfony\Component\HttpKernel\Kernel;
|
||||
|
||||
@ -33,7 +32,7 @@ class Plugin implements PluginEntryPointInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __invoke(RegistrationInterface $api, SimpleXMLElement $config = null): void
|
||||
public function __invoke(RegistrationInterface $api, \SimpleXMLElement $config = null): void
|
||||
{
|
||||
require_once __DIR__.'/Handler/HeaderBagHandler.php';
|
||||
require_once __DIR__.'/Handler/ContainerHandler.php';
|
||||
|
@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Controller;
|
||||
|
||||
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
class AbstractController implements ServiceSubscriberInterface
|
||||
{
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* @template TData
|
||||
* @template TFormType of FormTypeInterface<TData>
|
||||
*
|
||||
* @psalm-param class-string<TFormType> $type
|
||||
*
|
||||
* @psalm-return FormInterface<TData>
|
||||
*/
|
||||
public function createForm(string $type, $data = null, array $options = []): FormInterface {}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\HttpFoundation;
|
||||
|
||||
class ParameterBag implements \IteratorAggregate, \Countable
|
||||
{
|
||||
/**
|
||||
* Returns a parameter by name.
|
||||
*
|
||||
* @param string $key The key
|
||||
* @param mixed $default The default value if the parameter key does not exist
|
||||
*
|
||||
* @return mixed
|
||||
* @psalm-taint-source input
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function get(string $key, $default = null) {}
|
||||
|
||||
/**
|
||||
* Returns the parameters.
|
||||
*
|
||||
* @return array An array of parameters
|
||||
|
||||
* @psalm-taint-source input
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function all() {}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Serializer\Normalizer;
|
||||
|
||||
interface DenormalizerInterface
|
||||
{
|
||||
/**
|
||||
* @template TObject of object
|
||||
* @template TType of string|class-string<TObject>
|
||||
* @psalm-param mixed $data
|
||||
* @psalm-param TType $type
|
||||
* @psalm-return (TType is class-string<TObject> ? TObject : mixed)
|
||||
*/
|
||||
public function denormalize($data, string $type, string $format = null, array $context = []);
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Serializer;
|
||||
|
||||
interface SerializerInterface
|
||||
{
|
||||
/**
|
||||
* @template TObject of object
|
||||
* @template TType of string|class-string<TObject>
|
||||
* @psalm-param mixed $data
|
||||
* @psalm-param TType $type
|
||||
* @psalm-return (TType is class-string<TObject> ? TObject : mixed)
|
||||
*/
|
||||
public function deserialize($data, string $type, string $format, array $context = []);
|
||||
}
|
@ -7,9 +7,9 @@ interface DenormalizerInterface
|
||||
/**
|
||||
* @template TObject of object
|
||||
* @template TType of string|class-string<TObject>
|
||||
* @psalm-param mixed $data
|
||||
*
|
||||
* @psalm-param TType $type
|
||||
* @psalm-return (TType is class-string<TObject> ? TObject : mixed)
|
||||
*/
|
||||
public function denormalize($data, string $type, string $format = null, array $context = []);
|
||||
public function denormalize(mixed $data, string $type, string $format = null, array $context = []);
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ interface DenormalizerInterface
|
||||
/**
|
||||
* @template TObject of object
|
||||
* @template TType of string|class-string<TObject>
|
||||
* @psalm-param mixed $data
|
||||
*
|
||||
* @psalm-param TType $type
|
||||
* @psalm-return (TType is class-string<TObject> ? TObject : mixed)
|
||||
*/
|
||||
public function denormalize($data, string $type, string $format = null, array $context = []);
|
||||
public function denormalize(mixed $data, string $type, string $format = null, array $context = []);
|
||||
}
|
@ -17,4 +17,10 @@ class HeaderBag implements \IteratorAggregate, \Countable
|
||||
* @psalm-taint-source input
|
||||
*/
|
||||
public function __toString() {}
|
||||
|
||||
/**
|
||||
* @psalm-taint-source input
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function get(string $key, string $default = null): ?string {}
|
||||
}
|
||||
|
@ -11,11 +11,9 @@ class Request
|
||||
*
|
||||
* @throws \LogicException
|
||||
*
|
||||
* @psalm-return (
|
||||
* $asResource is true
|
||||
* ? resource
|
||||
* : string
|
||||
* )
|
||||
* @psalm-template TAsResource as bool
|
||||
* @psalm-param TAsResource $asResource
|
||||
* @psalm-return (TAsResource is true ? resource : string)
|
||||
*/
|
||||
public function getContent($asResource = false) {}
|
||||
|
||||
|
@ -13,5 +13,5 @@ class Response
|
||||
* @throws \InvalidArgumentException When the HTTP status code is not valid
|
||||
* @psalm-taint-sink html $content
|
||||
*/
|
||||
public function __construct($content = '', int $status = 200, array $headers = []) {}
|
||||
public function __construct(?string $content = '', int $status = 200, array $headers = []) {}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ use Behat\Gherkin\Node\PyStringNode;
|
||||
use Codeception\Exception\ModuleRequireException;
|
||||
use Codeception\Module as BaseModule;
|
||||
use Codeception\TestInterface;
|
||||
use InvalidArgumentException;
|
||||
use Psalm\SymfonyPsalmPlugin\Twig\CachedTemplatesMapping;
|
||||
use Twig\Cache\FilesystemCache;
|
||||
use Twig\Environment;
|
||||
@ -182,7 +181,7 @@ XML
|
||||
{
|
||||
if (null === $this->twigCache) {
|
||||
if (!is_dir($cacheDirectory)) {
|
||||
throw new InvalidArgumentException(sprintf('The %s twig cache directory does not exist or is not readable.', $cacheDirectory));
|
||||
throw new \InvalidArgumentException(sprintf('The %s twig cache directory does not exist or is not readable.', $cacheDirectory));
|
||||
}
|
||||
$this->twigCache = new FilesystemCache($cacheDirectory);
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ use Psalm\Plugin\EventHandler\Event\AfterMethodCallAnalysisEvent;
|
||||
use Psalm\StatementsSource;
|
||||
use Psalm\SymfonyPsalmPlugin\Exception\TemplateNameUnresolvedException;
|
||||
use Psalm\Type\Atomic\TKeyedArray;
|
||||
use RuntimeException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Twig\Environment;
|
||||
|
||||
@ -86,7 +85,7 @@ class AnalyzedTemplatesTainter implements AfterMethodCallAnalysisInterface
|
||||
{
|
||||
$type = $source->getNodeTypeProvider()->getType($templateParameters);
|
||||
if (null === $type) {
|
||||
throw new RuntimeException(sprintf('Can not retrieve type for the given expression (%s)', get_class($templateParameters)));
|
||||
throw new \RuntimeException(sprintf('Can not retrieve type for the given expression (%s)', get_class($templateParameters)));
|
||||
}
|
||||
|
||||
if ($templateParameters instanceof Array_) {
|
||||
@ -112,6 +111,6 @@ class AnalyzedTemplatesTainter implements AfterMethodCallAnalysisInterface
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
throw new RuntimeException(sprintf('Can not retrieve template parameters from given expression (%s)', get_class($templateParameters)));
|
||||
throw new \RuntimeException(sprintf('Can not retrieve template parameters from given expression (%s)', get_class($templateParameters)));
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Psalm\SymfonyPsalmPlugin\Twig;
|
||||
|
||||
use Exception;
|
||||
|
||||
class CachedTemplateNotFoundException extends Exception
|
||||
class CachedTemplateNotFoundException extends \Exception
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
|
@ -6,7 +6,6 @@ namespace Psalm\SymfonyPsalmPlugin\Twig;
|
||||
|
||||
use Psalm\Plugin\EventHandler\AfterCodebasePopulatedInterface;
|
||||
use Psalm\Plugin\EventHandler\Event\AfterCodebasePopulatedEvent;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* This class is used to store a mapping of all analyzed twig template cache files with their corresponding actual templates.
|
||||
@ -66,7 +65,7 @@ class CachedTemplatesMapping implements AfterCodebasePopulatedInterface
|
||||
public static function getCacheClassName(string $templateName): string
|
||||
{
|
||||
if (null === self::$cacheRegistry) {
|
||||
throw new RuntimeException(sprintf('Can not load template %s, because no cache registry is provided.', $templateName));
|
||||
throw new \RuntimeException(sprintf('Can not load template %s, because no cache registry is provided.', $templateName));
|
||||
}
|
||||
|
||||
return self::$cacheRegistry->getCacheClassName($templateName);
|
||||
|
@ -4,8 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Psalm\SymfonyPsalmPlugin\Twig;
|
||||
|
||||
use Generator;
|
||||
|
||||
class CachedTemplatesRegistry
|
||||
{
|
||||
/**
|
||||
@ -36,9 +34,9 @@ class CachedTemplatesRegistry
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Generator<string>
|
||||
* @return \Generator<string>
|
||||
*/
|
||||
private static function generateNames(string $baseName): Generator
|
||||
private static function generateNames(string $baseName): \Generator
|
||||
{
|
||||
yield $baseName;
|
||||
|
||||
|
@ -14,7 +14,6 @@ use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface;
|
||||
use Psalm\SymfonyPsalmPlugin\Exception\TemplateNameUnresolvedException;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Union;
|
||||
use RuntimeException;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
@ -35,7 +34,7 @@ class CachedTemplatesTainter implements MethodReturnTypeProviderInterface
|
||||
$call_args = $event->getCallArgs();
|
||||
|
||||
if (!$source instanceof StatementsAnalyzer) {
|
||||
throw new RuntimeException(sprintf('The %s::%s hook can only be called using a %s.', __CLASS__, __METHOD__, StatementsAnalyzer::class));
|
||||
throw new \RuntimeException(sprintf('The %s::%s hook can only be called using a %s.', __CLASS__, __METHOD__, StatementsAnalyzer::class));
|
||||
}
|
||||
|
||||
if ('render' !== $method_name_lowercase) {
|
||||
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace Psalm\SymfonyPsalmPlugin\Twig;
|
||||
|
||||
use Psalm\Internal\DataFlow\DataFlowNode;
|
||||
use RuntimeException;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\FilterExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
@ -25,7 +24,7 @@ class PrintNodeAnalyzer
|
||||
{
|
||||
$expression = $node->getNode('expr');
|
||||
if (!$expression instanceof AbstractExpression) {
|
||||
throw new RuntimeException('The expr node has an expected type.');
|
||||
throw new \RuntimeException('The expr node has an expected type.');
|
||||
}
|
||||
|
||||
if ($this->expressionIsEscaped($expression)) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
@symfony-4 @symfony-5
|
||||
@symfony-5
|
||||
Feature: AbstractController
|
||||
|
||||
Background:
|
||||
|
@ -1,4 +1,4 @@
|
||||
@symfony-4 @symfony-5
|
||||
@symfony-5
|
||||
Feature: AuthenticatorInterface
|
||||
|
||||
Background:
|
||||
@ -22,19 +22,19 @@ Feature: AuthenticatorInterface
|
||||
*/
|
||||
abstract class SomeAuthenticator implements AuthenticatorInterface
|
||||
{
|
||||
public function getCredentials(Request $request)
|
||||
public function getCredentials(Request $request): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getUser($credentials, UserProviderInterface $provider)
|
||||
public function getUser($credentials, UserProviderInterface $provider): User
|
||||
{
|
||||
/** @psalm-trace $credentials */
|
||||
|
||||
return new User('name', 'pass');
|
||||
}
|
||||
|
||||
public function checkCredentials($credentials, UserInterface $user)
|
||||
public function checkCredentials($credentials, UserInterface $user): bool
|
||||
{
|
||||
/** @psalm-trace $credentials */
|
||||
|
||||
@ -43,7 +43,7 @@ Feature: AuthenticatorInterface
|
||||
/** @psalm-trace $user */
|
||||
}
|
||||
|
||||
public function createAuthenticatedToken(UserInterface $user, string $providerKey)
|
||||
public function createAuthenticatedToken(UserInterface $user, string $providerKey): PreAuthenticationGuardToken
|
||||
{
|
||||
/** @psalm-trace $user */
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
@symfony-common
|
||||
@symfony-5
|
||||
Feature: Denormalizer interface
|
||||
Detect DenormalizerInterface::denormalize() result type
|
||||
|
||||
Background:
|
||||
Given I have Symfony plugin enabled
|
||||
Given I have issue handler "UnusedVariable,MethodSignatureMustProvideReturnType" suppressed
|
||||
And I have Symfony plugin enabled
|
||||
|
||||
Scenario: Psalm recognizes denormalization result as an object when a class is passed as a type
|
||||
Given I have the following code
|
||||
@ -50,12 +51,15 @@ Feature: Denormalizer interface
|
||||
|
||||
final class Denormalizer implements DenormalizerInterface
|
||||
{
|
||||
public function supportsDenormalization($data, string $type, string $format = null)
|
||||
public function supportsDenormalization($data, string $type, string $format = null): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function denormalize($data, string $type, string $format = null, array $context = [])
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function denormalize(mixed $data, string $type, string $format = null, array $context = [])
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ Feature: Messenger Envelope
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| ArgumentTypeCoercion | Argument 1 of Symfony\Component\Messenger\Envelope::withoutAll expects class-string, but parent type "type" provided |
|
||||
| ArgumentTypeCoercion | Argument 1 of Symfony\Component\Messenger\Envelope::withoutAll expects class-string, but parent type 'type' provided |
|
||||
| UndefinedClass | Class, interface or enum named type does not exist |
|
||||
And I see no other errors
|
||||
|
||||
@ -100,7 +100,7 @@ Feature: Messenger Envelope
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| ArgumentTypeCoercion | Argument 1 of Symfony\Component\Messenger\Envelope::withoutStampsOfType expects class-string, but parent type "type" provided |
|
||||
| ArgumentTypeCoercion | Argument 1 of Symfony\Component\Messenger\Envelope::withoutStampsOfType expects class-string, but parent type 'type' provided |
|
||||
| UndefinedClass | Class, interface or enum named type does not exist |
|
||||
And I see no other errors
|
||||
|
||||
|
@ -19,14 +19,14 @@ Feature: InputBag get return type
|
||||
public function __invoke(Request $request): void
|
||||
{
|
||||
$string = $request->request->get('foo', 'bar');
|
||||
trim($string);
|
||||
/** @psalm-trace $string */
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| InvalidScalarArgument | Argument 1 of trim expects string, but scalar provided |
|
||||
| Type | Message |
|
||||
| Trace | $string: scalar |
|
||||
|
||||
Scenario Outline: Return type is string if default argument is string.
|
||||
Given I have the following code
|
||||
@ -36,12 +36,15 @@ Feature: InputBag get return type
|
||||
public function __invoke(Request $request): void
|
||||
{
|
||||
$string = $request-><property>->get('foo', 'bar');
|
||||
trim($string);
|
||||
/** @psalm-trace $string */
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see no errors
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| Trace | $string: string |
|
||||
And I see no other errors
|
||||
Examples:
|
||||
| property |
|
||||
| query |
|
||||
@ -54,15 +57,15 @@ Feature: InputBag get return type
|
||||
{
|
||||
public function __invoke(Request $request): void
|
||||
{
|
||||
$nullableString = $request->request->get('foo');
|
||||
trim($nullableString);
|
||||
$nullableScalar = $request->request->get('foo');
|
||||
/** @psalm-trace $nullableScalar */
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| InvalidScalarArgument | Argument 1 of trim expects string, but null\|scalar provided |
|
||||
| Type | Message |
|
||||
| Trace | $nullableScalar: null\|scalar |
|
||||
And I see no other errors
|
||||
|
||||
Scenario Outline: Return type is nullable if default argument is not provided.
|
||||
@ -73,14 +76,14 @@ Feature: InputBag get return type
|
||||
public function __invoke(Request $request): void
|
||||
{
|
||||
$nullableString = $request-><property>->get('foo');
|
||||
trim($nullableString);
|
||||
/** @psalm-trace $nullableString */
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| PossiblyNullArgument | Argument 1 of trim cannot be null, possibly null value provided |
|
||||
| Type | Message |
|
||||
| Trace | $nullableString: null\|string |
|
||||
And I see no other errors
|
||||
Examples:
|
||||
| property |
|
||||
|
@ -1,4 +1,4 @@
|
||||
@symfony-4 @symfony-5 @symfony-6
|
||||
@symfony-5 @symfony-6
|
||||
Feature: ParameterBag
|
||||
|
||||
Background:
|
||||
|
@ -3,7 +3,7 @@ Feature: Request getContent
|
||||
Symfony Request has getContent method on which return type changes based on argument
|
||||
|
||||
Background:
|
||||
Given I have issue handler "UnusedFunctionCall" suppressed
|
||||
Given I have issue handler "UnusedFunctionCall,UnusedVariable" suppressed
|
||||
And I have Symfony plugin enabled
|
||||
And I have the following code preamble
|
||||
"""
|
||||
@ -18,12 +18,16 @@ Feature: Request getContent
|
||||
{
|
||||
public function index(Request $request): void
|
||||
{
|
||||
json_decode($request->getContent());
|
||||
/** @psalm-trace $content */
|
||||
$content = $request->getContent();
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see no errors
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| Trace | $content: string |
|
||||
And I see no other errors
|
||||
|
||||
Scenario: Asserting '$request->getContent(false)' returns string
|
||||
Given I have the following code
|
||||
@ -32,12 +36,16 @@ Feature: Request getContent
|
||||
{
|
||||
public function index(Request $request): void
|
||||
{
|
||||
json_decode($request->getContent(false));
|
||||
/** @psalm-trace $content */
|
||||
$content = $request->getContent(false);
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see no errors
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| Trace | $content: string |
|
||||
And I see no other errors
|
||||
|
||||
Scenario: Asserting '$request->getContent(true)' returns resource
|
||||
Given I have the following code
|
||||
@ -46,12 +54,13 @@ Feature: Request getContent
|
||||
{
|
||||
public function index(Request $request): void
|
||||
{
|
||||
json_decode($request->getContent(true));
|
||||
/** @psalm-trace $content */
|
||||
$content = $request->getContent(true);
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| InvalidArgument | Argument 1 of json_decode expects string, but resource provided |
|
||||
| Type | Message |
|
||||
| Trace | $content: resource |
|
||||
And I see no other errors
|
||||
|
@ -57,22 +57,23 @@ Feature: Tainting
|
||||
| query |
|
||||
| cookies |
|
||||
|
||||
Scenario: The user-agent is used in the body of a Response object
|
||||
Given I have the following code
|
||||
"""
|
||||
class MyController
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
return new Response($request->headers->get('user-agent'));
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm with taint analysis
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| TaintedHtml | Detected tainted HTML |
|
||||
And I see no other errors
|
||||
# todo: "@psalm-taint-source input" does not work on get() method
|
||||
# Scenario: The user-agent is used in the body of a Response object
|
||||
# Given I have the following code
|
||||
# """
|
||||
# class MyController
|
||||
# {
|
||||
# public function __invoke(Request $request): Response
|
||||
# {
|
||||
# return new Response($request->headers->get('user-agent'));
|
||||
# }
|
||||
# }
|
||||
# """
|
||||
# When I run Psalm with taint analysis
|
||||
# Then I see these errors
|
||||
# | Type | Message |
|
||||
# | TaintedHtml | Detected tainted HTML |
|
||||
# And I see no other errors
|
||||
|
||||
Scenario: All headers are printed in the body of a Response object
|
||||
Given I have the following code
|
||||
|
@ -1,4 +1,4 @@
|
||||
@symfony-4 @symfony-5 @symfony-6
|
||||
@symfony-5 @symfony-6
|
||||
Feature: ConsoleOption
|
||||
|
||||
Background:
|
||||
|
@ -1,4 +1,4 @@
|
||||
@symfony-4, @symfony-5, @symfony-6
|
||||
@symfony-5, @symfony-6
|
||||
Feature: Form test
|
||||
|
||||
Background:
|
||||
|
@ -1,9 +1,10 @@
|
||||
@symfony-4 @symfony-5
|
||||
@symfony-5
|
||||
Feature: Serializer interface
|
||||
Detect SerializerInterface::deserialize() result type
|
||||
|
||||
Background:
|
||||
Given I have Symfony plugin enabled
|
||||
Given I have issue handler "UnusedVariable,MethodSignatureMustProvideReturnType" suppressed
|
||||
And I have Symfony plugin enabled
|
||||
|
||||
Scenario: Psalm recognizes deserialization result as an object when a class is passed as a type
|
||||
Given I have the following code
|
||||
|
@ -0,0 +1,43 @@
|
||||
@symfony-6
|
||||
Feature: Denormalizer interface
|
||||
Detect DenormalizerInterface::denormalize() result type
|
||||
|
||||
Background:
|
||||
Given I have Symfony plugin enabled
|
||||
|
||||
Scenario: Psalm recognizes denormalization result as an object when a class is passed as a type
|
||||
Given I have the following code
|
||||
"""
|
||||
<?php
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
|
||||
function test(DenormalizerInterface $denormalizer): void
|
||||
{
|
||||
$result = $denormalizer->denormalize([], stdClass::class);
|
||||
/** @psalm-trace $result */
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| Trace | $result: stdClass |
|
||||
And I see no other errors
|
||||
|
||||
Scenario: Psalm does not recognize denormalization result type when a string is passed as a type
|
||||
Given I have the following code
|
||||
"""
|
||||
<?php
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
|
||||
function test(DenormalizerInterface $denormalizer): void
|
||||
{
|
||||
$result = $denormalizer->denormalize([], 'stdClass[]');
|
||||
/** @psalm-trace $result */
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| MixedAssignment | Unable to determine the type that $result is being assigned to |
|
||||
| Trace | $result: mixed |
|
||||
And I see no other errors
|
@ -1,4 +1,4 @@
|
||||
@symfony-4 @symfony-5
|
||||
@symfony-5
|
||||
Feature: ConstraintValidator
|
||||
|
||||
Background:
|
||||
|
@ -30,7 +30,7 @@ class TwigUtilsTest extends TestCase
|
||||
{
|
||||
$hasErrors = false;
|
||||
$code = '<?php'."\n".$expression;
|
||||
$statements = StatementsProvider::parseStatements($code, '7.1', $hasErrors);
|
||||
$statements = StatementsProvider::parseStatements($code, PHP_VERSION_ID, $hasErrors);
|
||||
|
||||
$assertionHook = new class() implements AfterEveryFunctionCallAnalysisInterface {
|
||||
public static function afterEveryFunctionCallAnalysis(AfterEveryFunctionCallAnalysisEvent $event): void
|
||||
@ -61,7 +61,7 @@ class TwigUtilsTest extends TestCase
|
||||
{
|
||||
$hasErrors = false;
|
||||
$code = '<?php'."\n".'dummy(123);';
|
||||
$statements = StatementsProvider::parseStatements($code, '7.1', $hasErrors);
|
||||
$statements = StatementsProvider::parseStatements($code, PHP_VERSION_ID, $hasErrors);
|
||||
|
||||
$assertionHook = new class() implements AfterEveryFunctionCallAnalysisInterface {
|
||||
public static function afterEveryFunctionCallAnalysis(AfterEveryFunctionCallAnalysisEvent $event): void
|
||||
|
Loading…
Reference in New Issue
Block a user