diff --git a/src/Rule/YiiConfigHelper.php b/src/Rule/YiiConfigHelper.php index 1017f29..b21c2c2 100644 --- a/src/Rule/YiiConfigHelper.php +++ b/src/Rule/YiiConfigHelper.php @@ -186,21 +186,24 @@ public function validateConstructorArgs(Type $object, ConstantArrayType $config, /** @var list<\PHPStan\Rules\IdentifierRuleError> $paramSearchErrors */ $paramSearchErrors = []; - /** @var \PHPStan\Reflection\ParameterReflection[] $foundParams */ + /** @var list $foundParams */ $foundParams = []; /** @var \PHPStan\Reflection\ClassReflection $classReflection */ foreach ($object->getObjectClassReflections() as $classReflection) { $constructorParams = ParametersAcceptorSelector::selectSingle($classReflection->getConstructor()->getVariants())->getParameters(); $param = null; + $paramIndex = 0; // TODO: prevent direct pass of 'config' param to constructor args (\yii\base\Configurable) if (is_int($paramName)) { $param = $constructorParams[$paramName] ?? null; + $paramIndex = $paramName; } else { - foreach ($constructorParams as $constructorParam) { + foreach ($constructorParams as $j => $constructorParam) { if ($constructorParam->getName() === $paramName) { $param = $constructorParam; + $paramIndex = $j; break; } } @@ -217,7 +220,7 @@ public function validateConstructorArgs(Type $object, ConstantArrayType $config, continue; } - $foundParams[$classReflection->getName()] = $param; + $foundParams[] = [$classReflection->getName(), $param, sprintf('#%s $%s', $paramIndex + 1, $param->getName())]; } if (empty($foundParams)) { @@ -239,15 +242,14 @@ public function validateConstructorArgs(Type $object, ConstantArrayType $config, /** @var list<\PHPStan\Rules\IdentifierRuleError> $typeCheckErrors */ $typeCheckErrors = []; $accepted = false; - foreach ($foundParams as $className => $constructorParam) { - // TODO: expose param name + foreach ($foundParams as [$className, $constructorParam, $strToReport]) { $paramType = $constructorParam->getType(); $result = $this->ruleLevelHelper->acceptsWithReason($paramType, $paramValue, $scope->isDeclareStrictTypes()); if (!$result->result) { $level = VerbosityLevel::getRecommendedLevelByType($paramType, $paramValue); $typeCheckErrors[] = RuleErrorBuilder::message(sprintf( 'Parameter %s of class %s constructor expects %s, %s given.', - $paramStrToReport, + $strToReport, $className, $paramType->describe($level), $paramValue->describe($level), diff --git a/tests/Rule/CreateObjectRuleTest.php b/tests/Rule/CreateObjectRuleTest.php index 620312f..4137873 100644 --- a/tests/Rule/CreateObjectRuleTest.php +++ b/tests/Rule/CreateObjectRuleTest.php @@ -21,14 +21,14 @@ public function testRule(): void { $this->analyse([__DIR__ . '/_data/create_object_array_valid.php'], []); $this->analyse([__DIR__ . '/_data/create_object_array_union_type_valid.php'], []); $this->analyse([__DIR__ . '/_data/create_object_classname_invalid.php'], [ - ['Parameter #1 of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects string, int given.', 6], - ['Parameter #2 of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects int, string given.', 6], - ['Parameter $stringArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects string, int given.', 7], - ['Parameter $intArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects int, string given.', 7], + ['Parameter #1 $stringArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects string, int given.', 6], + ['Parameter #2 $intArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects int, string given.', 6], + ['Parameter #1 $stringArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects string, int given.', 7], + ['Parameter #2 $intArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects int, string given.', 7], ]); $this->analyse([__DIR__ . '/_data/create_object_array_invalid.php'], [ - ['Parameter $stringArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects string, int given.', 6], - ['Parameter $intArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects int, string given.', 6], + ['Parameter #1 $stringArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects string, int given.', 6], + ['Parameter #2 $intArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects int, string given.', 6], ['Property ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent::$publicStringProp (string) does not accept int.', 6], [ 'Property ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent::$publicArrayProp (array{key: string}) does not accept array{key: false}.', @@ -46,15 +46,15 @@ public function testRule(): void { ['Property ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent::$readonlyFunctionProp is not writable.', 6], ]); $this->analyse([__DIR__ . '/_data/create_object_array_with_indexed_args.php'], [ - ['Parameter #1 of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects string, int given.', 6], - ['Parameter #2 of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects int, string given.', 6], + ['Parameter #1 $stringArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects string, int given.', 6], + ['Parameter #2 $intArg of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent constructor expects int, string given.', 6], ]); $this->analyse([__DIR__ . '/_data/create_object_array_with_missing_class.php'], [ ['Configuration params array must have "class" or "__class" key', 4], ]); $this->analyse([__DIR__ . '/_data/create_object_array_union_type_invalid.php'], [ ["The config for ErickSkrauch\PHPStan\Yii2\Tests\Yii\Article|ErickSkrauch\PHPStan\Yii2\Tests\Yii\Comment is wrong: the property field doesn't exists", 10], - ['Parameter #1 of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\BarComponent constructor expects DateTimeInterface, string given.', 17], + ['Parameter #1 $dateTime of class ErickSkrauch\PHPStan\Yii2\Tests\Yii\BarComponent constructor expects DateTimeInterface, string given.', 17], ]); }