[console] params analyze considering PHP8 named arguments (#183)

This commit is contained in:
Farhad Safarov 2021-06-04 12:00:11 +03:00 committed by GitHub
parent f2a2aee2c8
commit ce6bb2995b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 144 additions and 28 deletions

View File

@ -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'

View File

@ -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
*/

View File

@ -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

View File

@ -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