mirror of
https://github.com/danog/psalm-plugin-symfony.git
synced 2024-11-26 11:55:00 +01:00
[console] params analyze considering PHP8 named arguments (#183)
This commit is contained in:
parent
f2a2aee2c8
commit
ce6bb2995b
18
.github/workflows/integrate.yaml
vendored
18
.github/workflows/integrate.yaml
vendored
@ -131,17 +131,9 @@ jobs:
|
||||
- name: "Build acceptance tests with codeception"
|
||||
run: vendor/bin/codecept build
|
||||
|
||||
- name: "Run base acceptance tests with codeception"
|
||||
run: vendor/bin/codecept run -v -g symfony-common
|
||||
- name: "Run acceptance tests with codeception"
|
||||
run: vendor/bin/codecept run -v -g symfony-common -g symfony-${{ matrix.symfony-version }}
|
||||
|
||||
- name: "Run Symfony 3 acceptance tests with codeception"
|
||||
if: matrix.symfony-version == '3'
|
||||
run: vendor/bin/codecept run -v -g symfony-3
|
||||
|
||||
- name: "Run symfony 4 acceptance tests with codeception"
|
||||
if: matrix.symfony-version == '4'
|
||||
run: vendor/bin/codecept run -v -g symfony-4
|
||||
|
||||
- name: "Run Symfony 5 acceptance tests with codeception"
|
||||
if: matrix.symfony-version == '5'
|
||||
run: vendor/bin/codecept run -v -g symfony-5
|
||||
- name: "Run acceptance tests with codeception PHP8 only tests"
|
||||
run: vendor/bin/codecept run -v -g php-8
|
||||
if: matrix.php-version == '8.0'
|
||||
|
@ -25,6 +25,7 @@ use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Union;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
{
|
||||
@ -116,17 +117,20 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
*/
|
||||
private static function analyseArgument(array $args, StatementsSource $statements_source): void
|
||||
{
|
||||
$identifier = self::getNodeIdentifier($args[0]->value);
|
||||
$normalizedParams = self::normalizeArgumentParams($args);
|
||||
|
||||
$identifier = self::getNodeIdentifier($normalizedParams['name']->value);
|
||||
if (!$identifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($args) > 1) {
|
||||
$modeParam = $normalizedParams['mode'];
|
||||
if ($modeParam) {
|
||||
try {
|
||||
$mode = self::getModeValue($args[1]->value);
|
||||
$mode = self::getModeValue($modeParam->value);
|
||||
} catch (InvalidConsoleModeException $e) {
|
||||
IssueBuffer::accepts(
|
||||
new InvalidConsoleArgumentValue(new CodeLocation($statements_source, $args[1]->value)),
|
||||
new InvalidConsoleArgumentValue(new CodeLocation($statements_source, $modeParam->value)),
|
||||
$statements_source->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -144,10 +148,10 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
$returnTypes = new Union([new TString(), new TNull()]);
|
||||
}
|
||||
|
||||
if (isset($args[3])) {
|
||||
$defaultArg = $args[3];
|
||||
$defaultParam = $normalizedParams['default'];
|
||||
if ($defaultParam) {
|
||||
$returnTypes->removeType('null');
|
||||
if ($defaultArg->value instanceof Expr\ConstFetch && 'null' === $defaultArg->value->name->parts[0]) {
|
||||
if ($defaultParam->value instanceof Expr\ConstFetch && 'null' === $defaultParam->value->name->parts[0]) {
|
||||
$returnTypes->addType(new TNull());
|
||||
}
|
||||
}
|
||||
@ -160,7 +164,9 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
*/
|
||||
private static function analyseOption(array $args, StatementsSource $statements_source): void
|
||||
{
|
||||
$identifier = self::getNodeIdentifier($args[0]->value);
|
||||
$normalizedParams = self::normalizeOptionParams($args);
|
||||
|
||||
$identifier = self::getNodeIdentifier($normalizedParams['name']->value);
|
||||
if (!$identifier) {
|
||||
return;
|
||||
}
|
||||
@ -169,12 +175,13 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
$identifier = substr($identifier, 2);
|
||||
}
|
||||
|
||||
if (isset($args[2])) {
|
||||
$modeOption = $normalizedParams['mode'];
|
||||
if ($modeOption) {
|
||||
try {
|
||||
$mode = self::getModeValue($args[2]->value);
|
||||
$mode = self::getModeValue($modeOption->value);
|
||||
} catch (InvalidConsoleModeException $e) {
|
||||
IssueBuffer::accepts(
|
||||
new InvalidConsoleOptionValue(new CodeLocation($statements_source, $args[2]->value)),
|
||||
new InvalidConsoleOptionValue(new CodeLocation($statements_source, $modeOption->value)),
|
||||
$statements_source->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -186,11 +193,11 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
|
||||
$returnTypes = new Union([new TString(), new TNull()]);
|
||||
|
||||
if (isset($args[4])) {
|
||||
$defaultArg = $args[4];
|
||||
$defaultParam = $normalizedParams['default'];
|
||||
if ($defaultParam) {
|
||||
$returnTypes->removeType('null');
|
||||
if ($defaultArg->value instanceof Expr\ConstFetch) {
|
||||
switch ($defaultArg->value->name->parts[0]) {
|
||||
if ($defaultParam->value instanceof Expr\ConstFetch) {
|
||||
switch ($defaultParam->value->name->parts[0]) {
|
||||
case 'null':
|
||||
$returnTypes->addType(new TNull());
|
||||
break;
|
||||
@ -217,6 +224,46 @@ class ConsoleHandler implements AfterMethodCallAnalysisInterface
|
||||
self::$options[$identifier] = $returnTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Arg> $args
|
||||
*
|
||||
* @psalm-return array{name: Arg, shortcut: ?Arg, mode: ?Arg, description: ?Arg, default: ?Arg}
|
||||
*/
|
||||
private static function normalizeOptionParams(array $args): array
|
||||
{
|
||||
return self::normalizeParams(['name', 'shortcut', 'mode', 'description', 'default'], $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Arg> $args
|
||||
*
|
||||
* @psalm-return array{name: Arg, mode: ?Arg, description: ?Arg, default: ?Arg}
|
||||
*/
|
||||
private static function normalizeArgumentParams(array $args): array
|
||||
{
|
||||
return self::normalizeParams(['name', 'mode', 'description', 'default'], $args);
|
||||
}
|
||||
|
||||
private static function normalizeParams(array $params, array $args): array
|
||||
{
|
||||
$result = array_fill_keys($params, null);
|
||||
foreach ($args as $arg) {
|
||||
if ($arg->name) {
|
||||
$name = $arg->name->name;
|
||||
|
||||
$key = array_search($name, $params);
|
||||
Assert::integer($key);
|
||||
$params = array_slice($params, $key + 1);
|
||||
} else {
|
||||
$name = array_shift($params);
|
||||
}
|
||||
|
||||
$result[$name] = $arg;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $mode
|
||||
*/
|
||||
|
@ -0,0 +1,38 @@
|
||||
@symfony-common @php-8
|
||||
Feature: ConsoleArgument named arguments with PHP8
|
||||
|
||||
Background:
|
||||
Given I have Symfony plugin enabled
|
||||
And I have the following code preamble
|
||||
"""
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
"""
|
||||
|
||||
Scenario: Assert adding console argument skipping default arguments with named arguments works as expected
|
||||
Given I have the following code
|
||||
"""
|
||||
class MyCommand extends Command
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addArgument('test', default: 'test');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
/** @psalm-trace $argument */
|
||||
$argument = $input->getArgument('test');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| Trace | $argument: string |
|
||||
And I see no other errors
|
@ -0,0 +1,39 @@
|
||||
@symfony-common @php-8
|
||||
Feature: ConsoleOption named arguments with PHP8
|
||||
|
||||
Background:
|
||||
Given I have Symfony plugin enabled
|
||||
And I have the following code preamble
|
||||
"""
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
"""
|
||||
|
||||
Scenario: Assert adding options skipping default arguments with named arguments works as expected
|
||||
Given I have the following code
|
||||
"""
|
||||
class MyCommand extends Command
|
||||
{
|
||||
public function configure(): void
|
||||
{
|
||||
$this->addOption('test', mode: InputOption::VALUE_REQUIRED, default: 'test');
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
/** @psalm-trace $string */
|
||||
$string = $input->getOption('test');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| Trace | $string: string |
|
||||
And I see no other errors
|
Loading…
Reference in New Issue
Block a user