Support Psalm v4.0 (#98)

* Support Psalm v4.0

* no message

* no message

* no message
This commit is contained in:
Farhad Safarov 2020-10-20 16:03:17 +03:00 committed by GitHub
parent 720e554ff8
commit 6c6b4f53fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 54 additions and 32 deletions

View File

@ -61,7 +61,7 @@ jobs:
restore-keys: php-${{ matrix.php-version }}-psalm-
- name: "Run vimeo/psalm"
run: vendor/bin/psalm --find-dead-code --find-unused-psalm-suppress --diff --diff-methods --shepherd --show-info=false --stats --output-format=github
run: vendor/bin/psalm --find-dead-code --find-unused-psalm-suppress --shepherd --show-info=false --stats --output-format=github
tests:
name: "Tests"
@ -71,10 +71,9 @@ jobs:
strategy:
matrix:
php-version:
- 7.1
- 7.2
- 7.3
- 7.4
- nightly
symfony-version:
- 3

View File

@ -10,10 +10,10 @@
}
],
"require": {
"php": "^7.1",
"php": "^7.3 || ^8.0",
"ext-simplexml": "*",
"symfony/framework-bundle": "^3.0 || ^4.0 || ^5.0",
"vimeo/psalm": "^3.17"
"vimeo/psalm": "^4.0"
},
"require-dev": {
"doctrine/orm": "^2.7",

View File

@ -50,7 +50,7 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
Codebase $codebase,
array &$file_replacements = [],
Union &$return_type_candidate = null
) {
): void {
switch ($declaring_method_id) {
case 'Symfony\Component\Console\Command\Command::addargument':
self::analyseArgument($expr->args, $statements_source);

View File

@ -23,7 +23,7 @@ class ContainerDependencyHandler implements AfterFunctionLikeAnalysisInterface
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = []
) {
): ?bool {
if ($stmt instanceof Node\Stmt\ClassMethod && '__construct' === $stmt->name->name) {
foreach ($stmt->params as $param) {
if ($param->type instanceof Node\Name && ContainerInterface::class === $param->type->getAttribute('resolvedName')) {
@ -34,5 +34,7 @@ class ContainerDependencyHandler implements AfterFunctionLikeAnalysisInterface
}
}
}
return null;
}
}

View File

