diff --git a/src/Rules/DoctrineKeyValueStyleRule.php b/src/Rules/DoctrineKeyValueStyleRule.php index 0ab613e2..a8e408c6 100644 --- a/src/Rules/DoctrineKeyValueStyleRule.php +++ b/src/Rules/DoctrineKeyValueStyleRule.php @@ -9,7 +9,9 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\New_; use PhpParser\Node\Name\FullyQualified; +use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; @@ -109,13 +111,36 @@ public function processNode(Node $callLike, Scope $scope): array return []; } - $args = $callLike->getArgs(); + // Reorder arguments to account for named parameters + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $callLike->getArgs(), + $methodReflection->getVariants(), + $methodReflection->getNamedArgumentsVariants(), + ); - if (\count($args) < 1) { + if ($callLike instanceof MethodCall) { + $reorderedCall = ArgumentsNormalizer::reorderMethodArguments( + $parametersAcceptor, + $callLike, + ); + } else { + $reorderedCall = ArgumentsNormalizer::reorderNewArguments( + $parametersAcceptor, + $callLike, + ); + } + + if ($reorderedCall === null) { + return []; + } + $reorderedArgs = $reorderedCall->getArgs(); + + if (\count($reorderedArgs) < 1) { return []; } - $tableExpr = $args[0]->value; + $tableExpr = $reorderedArgs[0]->value; $tableType = $scope->getType($tableExpr); $tableNames = $tableType->getConstantStrings(); if (\count($tableNames) === 0) { @@ -143,11 +168,11 @@ public function processNode(Node $callLike, Scope $scope): array foreach ($arrayArgPositions as $arrayArgPosition) { // If the argument doesn't exist, just skip it since we don't want // to error in case it has a default value - if (! \array_key_exists($arrayArgPosition, $args)) { + if (! \array_key_exists($arrayArgPosition, $reorderedArgs)) { continue; } - $argType = $scope->getType($args[$arrayArgPosition]->value); + $argType = $scope->getType($reorderedArgs[$arrayArgPosition]->value); $argArrays = $argType->getConstantArrays(); if (\count($argArrays) === 0) { $errors[] = 'Argument #' . $arrayArgPosition . ' is not a constant array, got ' . $argType->describe(VerbosityLevel::precise()); diff --git a/tests/rules/DoctrineKeyValueStyleRuleTest.php b/tests/rules/DoctrineKeyValueStyleRuleTest.php index e87a4f36..42dcf423 100644 --- a/tests/rules/DoctrineKeyValueStyleRuleTest.php +++ b/tests/rules/DoctrineKeyValueStyleRuleTest.php @@ -111,4 +111,18 @@ public function testLaxIntegerRanges(): void { $this->analyse([__DIR__ . '/data/doctrine-key-value-style-integer-ranges.php'], []); } + + public function testNamedParameters(): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0'); + } + + $this->analyse([__DIR__ . '/data/doctrine-key-value-style-named-parameters.php'], [ + [ + 'Query error: Column "ada.not_a_column" does not exist', + 16, + ], + ]); + } } diff --git a/tests/rules/data/doctrine-key-value-style-named-parameters.php b/tests/rules/data/doctrine-key-value-style-named-parameters.php new file mode 100644 index 00000000..2c173725 --- /dev/null +++ b/tests/rules/data/doctrine-key-value-style-named-parameters.php @@ -0,0 +1,18 @@ += 8.0 + +namespace DoctrineKeyValueStyleRuleTest; + +use staabm\PHPStanDba\Tests\Fixture\Connection; + +class Foo +{ + public function noErrorWithNamedParameters(Connection $conn) + { + $conn->assembleOneArray(cols: ['email' => 'foo'], tableName: 'ada'); + } + + public function errorWithNamedParameters(Connection $conn) + { + $conn->assembleOneArray(cols: ['not_a_column' => 'foo'], tableName: 'ada'); + } +}