[feature] request getContent return type fix (#5)

This commit is contained in:
Farhad Safarov 2019-12-06 12:25:16 +03:00 committed by GitHub
parent b1c05203bc
commit c2924cb89a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 26 deletions

View File

@ -9,6 +9,12 @@ composer require --dev seferov/symfony-psalm-plugin
vendor/bin/psalm-plugin enable seferov/symfony-psalm-plugin
```
### Features
- 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.
- Complains when `Container` is injected to a service. Use dependency-injection.
### Credits
- [@weirdan](https://github.com/weirdan) for [codeception psalm module](https://github.com/weirdan/codeception-psalm-module)

View File

@ -5,12 +5,9 @@ namespace Seferov\SymfonyPsalmPlugin\Handler;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use Psalm\Codebase;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\FileManipulation;
use Psalm\IssueBuffer;
use Psalm\Plugin\Hook\AfterClassLikeAnalysisInterface;
use Psalm\Plugin\Hook\AfterMethodCallAnalysisInterface;
@ -24,11 +21,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class ClassHandler implements AfterClassLikeAnalysisInterface, AfterMethodCallAnalysisInterface
{
/**
* Called after a statement has been checked
*
* @param FileManipulation[] $file_replacements
*
* @return null|false
* {@inheritDoc}
*/
public static function afterStatementAnalysis(Node\Stmt\ClassLike $stmt, ClassLikeStorage $classlike_storage, StatementsSource $statements_source, Codebase $codebase, array &$file_replacements = [])
{
@ -49,10 +42,7 @@ class ClassHandler implements AfterClassLikeAnalysisInterface, AfterMethodCallAn
}
/**
* @param MethodCall|StaticCall $expr
* @param FileManipulation[] $file_replacements
*
* @return void
* {@inheritDoc}
*/
public static function afterMethodCallAnalysis(
Expr $expr,
@ -65,20 +55,26 @@ class ClassHandler implements AfterClassLikeAnalysisInterface, AfterMethodCallAn
array &$file_replacements = [],
Union &$return_type_candidate = null
) {
if (!$expr instanceof MethodCall) {
return;
}
switch ($declaring_method_id) {
case 'Doctrine\ORM\EntityManagerInterface::getrepository':
if (!$expr->args[0]->value instanceof ClassConstFetch) {
IssueBuffer::accepts(
new RepositoryStringShortcut(new CodeLocation($statements_source, $expr->args[0]->value)),
$statements_source->getSuppressedIssues()
);
}
break;
if ($expr->name instanceof Node\Identifier && 'getRepository' === $expr->name->name) {
/** @psalm-suppress InternalMethod */
$methodStorage = $codebase->methods->getStorage($declaring_method_id);
if ('Doctrine\ORM\EntityManagerInterface' === $methodStorage->defining_fqcln && !$expr->args[0]->value instanceof ClassConstFetch) {
IssueBuffer::accepts(
new RepositoryStringShortcut(new CodeLocation($statements_source, $expr->args[0]->value)),
$statements_source->getSuppressedIssues()
);
}
case 'Symfony\Component\HttpFoundation\Request::getcontent':
if ($return_type_candidate) {
$removeType = 'resource';
if (isset($expr->args[0]->value->name->parts[0])) {
/** @psalm-suppress MixedArrayAccess */
$removeType = 'true' === $expr->args[0]->value->name->parts[0] ? 'string' : 'resource';
}
$return_type_candidate->removeType($removeType);
}
break;
}
}
}

View File

@ -9,6 +9,6 @@ class ContainerDependency extends CodeIssue
{
public function __construct(CodeLocation $code_location)
{
parent::__construct('Container must not inject into services as dependency!', $code_location);
parent::__construct('Container must not inject into services as dependency! Use dependency-injection.', $code_location);
}
}

View File

@ -0,0 +1,74 @@
Feature: Request getContent
Symfony Request has getContent method on which return type changes based on argument
Background:
Given I have the following config
"""
<?xml version="1.0"?>
<psalm totallyTyped="true">
<projectFiles>
<directory name="."/>
<ignoreFiles> <directory name="../../vendor"/> </ignoreFiles>
</projectFiles>
<plugins>
<pluginClass class="Seferov\SymfonyPsalmPlugin\Plugin"/>
</plugins>
</psalm>
"""
Scenario: Asserting '$request->getContent()' without any argument returns string
Given I have the following code
"""
<?php
use Symfony\Component\HttpFoundation\Request;
class App
{
public function index(Request $request): void
{
json_decode($request->getContent());
}
}
"""
When I run Psalm
Then I see no errors
Scenario: Asserting '$request->getContent(false)' returns string
Given I have the following code
"""
<?php
use Symfony\Component\HttpFoundation\Request;
class App
{
public function index(Request $request): void
{
json_decode($request->getContent(false));
}
}
"""
When I run Psalm
Then I see no errors
Scenario: Asserting '$request->getContent(true)' returns resource
Given I have the following code
"""
<?php
use Symfony\Component\HttpFoundation\Request;
class App
{
public function index(Request $request): void
{
json_decode($request->getContent(true));
}
}
"""
When I run Psalm
Then I see these errors
| Type | Message |
| InvalidArgument | Argument 1 of json_decode expects string, resource provided |