diff --git a/README.md b/README.md index 5e118e8..6f650df 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ to use InputArgument and InputOption constants as a part of best practise. - Detects correct Doctrine repository class if entities are configured with annotations. - Fixes `PossiblyInvalidArgument` for `Symfony\Component\HttpFoundation\Request::getContent`. The plugin calculates real return type by checking the given argument and marks return type as either string or resource. -- Detect return type of `Symfony\Component\HttpFoundation\HeaderBag::get` (by checking third argument for < Symfony 4.4) +- Detect return type of `Symfony\Component\HttpFoundation\HeaderBag::get` (by checking default value and third argument for < Symfony 4.4) - Detects service [naming convention](https://symfony.com/doc/current/contributing/code/standards.html#naming-conventions) violations - Complains when `Container` is injected to a service. Use dependency-injection. diff --git a/src/Handler/HeaderBagHandler.php b/src/Handler/HeaderBagHandler.php index c3a4231..800e5bd 100644 --- a/src/Handler/HeaderBagHandler.php +++ b/src/Handler/HeaderBagHandler.php @@ -2,42 +2,48 @@ namespace Psalm\SymfonyPsalmPlugin\Handler; -use PhpParser\Node\Expr; -use Psalm\Codebase; +use PhpParser\Node\Expr\ConstFetch; +use PhpParser\Node\Scalar\String_; +use Psalm\CodeLocation; use Psalm\Context; -use Psalm\Plugin\Hook\AfterMethodCallAnalysisInterface; +use Psalm\Plugin\Hook\MethodReturnTypeProviderInterface; use Psalm\StatementsSource; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TString; use Psalm\Type\Union; +use Symfony\Component\HttpFoundation\HeaderBag; -class HeaderBagHandler implements AfterMethodCallAnalysisInterface +class HeaderBagHandler implements MethodReturnTypeProviderInterface { - /** - * {@inheritdoc} - */ - public static function afterMethodCallAnalysis( - Expr $expr, - string $method_id, - string $appearing_method_id, - string $declaring_method_id, - Context $context, - StatementsSource $statements_source, - Codebase $codebase, - array &$file_replacements = [], - Union &$return_type_candidate = null - ) { - if ('Symfony\Component\HttpFoundation\HeaderBag::get' === $declaring_method_id) { - if ($return_type_candidate) { - /** @psalm-suppress MixedArrayAccess */ - if (isset($expr->args[2]->value->name->parts[0]) && 'false' === $expr->args[2]->value->name->parts[0]) { - $return_type_candidate = new Union([new TArray([new Union([new TInt()]), new Union([new TString()])])]); - } else { - $return_type_candidate = new Union([new TString(), new TNull()]); + public static function getClassLikeNames(): array + { + return [ + HeaderBag::class, + ]; + } + + public static function getMethodReturnType(StatementsSource $source, string $fq_classlike_name, string $method_name_lowercase, array $call_args, Context $context, CodeLocation $code_location, array $template_type_parameters = null, string $called_fq_classlike_name = null, string $called_method_name_lowercase = null) + { + if (HeaderBag::class !== $fq_classlike_name) { + return null; + } + + if ('get' === $method_name_lowercase) { + if (3 === count($call_args) && (($arg = $call_args[2]->value) instanceof ConstFetch) && 'false' === $arg->name->parts[0]) { + return new Union([new TArray([new Union([new TInt()]), new Union([new TString()])])]); + } + + if (isset($call_args[1])) { + if ($call_args[1]->value instanceof String_) { + return new Union([new TString()]); } } + + return new Union([new TString(), new TNull()]); } + + return null; } } diff --git a/tests/acceptance/acceptance/HeaderBag.feature b/tests/acceptance/acceptance/HeaderBag.feature index 0410ed6..e294d95 100644 --- a/tests/acceptance/acceptance/HeaderBag.feature +++ b/tests/acceptance/acceptance/HeaderBag.feature @@ -15,14 +15,16 @@ Feature: Header get """ - - Scenario: HeaderBag get method return type should return `?string` (unless third argument is provided for < Sf4.4) - Given I have the following code + And I have the following code preamble """ headers->get('string', 'string'); + + trim($string); + } + } + """ + When I run Psalm + Then I see no errors