@ -54,7 +54,7 @@ class ContainerHandler implements AfterMethodCallAnalysisInterface, AfterClassLi
Codebase $codebase,
array &$file_replacements = [],
Union &$return_type_candidate = null
) {
): void {
if (!self::isContainerMethod($declaring_method_id, 'get')) {
if (self::isContainerMethod($declaring_method_id, 'getparameter')) {
$argument = $expr->args[0]->value;

View File

@ -37,7 +37,7 @@ class DoctrineRepositoryHandler implements AfterMethodCallAnalysisInterface, Aft
Codebase $codebase,
array &$file_replacements = [],
Union &$return_type_candidate = null
) {
): void {
if (in_array($declaring_method_id, ['Doctrine\ORM\EntityManagerInterface::getrepository', 'Doctrine\Persistence\ObjectManager::getrepository'])) {
$entityName = $expr->args[0]->value;
if ($entityName instanceof String_) {

View File

@ -8,6 +8,7 @@ use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Plugin\Hook\MethodReturnTypeProviderInterface;
use Psalm\StatementsSource;
use Psalm\Type;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TNull;
@ -25,8 +26,17 @@ class HeaderBagHandler implements MethodReturnTypeProviderInterface
];
}
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)
{
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
): ?Type\Union {
if (HeaderBag::class !== $fq_classlike_name) {
return null;
}

View File

@ -47,7 +47,7 @@ class Plugin implements PluginEntryPointInterface
/**
* {@inheritdoc}
*/
public function __invoke(RegistrationInterface $api, SimpleXMLElement $config = null)
public function __invoke(RegistrationInterface $api, SimpleXMLElement $config = null): void
{
require_once __DIR__.'/Handler/HeaderBagHandler.php';
require_once __DIR__.'/Handler/ContainerHandler.php';

View File

@ -30,14 +30,23 @@ class CachedTemplatesTainter implements MethodReturnTypeProviderInterface
return [Environment::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): void
{
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
): ?Union {
if (!$source instanceof StatementsAnalyzer) {
throw new RuntimeException(sprintf('The %s::%s hook can only be called using a %s.', __CLASS__, __METHOD__, StatementsAnalyzer::class));
}
if ('render' !== $method_name_lowercase) {
return;
return null;
}
$fake_method_call = new MethodCall(
@ -52,7 +61,7 @@ class CachedTemplatesTainter implements MethodReturnTypeProviderInterface
$firstArgument = $call_args[0]->value;
if (!$firstArgument instanceof String_) {
return;
return null;
}
$cacheClassName = CachedTemplatesMapping::getCacheClassName($firstArgument->value);
@ -66,5 +75,7 @@ class CachedTemplatesTainter implements MethodReturnTypeProviderInterface
$fake_method_call,
$context
);
return null;
}
}

View File

@ -6,9 +6,9 @@ namespace Psalm\SymfonyPsalmPlugin\Twig;
use Psalm\CodeLocation;
use Psalm\Internal\Codebase\TaintFlowGraph;
use Psalm\Internal\ControlFlow\ControlFlowNode;
use Psalm\Internal\ControlFlow\TaintSink;
use Psalm\Internal\ControlFlow\TaintSource;
use Psalm\Internal\DataFlow\DataFlowNode;
use Psalm\Internal\DataFlow\TaintSink;
use Psalm\Internal\DataFlow\TaintSource;
use Psalm\Type\TaintKind;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Expression\NameExpression;
@ -18,10 +18,10 @@ use Twig\Source;
class Context
{
/** @var array<string, ControlFlowNode> */
/** @var array<string, DataFlowNode> */
private $unassignedVariables = [];
/** @var array<string, ControlFlowNode> */
/** @var array<string, DataFlowNode> */
private $localVariables = [];
/** @var Source */
@ -36,7 +36,7 @@ class Context
$this->taint = $taint;
}
public function addSink(Node $node, ControlFlowNode $source): void
public function addSink(Node $node, DataFlowNode $source): void
{
$codeLocation = $this->getNodeLocation($node);
@ -59,7 +59,7 @@ class Context
$this->taint->addPath($source, $sink, 'arg');
}
public function taintVariable(NameExpression $expression): ControlFlowNode
public function taintVariable(NameExpression $expression): DataFlowNode
{
/** @var string $variableName */
$variableName = $expression->getAttribute('name');
@ -72,7 +72,7 @@ class Context
return $this->addVariableUsage($variableName, $sinkNode);
}
public function getTaintDestination(ControlFlowNode $taintSource, FilterExpression $expression): ControlFlowNode
public function getTaintDestination(DataFlowNode $taintSource, FilterExpression $expression): DataFlowNode
{
/** @var string $filterName */
$filterName = $expression->getNode('filter')->getAttribute('value');
@ -117,7 +117,7 @@ class Context
}
}
private function addVariableTaintNode(NameExpression $variableNode): ControlFlowNode
private function addVariableTaintNode(NameExpression $variableNode): DataFlowNode
{
/** @var string $variableName */
$variableName = $variableNode->getAttribute('name');
@ -128,7 +128,7 @@ class Context
return $taintNode;
}
private function addVariableUsage(string $variableName, ControlFlowNode $variableTaint): ControlFlowNode
private function addVariableUsage(string $variableName, DataFlowNode $variableTaint): DataFlowNode
{
if (!isset($this->localVariables[$variableName])) {
return $this->unassignedVariables[$variableName] = $variableTaint;

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Psalm\SymfonyPsalmPlugin\Twig;
use Psalm\Internal\ControlFlow\ControlFlowNode;
use Psalm\Internal\DataFlow\DataFlowNode;
use RuntimeException;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\FilterExpression;
@ -47,7 +47,7 @@ class PrintNodeAnalyzer
return false;
}
private function getTaintSource(AbstractExpression $expression): ?ControlFlowNode
private function getTaintSource(AbstractExpression $expression): ?DataFlowNode
{
if ($expression instanceof FilterExpression) {
/** @var AbstractExpression $filteredExpression */

View File

@ -6,7 +6,7 @@ namespace Psalm\SymfonyPsalmPlugin\Twig;
use Psalm\Context as PsalmContext;
use Psalm\Internal\Analyzer\FileAnalyzer;
use Psalm\Internal\ControlFlow\TaintSource;
use Psalm\Internal\DataFlow\DataFlowNode;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\NodeTraverser;
@ -17,7 +17,7 @@ class TemplateFileAnalyzer extends FileAnalyzer
PsalmContext $file_context = null,
bool $preserve_analyzers = false,
PsalmContext $global_context = null
) {
): void {
$codebase = $this->project_analyzer->getCodebase();
$taint = $codebase->taint_flow_graph;
@ -52,9 +52,9 @@ class TemplateFileAnalyzer extends FileAnalyzer
public static function getTaintNodeForTwigNamedVariable(
string $template_id,
string $variable_name
): TaintSource {
): DataFlowNode {
$label = $arg_id = strtolower($template_id).'#'.strtolower($variable_name);
return new TaintSource($arg_id, $label, null, null);
return new DataFlowNode($arg_id, $label, null, null);
}
}