-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
167 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
rules: | ||
- ErickSkrauch\PHPStan\Yii2\Rule\CreateConfigurableObjectRule | ||
- ErickSkrauch\PHPStan\Yii2\Rule\CreateObjectRule |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace ErickSkrauch\PHPStan\Yii2\Rule; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Expr\New_; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Reflection\ParametersAcceptorSelector; | ||
use PHPStan\Reflection\ReflectionProvider; | ||
use PHPStan\Rules\Rule; | ||
use yii\base\Configurable; | ||
|
||
/** | ||
* @implements Rule<New_> | ||
*/ | ||
final class CreateConfigurableObjectRule implements Rule { | ||
|
||
private ReflectionProvider $reflectionProvider; | ||
|
||
private YiiConfigHelper $configHelper; | ||
|
||
public function __construct(ReflectionProvider $reflectionProvider, YiiConfigHelper $configHelper) { | ||
$this->reflectionProvider = $reflectionProvider; | ||
$this->configHelper = $configHelper; | ||
} | ||
|
||
public function getNodeType(): string { | ||
return New_::class; | ||
} | ||
|
||
/** | ||
* @param New_ $node | ||
*/ | ||
public function processNode(Node $node, Scope $scope): array { | ||
$calledOn = $node->class; | ||
if (!$calledOn instanceof Node\Name) { | ||
return []; | ||
} | ||
|
||
$className = $calledOn->toString(); | ||
|
||
// Invalid call, leave it for another rules | ||
if (!$this->reflectionProvider->hasClass($className)) { | ||
return []; | ||
} | ||
|
||
$class = $this->reflectionProvider->getClass($className); | ||
// This rule intended for use only with Configurable interface | ||
if (!$class->is(Configurable::class)) { | ||
return []; | ||
} | ||
|
||
$constructorParams = ParametersAcceptorSelector::selectSingle($class->getConstructor()->getVariants())->getParameters(); | ||
$lastArgName = $constructorParams[array_key_last($constructorParams)]->getName(); | ||
|
||
$args = $node->args; | ||
foreach ($args as $arg) { | ||
// Try to find config by named argument | ||
if ($arg instanceof Node\Arg && $arg->name !== null && $arg->name->name === $lastArgName) { | ||
$configArg = $arg; | ||
break; | ||
} | ||
} | ||
|
||
// Attempt to find by named arg failed, try to find it by index | ||
if (!isset($configArg) && isset($args[count($constructorParams) - 1])) { | ||
$configArg = $args[count($constructorParams) - 1]; | ||
// At this moment I don't know what to do with variadic arguments | ||
if (!$configArg instanceof Node\Arg) { | ||
return []; | ||
} | ||
} | ||
|
||
// Config arg wasn't specified, so nothing to validate | ||
if (!isset($configArg)) { | ||
return []; | ||
} | ||
|
||
$configArgType = $scope->getType($configArg->value); | ||
$errors = []; | ||
foreach ($configArgType->getConstantArrays() as $constantArray) { | ||
$errors = array_merge($errors, $this->configHelper->validateArray($class, $constantArray, $scope)); | ||
} | ||
|
||
return $errors; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace ErickSkrauch\PHPStan\Yii2\Tests\Rule; | ||
|
||
use ErickSkrauch\PHPStan\Yii2\Rule\CreateConfigurableObjectRule; | ||
use ErickSkrauch\PHPStan\Yii2\Rule\YiiConfigHelper; | ||
use ErickSkrauch\PHPStan\Yii2\Tests\ConfigTrait; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<CreateConfigurableObjectRule> | ||
*/ | ||
final class CreateConfigurableObjectRuleTest extends RuleTestCase { | ||
use ConfigTrait; | ||
|
||
public function testRule(): void { | ||
$this->analyse([__DIR__ . '/_data/create_configurable_object_valid.php'], []); | ||
$this->analyse([__DIR__ . '/_data/create_configurable_object_invalid.php'], [ | ||
['Property ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent::$privateStringProp (string) does not accept int.', 10], | ||
['Property ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent::$privateStringProp (string) does not accept int.', 15], | ||
]); | ||
} | ||
|
||
protected function getRule(): Rule { | ||
return new CreateConfigurableObjectRule( | ||
self::createReflectionProvider(), | ||
self::getContainer()->getByType(YiiConfigHelper::class), | ||
); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
use ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent; | ||
|
||
// Config param wasn't used | ||
new MyComponent('string', 123); | ||
|
||
// Param passed as a config arg | ||
new MyComponent('string', 123, [ | ||
'privateStringProp' => 123, | ||
]); | ||
|
||
// Param passed as a named arg | ||
new MyComponent('string', config: [ | ||
'privateStringProp' => 123, | ||
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
use ErickSkrauch\PHPStan\Yii2\Tests\Yii\MyComponent; | ||
|
||
// Config param wasn't used | ||
new MyComponent('string', 123); | ||
|
||
// Param passed as a config arg | ||
new MyComponent('string', 123, [ | ||
'privateStringProp' => 'string', | ||
]); | ||
|
||
// Param passed as a named arg | ||
new MyComponent('string', config: [ | ||
'privateStringProp' => 'string', | ||
]); | ||
|
||
// Some real world usage | ||
new \yii\widgets\DetailView([ | ||
'model' => new \ErickSkrauch\PHPStan\Yii2\Tests\Yii\Article(), | ||
'attributes' => [ | ||
'id', | ||
'text', | ||
], | ||
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters